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