diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/LogGraphCanvas.qml b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/LogGraphCanvas.qml
index 2d7eb08..e54781f 100644
--- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/LogGraphCanvas.qml
+++ b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/LogGraphCanvas.qml
@@ -140,7 +140,7 @@ Canvas {
id: drawingErrorDialog
title: qsTranslate("expression", "LogarithmPlotter - Drawing error")
text: ""
- function showDialog(objType, objName, error) {
+ function show(objType, objName, error) {
text = qsTranslate("error", "Error while attempting to draw %1 %2:\n%3\n\nUndoing last change.").arg(objType).arg(objName).arg(error)
open()
}
diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/module/canvas.mjs b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/module/canvas.mjs
index 75efb04..04abf68 100644
--- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/module/canvas.mjs
+++ b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/module/canvas.mjs
@@ -17,60 +17,28 @@
*/
import { Module } from "./common.mjs"
+import { FUNCTION, Interface, CanvasInterface, DialogInterface } from "./interface.mjs"
import { textsup } from "../utils.mjs"
import { Expression } from "../math/index.mjs"
import Latex from "./latex.mjs"
import Objects from "./objects.mjs"
import History from "./history.mjs"
-
-/**
- * @typedef {Settings} Canvas
- * @property {object} imageLoaders
- * @property {function()} requestPaint
- * @property {function(string)} getContext
- * @property {function(rect)} markDirty
- * @property {function(string)} loadImage
- */
-
class CanvasAPI extends Module {
constructor() {
super("Canvas", {
- canvas: {
- width: "number",
- height: "number",
- xmin: "number",
- ymax: "number",
- xzoom: "number",
- yzoom: "number",
- xaxisstep: "number",
- yaxisstep: "number",
- xlabel: "string",
- ylabel: "string",
- linewidth: "number",
- textsize: "number",
- logscalex: "boolean",
- showxgrad: "boolean",
- showygrad: "boolean",
- imageLoaders: "object",
- getContext: Function,
- markDirty: Function,
- loadImage: Function,
- requestPaint: Function,
- },
- drawingErrorDialog: {
- showDialog: Function,
- }
+ canvas: CanvasInterface,
+ drawingErrorDialog: DialogInterface
})
- /** @type {Canvas} */
+ /** @type {CanvasInterface} */
this._canvas = null
/** @type {CanvasRenderingContext2D} */
this._ctx = null
/**
- * @type {{showDialog(string, string, string)}}
+ * @type {{show(string, string, string)}}
* @private
*/
this._drawingErrorDialog = null
@@ -94,8 +62,8 @@ class CanvasAPI extends Module {
/**
* Initialize the module.
- * @param {Canvas} canvas
- * @param {{showDialog(string, string, string)}} drawingErrorDialog
+ * @param {CanvasInterface} canvas
+ * @param {{show(string, string, string)}} drawingErrorDialog
*/
initialize({ canvas, drawingErrorDialog }) {
super.initialize({ canvas, drawingErrorDialog })
diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/module/common.mjs b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/module/common.mjs
index 9cd7bef..6902745 100644
--- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/module/common.mjs
+++ b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/module/common.mjs
@@ -16,6 +16,8 @@
* along with this program. If not, see .
*/
+import { Interface } from "./interface.mjs"
+
/**
* Base class for global APIs in runtime.
*/
@@ -24,7 +26,7 @@ export class Module {
/**
*
* @param {string} name - Name of the API
- * @param {Object.|string[]|string|object)>} initializationParameters - List of parameters for the initialize function.
+ * @param {Object.} initializationParameters - List of parameters for the initialize function.
*/
constructor(name, initializationParameters = {}) {
console.log(`Loading module ${name}...`)
@@ -43,23 +45,9 @@ export class Module {
for(const [name, value] of Object.entries(this.__initializationParameters)) {
if(!options.hasOwnProperty(name))
throw new Error(`Option '${name}' of initialize of module ${this.__name} does not exist.`)
- if(typeof value === "object") {
- if(value instanceof Array)
- for(const k in value)
- if(!options[name].hasOwnProperty(k))
- throw new Error(`Option '${name}' of initialize of module ${this.__name} does not have the property '${k}'.`)
- else
- for(const [k, v] in Object.entries(value)) {
- if(!options[name].hasOwnProperty(k))
- throw new Error(`Option '${name} of initialize of module ${this.__name} does not have the property '${k}'.`)
- else if(typeof (v) === "string" && typeof (options[name][k]) !== v)
- throw new Error(`Property '${k}' of initialize option ${name} of module ${this.__name}'s type is not '${v}'.`)
- else if(typeof (v) === "object" && !(options[name][k] instanceof v))
- throw new Error(`Property '${k}' of initialize option ${name} of module ${this.__name} is not a '${v}'.`)
- }
-
-
- } else if(typeof value === "string" && typeof options[name] !== value)
+ if(typeof value === "function" && value.prototype instanceof Interface)
+ Interface.check_implementation(value, options[name])
+ else if(typeof value !== typeof options[name])
throw new Error(`Option '${name}' of initialize of module ${this.__name} is not a '${value}' (${typeof options[name]}).`)
}
this.initialized = true
diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/module/history.mjs b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/module/history.mjs
index f773402..09b45d6 100644
--- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/module/history.mjs
+++ b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/module/history.mjs
@@ -18,22 +18,16 @@
import { Module } from "./common.mjs"
import Latex from "./latex.mjs"
+import { HistoryInterface, NUMBER, STRING } from "./interface.mjs"
class HistoryAPI extends Module {
constructor() {
super("History", {
- historyObj: {
- undo: Function,
- redo: Function,
- clear: Function,
- addToHistory: Function,
- unserialize: Function,
- serialize: Function
- },
- themeTextColor: "string",
- imageDepth: "number",
- fontSize: "number"
+ historyObj: HistoryInterface,
+ themeTextColor: STRING,
+ imageDepth: NUMBER,
+ fontSize: NUMBER
})
// History QML object
this.history = null
diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/module/interface.mjs b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/module/interface.mjs
new file mode 100644
index 0000000..9da3f4d
--- /dev/null
+++ b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/module/interface.mjs
@@ -0,0 +1,130 @@
+/**
+ * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
+ * Copyright (C) 2021-2024 Ad5001
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+export const NUMBER = 0
+export const STRING = "string"
+export const BOOLEAN = true
+export const OBJECT = {}
+export const FUNCTION = () => {
+}
+
+
+export class Interface {
+ /**
+ * Checks if the class to check implements the given interface.
+ * Throws an error if the implementation does not conform to the interface.
+ * @param {typeof Interface} interface_
+ * @param {object} classToCheck
+ * @return {boolean}
+ */
+ static check_implementation(interface_, classToCheck) {
+ const properties = new interface_()
+ const interfaceName = interface_.name
+ const toCheckName = classToCheck.constructor.name
+ for(const [property, value] of Object.entries(properties))
+ if(property !== "implement") {
+ console.log(classToCheck[property], value)
+ if(!classToCheck.hasOwnProperty(property))
+ // Check if the property exist
+ throw new Error(`Property '${property}' (${typeof value}) is present in interface ${interfaceName}, but not in implementation ${toCheckName}.`)
+ else if((typeof value) !== (typeof classToCheck[property]))
+ // Compare the types
+ throw new Error(`Property '${property}' of ${interfaceName} implementation ${toCheckName} is a '${typeof classToCheck[property]}' and not a '${typeof value}'.`)
+ else if((typeof value) === "object")
+ // Test type of object.
+ if(value instanceof Interface)
+ Interface.check_implementation(value, classToCheck[property])
+ else if(value.prototype && !(classToCheck[property] instanceof value))
+ throw new Error(`Property '${property}' of ${interfaceName} implementation ${toCheckName} is not '${value.constructor.name}'.`)
+ }
+ }
+
+ /**
+ * Decorator to automatically check if a class conforms to the current interface.
+ * @param {object} class_
+ */
+ implement(class_) {
+ Interface.check_implementation(this, class_)
+ return class_
+ }
+}
+
+
+export class SettingsInterface extends Interface {
+ constructor() {
+ super()
+ this.width = NUMBER
+ this.height = NUMBER
+ this.xmin = NUMBER
+ this.ymax = NUMBER
+ this.xzoom = NUMBER
+ this.yzoom = NUMBER
+ this.xaxisstep = STRING
+ this.yaxisstep = STRING
+ this.xlabel = STRING
+ this.ylabel = STRING
+ this.linewidth = NUMBER
+ this.textsize = NUMBER
+ this.logscalex = BOOLEAN
+ this.showxgrad = BOOLEAN
+ this.showygrad = BOOLEAN
+ }
+}
+
+export class CanvasInterface extends SettingsInterface {
+ constructor() {
+ super()
+ this.imageLoaders = OBJECT
+ /** @type {function(string): CanvasRenderingContext2D} */
+ this.getContext = FUNCTION
+ /** @type {function(rect)} */
+ this.markDirty = FUNCTION
+ /** @type {function(string)} */
+ this.loadImage = FUNCTION
+ /** @type {function()} */
+ this.requestPaint = FUNCTION
+ }
+}
+
+export class RootInterface extends Interface {
+ constructor() {
+ super()
+ this.width = NUMBER
+ this.height = NUMBER
+ this.updateObjectsLists = FUNCTION
+ }
+}
+
+export class DialogInterface extends Interface {
+ constructor() {
+ super()
+ this.show = FUNCTION
+ }
+}
+
+export class HistoryInterface extends Interface {
+ constructor() {
+ super()
+ this.undo = FUNCTION
+ this.redo = FUNCTION
+ this.clear = FUNCTION
+ this.addToHistory = FUNCTION
+ this.unserialize = FUNCTION
+ this.serialize = FUNCTION
+ }
+}
\ No newline at end of file
diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/module/io.mjs b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/module/io.mjs
index 9a9d893..2a540a7 100644
--- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/module/io.mjs
+++ b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/module/io.mjs
@@ -20,55 +20,16 @@ import { Module } from "./common.mjs"
import Objects from "./objects.mjs"
import History from "./history.mjs"
import Canvas from "./canvas.mjs"
+import { DialogInterface, FUNCTION, Interface, RootInterface, SettingsInterface } from "./interface.mjs"
-/**
- * @typedef Settings
- * @property {number} width
- * @property {number} height
- * @property {number} xmin
- * @property {number} ymax
- * @property {number} xzoom
- * @property {number} yzoom
- * @property {number} xaxisstep
- * @property {number} yaxisstep
- * @property {string} xlabel
- * @property {string} ylabel
- * @property {number} linewidth
- * @property {number} textsize
- * @property {boolean} logscalex
- * @property {boolean} showxgrad
- * @property {boolean} showygrad
- */
class IOAPI extends Module {
constructor() {
super("IO", {
- root: {
- width: "number",
- height: "number",
- updateObjectsLists: Function,
- },
- alert: {
- show: Function
- },
- settings: {
- width: "number",
- height: "number",
- xmin: "number",
- ymax: "number",
- xzoom: "number",
- yzoom: "number",
- xaxisstep: "number",
- yaxisstep: "number",
- xlabel: "string",
- ylabel: "string",
- linewidth: "number",
- textsize: "number",
- logscalex: "boolean",
- showxgrad: "boolean",
- showygrad: "boolean"
- }
+ alert: DialogInterface,
+ root: RootInterface,
+ settings: SettingsInterface
})
/**
* Path of the currently opened file. Empty if no file is opened.
@@ -79,8 +40,8 @@ class IOAPI extends Module {
/**
* Initializes module with QML elements.
- * @param {{width: number, height: number, updateObjectsLists: function()}} root
- * @param {Settings} settings
+ * @param {RootInterface} root
+ * @param {SettingsInterface} settings
* @param {{show: function(string)}} alert
*/
initialize({ root, settings, alert }) {