diff --git a/common/src/events.mjs b/common/src/events.mjs new file mode 100644 index 0000000..f1ec970 --- /dev/null +++ b/common/src/events.mjs @@ -0,0 +1,96 @@ +/** + * 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 . + */ + +/** + * We do not inherit the DOM's Event, because not only the DOM part is unnecessary, + * but also because it does not exist within Qt environments. + */ + + +export class BaseEvent { + /** + * @property {string} name - Name of the event. + */ + constructor(name) { + this.name = name + } +} + + +/** + * Base class for all classes which can emit events. + */ +export class BaseEventEmitter { + static emits = [] + + /** @type {Record} */ + #listeners = {} + + constructor() { + for(const eventType of this.constructor.emits) { + this.#listeners[eventType] = new Set() + } + } + + /** + * Adds a listener to an event that can be emitted by this object. + * + * @param {string} eventType - Name of the event to listen to. Throws an error if this object does not emit this kind of event. + * @param {function(BaseEvent)} eventListener - The function to be called back when the event is emitted. + */ + addEventListener(eventType, eventListener) { + if(!this.emits.includes(eventType)) { + const className = this.constructor.name + const eventTypes = this.constructor.emits.join(", ") + throw new Error(`Cannot listen to unknown event ${eventType} in class ${className}. ${className} only emits: ${eventTypes}`) + } + if(!this.#listeners[eventType].has(eventListener)) + this.#listeners[eventType].add(eventListener) + } + + /** + * Remvoes a listener from an event that can be emitted by this object. + * + * @param {string} eventType - Name of the event that was listened to. Throws an error if this object does not emit this kind of event. + * @param {function(BaseEvent)} eventListener - The function previously registered as a listener. + * @returns {boolean} True if the listener was removed, false if it was not found. + */ + removeEventListener(eventType, eventListener) { + if(!this.emits.includes(eventType)) { + const className = this.constructor.name + const eventTypes = this.constructor.emits.join(", ") + throw new Error(`Cannot listen to unknown event ${eventType} in class ${className}. ${className} only emits: ${eventTypes}`) + } + return this.#listeners[eventType].delete(eventListener) + + } + + /** + * Emits an event to all of its listeners. + * + * @param {BaseEvent} e + */ + emit(e) { + if(!(e instanceof BaseEvent)) + throw new Error("Cannot emit non event object.") + if(!this.emits.includes(e.name)) + throw new Error(`Cannot emit event '${e.name}' from class ${this.constructor.name}. ${this.constructor.name} can only emits: ${this.constructor.emits.join(", ")}.`) + for(const listener of this.#listeners[e.name]) + listener(e) + } +} diff --git a/common/src/lib/polyfills/js.mjs b/common/src/lib/polyfills/js.mjs index 43ed375..9bcf80c 100644 --- a/common/src/lib/polyfills/js.mjs +++ b/common/src/lib/polyfills/js.mjs @@ -1,4 +1,4 @@ -/** +/*! * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * Copyright (C) 2021-2024 Ad5001 * @@ -123,4 +123,4 @@ for(const [year, entries] of Object.entries(polyfills)) { for(const [context, functionName, polyfill] of entries.filter(x => x[0][x[1]] === undefined)) { context[functionName] = polyfill } -} \ No newline at end of file +} diff --git a/common/src/module/common.mjs b/common/src/module/common.mjs index 5fc9387..bdca62f 100644 --- a/common/src/module/common.mjs +++ b/common/src/module/common.mjs @@ -17,6 +17,7 @@ */ import { Interface } from "./interface.mjs" +import { BaseEventEmitter } from "../events.mjs" // Define Modules interface before they are imported. globalThis.Modules = globalThis.Modules || {} @@ -24,7 +25,7 @@ globalThis.Modules = globalThis.Modules || {} /** * Base class for global APIs in runtime. */ -export class Module { +export class Module extends BaseEventEmitter { /** @type {string} */ #name /** @type {Object.} */ @@ -36,6 +37,7 @@ export class Module { * @param {Object.} initializationParameters - List of parameters for the initialize function. */ constructor(name, initializationParameters = {}) { + super() console.log(`Loading module ${name}...`) this.#name = name this.#initializationParameters = initializationParameters diff --git a/common/src/module/interface.mjs b/common/src/module/interface.mjs index 9273c8f..f6e5a13 100644 --- a/common/src/module/interface.mjs +++ b/common/src/module/interface.mjs @@ -1,4 +1,4 @@ -/*! +/** * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * * @author Ad5001 @@ -184,4 +184,4 @@ export class HelperInterface extends Interface { * @returns {string} the loaded data - just JSON encoded, requires the "LPFv1" mime to be stripped */ load = FUNCTION -} \ No newline at end of file +} diff --git a/common/src/module/settings.mjs b/common/src/module/settings.mjs new file mode 100644 index 0000000..4487dde --- /dev/null +++ b/common/src/module/settings.mjs @@ -0,0 +1,173 @@ +/** + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2024 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import { Module } from "./common.mjs" +import { BaseEvent } from "../events.mjs" + + +/** + * Base event for when a setting was changed. + */ +class ChangedEvent extends BaseEvent { + /** + * + * @param {string} property - Name of the property that was chagned + * @param {string|number|boolean} oldValue - Old value of the property + * @param {string|number|boolean} newValue - Current (new) value of the property + * @param {boolean} byUser - True if the user is at the source of the change in the setting. + */ + constructor(property, oldValue, newValue, byUser) { + super("changed") + + this.property = property + this.oldValue = oldValue + this.newValue = newValue + this.byUser = byUser + } +} + +/** + * Module for graph settings. + */ +class SettingsAPI extends Module { + static emits = ["changed"] + + #properties = new Map([ + ['xzoom', 100], + ['yzoom', 10], + ['xmin', .5], + ['ymax', 25], + ['xaxisstep', "4"], + ['yaxisstep', "4"], + ['xlabel', ""], + ['ylabel', ""], + ['linewidth', 1], + ['textsize', 18], + ['logscalex', true], + ['showxgrad', true], + ['showygrad', true], + ]) + + constructor() { + super("Settings", { + helper: HelperInterface + }) + } + + initialize({ helper }) { + super.initialize({ helper }) + // Initialize default values. + for(const key of this.#properties.keys()) { + switch(typeof this.#properties.get(key)) { + case 'boolean': + this.set(key, helper.getSettingBool(key), false) + break + case 'number': + this.set(key, helper.getSettingInt(key), false) + break + case 'string': + this.set(key, helper.getSetting(key), false) + break + } + } + } + + /** + * Sets a setting to a given value + * + * @param {boolean} byUser - Set to true if the user is at the origin of this change. + */ + set(property, value, byUser) { + if(!this.#properties.has(property)) + throw new Error(`Property ${property} is not a setting.`) + const oldValue = this.#properties.get(property) + const propType = typeof oldValue + if(propType !== typeof value) + throw new Error(`Value of ${property} must be a ${propType}.`) + this.#properties.set(property, value) + this.emit(new ChangedEvent(property, oldValue, value, byUser === true)) + } + + /** + * Zoom on the x axis of the diagram. + * @returns {number} + */ + get xzoom() { return this.#properties.get("xzoom"); } + /** + * Zoom on the y axis of the diagram. + * @returns {number} + */ + get yzoom() { return this.#properties.get("yzoom"); } + /** + * Minimum x of the diagram. + * @returns {number} + */ + get xmin() { return this.#properties.get("xmin"); } + /** + * Maximum y of the diagram. + * @returns {number} + */ + get ymax() { return this.#properties.get("ymax"); } + /** + * Step of the x axis graduation (expression). + * @note Only available in non-logarithmic mode. + * @returns {string} + */ + get xaxisstep() { return this.#properties.get("xaxisstep"); } + /** + * Step of the y axis graduation (expression). + * @returns {string} + */ + get yaxisstep() { return this.#properties.get("yaxisstep"); } + /** + * Label used on the x axis. + * @returns {string} + */ + get xlabel() { return this.#properties.get("xlabel"); } + /** + * Label used on the y axis. + * @returns {string} + */ + get ylabel() { return this.#properties.get("ylabel"); } + /** + * Width of lines that will be drawn into the canvas. + * @returns {number} + */ + get linewidth() { return this.#properties.get("linewidth"); } + /** + * Font size of the text that will be drawn into the canvas. + * @returns {number} + */ + get textsize() { return this.#properties.get("textsize"); } + /** + * true if the canvas should be in logarithmic mode, false otherwise. + * @returns {boolean} + */ + get logscalex() { return this.#properties.get("logscalex"); } + /** + * true if the x graduation should be shown, false otherwise. + * @returns {boolean} + */ + get showxgrad() { return this.#properties.get("showxgrad"); } + /** + * true if the y graduation should be shown, false otherwise. + * @returns {boolean} + */ + get showygrad() { return this.#properties.get("showygrad"); } + +}