Compare commits

..

No commits in common. "6a1f01ba1fdb410ec491f68db02a8efd9e548ef9" and "8c8964e75e6de52e99358933b11f40ba716f2812" have entirely different histories.

11 changed files with 136 additions and 358 deletions

View file

@ -212,11 +212,8 @@ Item {
} }
Component.onCompleted: { Component.onCompleted: {
Modules.History.initialize({ Modules.History.history = historyObj
historyObj, Modules.History.themeTextColor = sysPalette.windowText
themeTextColor: sysPalette.windowText.toString(), Modules.History.imageDepth = Screen.devicePixelRatio
imageDepth: Screen.devicePixelRatio,
fontSize: 14
})
} }
} }

View file

@ -120,6 +120,12 @@ Canvas {
*/ */
property bool showygrad: false property bool showygrad: false
/*!
\qmlproperty int LogGraphCanvas::maxgradx
Max power of the logarithmic scaled on the x axis in logarithmic mode.
*/
property int maxgradx: 90
/*! /*!
\qmlproperty var LogGraphCanvas::imageLoaders \qmlproperty var LogGraphCanvas::imageLoaders
Dictionary of format {image: [callback.image data]} containing data for defered image loading. Dictionary of format {image: [callback.image data]} containing data for defered image loading.
@ -133,14 +139,14 @@ Canvas {
Component.onCompleted: { Component.onCompleted: {
imageLoaders = {} imageLoaders = {}
Modules.Canvas.initialize({ canvas, drawingErrorDialog }) Modules.Canvas.initialize(canvas, drawingErrorDialog)
} }
Native.MessageDialog { Native.MessageDialog {
id: drawingErrorDialog id: drawingErrorDialog
title: qsTranslate("expression", "LogarithmPlotter - Drawing error") title: qsTranslate("expression", "LogarithmPlotter - Drawing error")
text: "" text: ""
function show(objType, objName, error) { 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) text = qsTranslate("error", "Error while attempting to draw %1 %2:\n%3\n\nUndoing last change.").arg(objType).arg(objName).arg(error)
open() open()
} }

View file

@ -128,7 +128,7 @@ ScrollView {
property string saveFilename: "" property string saveFilename: ""
Component.onCompleted: { Component.onCompleted: {
Modules.IO.initialize({ root, settings, alert }) Modules.IO.initialize(root, settings, alert)
} }
Column { Column {

View file

@ -17,28 +17,30 @@
*/ */
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"
class CanvasAPI extends Module {
constructor() {
super("Canvas", {
canvas: CanvasInterface,
drawingErrorDialog: DialogInterface
})
/** @type {CanvasInterface} */ class CanvasAPI extends Module {
constructor() {
super('Canvas', [
Modules.Objects,
Modules.History
])
/** @type {HTMLCanvasElement} */
this._canvas = null this._canvas = null
/** @type {CanvasRenderingContext2D} */ /** @type {CanvasRenderingContext2D} */
this._ctx = null this._ctx = null
/** /**
* @type {{show(string, string, string)}} * @type {Object}
* @property {function(string, string, string)} showDialog
* @private * @private
*/ */
this._drawingErrorDialog = null this._drawingErrorDialog = null
@ -60,132 +62,86 @@ class CanvasAPI extends Module {
} }
} }
/** initialize(canvasObject, drawingErrorDialog) {
* Initialize the module. this._canvas = canvasObject
* @param {CanvasInterface} canvas
* @param {{show(string, string, string)}} drawingErrorDialog
*/
initialize({ canvas, drawingErrorDialog }) {
super.initialize({ canvas, drawingErrorDialog })
this._canvas = canvas
this._drawingErrorDialog = drawingErrorDialog this._drawingErrorDialog = drawingErrorDialog
} }
get width() { get width() { return this._canvas.width }
if(!this.initialized) throw new Error("Attempting width before initialize!")
return this._canvas.width
}
get height() { get height() { return this._canvas.height }
if(!this.initialized) throw new Error("Attempting height before initialize!")
return this._canvas.height
}
/** /**
* Minimum x of the diagram, provided from settings. * Minimum x of the diagram, provided from settings.
* @returns {number} * @returns {number}
*/ */
get xmin() { get xmin() { return this._canvas.xmin }
if(!this.initialized) throw new Error("Attempting xmin before initialize!")
return this._canvas.xmin
}
/** /**
* Zoom on the x-axis of the diagram, provided from settings. * Zoom on the x-axis of the diagram, provided from settings.
* @returns {number} * @returns {number}
*/ */
get xzoom() { get xzoom() { return this._canvas.xzoom }
if(!this.initialized) throw new Error("Attempting xzoom before initialize!")
return this._canvas.xzoom
}
/** /**
* Maximum y of the diagram, provided from settings. * Maximum y of the diagram, provided from settings.
* @returns {number} * @returns {number}
*/ */
get ymax() { get ymax() { return this._canvas.ymax }
if(!this.initialized) throw new Error("Attempting ymax before initialize!")
return this._canvas.ymax
}
/** /**
* Zoom on the y-axis of the diagram, provided from settings. * Zoom on the y-axis of the diagram, provided from settings.
* @returns {number} * @returns {number}
*/ */
get yzoom() { get yzoom() { return this._canvas.yzoom }
if(!this.initialized) throw new Error("Attempting yzoom before initialize!")
return this._canvas.yzoom
}
/** /**
* Label used on the x-axis, provided from settings. * Label used on the x-axis, provided from settings.
* @returns {string} * @returns {string}
*/ */
get xlabel() { get xlabel() { return this._canvas.xlabel }
if(!this.initialized) throw new Error("Attempting xlabel before initialize!")
return this._canvas.xlabel
}
/** /**
* Label used on the y-axis, provided from settings. * Label used on the y-axis, provided from settings.
* @returns {string} * @returns {string}
*/ */
get ylabel() { get ylabel() { return this._canvas.ylabel }
if(!this.initialized) throw new Error("Attempting ylabel before initialize!")
return this._canvas.ylabel
}
/** /**
* Width of lines that will be drawn into the canvas, provided from settings. * Width of lines that will be drawn into the canvas, provided from settings.
* @returns {number} * @returns {number}
*/ */
get linewidth() { get linewidth() { return this._canvas.linewidth }
if(!this.initialized) throw new Error("Attempting linewidth before initialize!")
return this._canvas.linewidth
}
/** /**
* Font size of the text that will be drawn into the canvas, provided from settings. * Font size of the text that will be drawn into the canvas, provided from settings.
* @returns {number} * @returns {number}
*/ */
get textsize() { get textsize() { return this._canvas.textsize }
if(!this.initialized) throw new Error("Attempting textsize before initialize!")
return this._canvas.textsize
}
/** /**
* True if the canvas should be in logarithmic mode, false otherwise. * True if the canvas should be in logarithmic mode, false otherwise.
* @returns {boolean} * @returns {boolean}
*/ */
get logscalex() { get logscalex() { return this._canvas.logscalex }
if(!this.initialized) throw new Error("Attempting logscalex before initialize!")
return this._canvas.logscalex
}
/** /**
* True if the x graduation should be shown, false otherwise. * True if the x graduation should be shown, false otherwise.
* @returns {boolean} * @returns {boolean}
*/ */
get showxgrad() { get showxgrad() { return this._canvas.showxgrad }
if(!this.initialized) throw new Error("Attempting showxgrad before initialize!")
return this._canvas.showxgrad
}
/** /**
* True if the y graduation should be shown, false otherwise. * True if the y graduation should be shown, false otherwise.
* @returns {boolean} * @returns {boolean}
*/ */
get showygrad() { get showygrad() { return this._canvas.showygrad }
if(!this.initialized) throw new Error("Attempting showygrad before initialize!")
return this._canvas.showygrad
}
/** /**
* Max power of the logarithmic scaled on the x axis in logarithmic mode. * Max power of the logarithmic scaled on the x axis in logarithmic mode.
* @returns {number} * @returns {number}
*/ */
get maxgradx() { get maxgradx() {
if(!this.initialized) throw new Error("Attempting maxgradx before initialize!")
return Math.min( return Math.min(
309, // 10e309 = Infinity (beyond this land be dragons) 309, // 10e309 = Infinity (beyond this land be dragons)
Math.max( Math.max(
@ -200,7 +156,6 @@ class CanvasAPI extends Module {
// //
requestPaint() { requestPaint() {
if(!this.initialized) throw new Error("Attempting requestPaint before initialize!")
this._canvas.requestPaint() this._canvas.requestPaint()
} }
@ -208,7 +163,6 @@ class CanvasAPI extends Module {
* Redraws the entire canvas * Redraws the entire canvas
*/ */
redraw() { redraw() {
if(!this.initialized) throw new Error("Attempting redraw before initialize!")
this._ctx = this._canvas.getContext("2d") this._ctx = this._canvas.getContext("2d")
this._computeAxes() this._computeAxes()
this._reset() this._reset()
@ -217,7 +171,7 @@ class CanvasAPI extends Module {
this._drawLabels() this._drawLabels()
this._ctx.lineWidth = this.linewidth this._ctx.lineWidth = this.linewidth
for(let objType in Objects.currentObjects) { for(let objType in Objects.currentObjects) {
for(let obj of Objects.currentObjects[objType]) { for(let obj of Objects.currentObjects[objType]){
this._ctx.strokeStyle = obj.color this._ctx.strokeStyle = obj.color
this._ctx.fillStyle = obj.color this._ctx.fillStyle = obj.color
if(obj.visible) if(obj.visible)
@ -248,12 +202,12 @@ class CanvasAPI extends Module {
x: { x: {
expression: exprX, expression: exprX,
value: x1, value: x1,
maxDraw: Math.ceil(Math.max(Math.abs(this.xmin), Math.abs(this.px2x(this.width))) / x1) maxDraw: Math.ceil(Math.max(Math.abs(this.xmin), Math.abs(this.px2x(this.width)))/x1)
}, },
y: { y: {
expression: exprY, expression: exprY,
value: y1, value: y1,
maxDraw: Math.ceil(Math.max(Math.abs(this.ymax), Math.abs(this.px2y(this.height))) / y1) maxDraw: Math.ceil(Math.max(Math.abs(this.ymax), Math.abs(this.px2y(this.height)))/y1)
} }
} }
} }
@ -262,12 +216,12 @@ class CanvasAPI extends Module {
* Resets the canvas to a blank one with default setting. * Resets the canvas to a blank one with default setting.
* @private * @private
*/ */
_reset() { _reset(){
// Reset // Reset
this._ctx.fillStyle = "#FFFFFF" this._ctx.fillStyle = "#FFFFFF"
this._ctx.strokeStyle = "#000000" this._ctx.strokeStyle = "#000000"
this._ctx.font = `${this.textsize}px sans-serif` this._ctx.font = `${this.textsize}px sans-serif`
this._ctx.fillRect(0, 0, this.width, this.height) this._ctx.fillRect(0,0,this.width,this.height)
} }
/** /**
@ -279,18 +233,18 @@ class CanvasAPI extends Module {
if(this.logscalex) { if(this.logscalex) {
for(let xpow = -this.maxgradx; xpow <= this.maxgradx; xpow++) { for(let xpow = -this.maxgradx; xpow <= this.maxgradx; xpow++) {
for(let xmulti = 1; xmulti < 10; xmulti++) { for(let xmulti = 1; xmulti < 10; xmulti++) {
this.drawXLine(Math.pow(10, xpow) * xmulti) this.drawXLine(Math.pow(10, xpow)*xmulti)
} }
} }
} else { } else {
for(let x = 0; x < this.axesSteps.x.maxDraw; x += 1) { for(let x = 0; x < this.axesSteps.x.maxDraw; x+=1) {
this.drawXLine(x * this.axesSteps.x.value) this.drawXLine(x*this.axesSteps.x.value)
this.drawXLine(-x * this.axesSteps.x.value) this.drawXLine(-x*this.axesSteps.x.value)
} }
} }
for(let y = 0; y < this.axesSteps.y.maxDraw; y += 1) { for(let y = 0; y < this.axesSteps.y.maxDraw; y+=1) {
this.drawYLine(y * this.axesSteps.y.value) this.drawYLine(y*this.axesSteps.y.value)
this.drawYLine(-y * this.axesSteps.y.value) this.drawYLine(-y*this.axesSteps.y.value)
} }
} }
@ -306,10 +260,10 @@ class CanvasAPI extends Module {
let axisypx = this.x2px(axisypos) // X coordinate of Y axis let axisypx = this.x2px(axisypos) // X coordinate of Y axis
let axisxpx = this.y2px(0) // Y coordinate of X axis let axisxpx = this.y2px(0) // Y coordinate of X axis
// Drawing arrows // Drawing arrows
this.drawLine(axisypx, 0, axisypx - 10, 10) this.drawLine(axisypx, 0, axisypx-10, 10)
this.drawLine(axisypx, 0, axisypx + 10, 10) this.drawLine(axisypx, 0, axisypx+10, 10)
this.drawLine(this.width, axisxpx, this.width - 10, axisxpx - 10) this.drawLine(this.width, axisxpx, this.width-10, axisxpx-10)
this.drawLine(this.width, axisxpx, this.width - 10, axisxpx + 10) this.drawLine(this.width, axisxpx, this.width-10, axisxpx+10)
} }
/** /**
@ -322,38 +276,38 @@ class CanvasAPI extends Module {
// Labels // Labels
this._ctx.fillStyle = "#000000" this._ctx.fillStyle = "#000000"
this._ctx.font = `${this.textsize}px sans-serif` this._ctx.font = `${this.textsize}px sans-serif`
this._ctx.fillText(this.ylabel, axisypx + 10, 24) this._ctx.fillText(this.ylabel, axisypx+10, 24)
let textWidth = this._ctx.measureText(this.xlabel).width let textWidth = this._ctx.measureText(this.xlabel).width
this._ctx.fillText(this.xlabel, this.width - 14 - textWidth, axisxpx - 5) this._ctx.fillText(this.xlabel, this.width-14-textWidth, axisxpx-5)
// Axis graduation labels // Axis graduation labels
this._ctx.font = `${this.textsize - 4}px sans-serif` this._ctx.font = `${this.textsize-4}px sans-serif`
let txtMinus = this._ctx.measureText("-").width let txtMinus = this._ctx.measureText('-').width
if(this.showxgrad) { if(this.showxgrad) {
if(this.logscalex) { if(this.logscalex) {
for(let xpow = -this.maxgradx; xpow <= this.maxgradx; xpow += 1) { for(let xpow = -this.maxgradx; xpow <= this.maxgradx; xpow+=1) {
textWidth = this._ctx.measureText("10" + textsup(xpow)).width textWidth = this._ctx.measureText("10"+textsup(xpow)).width
if(xpow !== 0) if(xpow !== 0)
this.drawVisibleText("10" + textsup(xpow), this.x2px(Math.pow(10, xpow)) - textWidth / 2, axisxpx + 16 + (6 * (xpow === 1))) this.drawVisibleText("10"+textsup(xpow), this.x2px(Math.pow(10,xpow))-textWidth/2, axisxpx+16+(6*(xpow===1)))
} }
} else { } else {
for(let x = 1; x < this.axesSteps.x.maxDraw; x += 1) { for(let x = 1; x < this.axesSteps.x.maxDraw; x += 1) {
let drawX = x * this.axesSteps.x.value let drawX = x*this.axesSteps.x.value
let txtX = this.axesSteps.x.expression.simplify(x).toString().replace(/^\((.+)\)$/, "$1") let txtX = this.axesSteps.x.expression.simplify(x).toString().replace(/^\((.+)\)$/, '$1')
let textHeight = this.measureText(txtX).height let textHeight = this.measureText(txtX).height
this.drawVisibleText(txtX, this.x2px(drawX) - 4, axisxpx + this.textsize / 2 + textHeight) this.drawVisibleText(txtX, this.x2px(drawX)-4, axisxpx+this.textsize/2+textHeight)
this.drawVisibleText("-" + txtX, this.x2px(-drawX) - 4, axisxpx + this.textsize / 2 + textHeight) this.drawVisibleText('-'+txtX, this.x2px(-drawX)-4, axisxpx+this.textsize/2+textHeight)
} }
} }
} }
if(this.showygrad) { if(this.showygrad) {
for(let y = 0; y < this.axesSteps.y.maxDraw; y += 1) { for(let y = 0; y < this.axesSteps.y.maxDraw; y += 1) {
let drawY = y * this.axesSteps.y.value let drawY = y*this.axesSteps.y.value
let txtY = this.axesSteps.y.expression.simplify(y).toString().replace(/^\((.+)\)$/, "$1") let txtY = this.axesSteps.y.expression.simplify(y).toString().replace(/^\((.+)\)$/, '$1')
textWidth = this._ctx.measureText(txtY).width textWidth = this._ctx.measureText(txtY).width
this.drawVisibleText(txtY, axisypx - 6 - textWidth, this.y2px(drawY) + 4 + (10 * (y === 0))) this.drawVisibleText(txtY, axisypx-6-textWidth, this.y2px(drawY)+4+(10*(y===0)))
if(y !== 0) if(y !== 0)
this.drawVisibleText("-" + txtY, axisypx - 6 - textWidth - txtMinus, this.y2px(-drawY) + 4) this.drawVisibleText('-'+txtY, axisypx-6-textWidth-txtMinus, this.y2px(-drawY)+4)
} }
} }
this._ctx.fillStyle = "#FFFFFF" this._ctx.fillStyle = "#FFFFFF"
@ -394,7 +348,7 @@ class CanvasAPI extends Module {
drawVisibleText(text, x, y) { drawVisibleText(text, x, y) {
if(x > 0 && x < this.width && y > 0 && y < this.height) { if(x > 0 && x < this.width && y > 0 && y < this.height) {
text.toString().split("\n").forEach((txt, i) => { text.toString().split("\n").forEach((txt, i) => {
this._ctx.fillText(txt, x, y + (this.textsize * i)) this._ctx.fillText(txt, x, y+(this.textsize*i))
}) })
} }
} }
@ -409,7 +363,7 @@ class CanvasAPI extends Module {
* @param {number} height * @param {number} height
*/ */
drawVisibleImage(image, x, y, width, height) { drawVisibleImage(image, x, y, width, height) {
this._canvas.markDirty(Qt.rect(x, y, width, height)) this._canvas.markDirty(Qt.rect(x, y, width, height));
this._ctx.drawImage(image, x, y, width, height) this._ctx.drawImage(image, x, y, width, height)
} }
@ -426,7 +380,7 @@ class CanvasAPI extends Module {
theight += defaultHeight theight += defaultHeight
if(this._ctx.measureText(txt).width > twidth) twidth = this._ctx.measureText(txt).width if(this._ctx.measureText(txt).width > twidth) twidth = this._ctx.measureText(txt).width
} }
return { "width": twidth, "height": theight } return {'width': twidth, 'height': theight}
} }
/** /**
@ -438,9 +392,9 @@ class CanvasAPI extends Module {
x2px(x) { x2px(x) {
if(this.logscalex) { if(this.logscalex) {
const logxmin = Math.log(this.xmin) const logxmin = Math.log(this.xmin)
return (Math.log(x) - logxmin) * this.xzoom return (Math.log(x)-logxmin)*this.xzoom
} else } else
return (x - this.xmin) * this.xzoom return (x - this.xmin)*this.xzoom
} }
/** /**
@ -461,9 +415,9 @@ class CanvasAPI extends Module {
*/ */
px2x(px) { px2x(px) {
if(this.logscalex) { if(this.logscalex) {
return Math.exp(px / this.xzoom + Math.log(this.xmin)) return Math.exp(px/this.xzoom+Math.log(this.xmin))
} else } else
return (px / this.xzoom + this.xmin) return (px/this.xzoom+this.xmin)
} }
/** /**
@ -473,7 +427,7 @@ class CanvasAPI extends Module {
* @returns {number} * @returns {number}
*/ */
px2y(px) { px2y(px) {
return -(px / this.yzoom - this.ymax) return -(px/this.yzoom-this.ymax)
} }
/** /**
@ -494,10 +448,10 @@ class CanvasAPI extends Module {
* @param {number} y2 * @param {number} y2
*/ */
drawLine(x1, y1, x2, y2) { drawLine(x1, y1, x2, y2) {
this._ctx.beginPath() this._ctx.beginPath();
this._ctx.moveTo(x1, y1) this._ctx.moveTo(x1, y1);
this._ctx.lineTo(x2, y2) this._ctx.lineTo(x2, y2);
this._ctx.stroke() this._ctx.stroke();
} }
/** /**
@ -509,9 +463,9 @@ class CanvasAPI extends Module {
* @param {number} dashPxSize * @param {number} dashPxSize
*/ */
drawDashedLine(x1, y1, x2, y2, dashPxSize = 6) { drawDashedLine(x1, y1, x2, y2, dashPxSize = 6) {
this._ctx.setLineDash([dashPxSize / 2, dashPxSize]) this._ctx.setLineDash([dashPxSize/2, dashPxSize]);
this.drawLine(x1, y1, x2, y2) this.drawLine(x1, y1, x2, y2)
this._ctx.setLineDash([]) this._ctx.setLineDash([]);
} }
/** /**
@ -522,7 +476,7 @@ class CanvasAPI extends Module {
*/ */
renderLatexImage(ltxText, color, callback) { renderLatexImage(ltxText, color, callback) {
const onRendered = (imgData) => { const onRendered = (imgData) => {
if(!this._canvas.isImageLoaded(imgData.source) && !this._canvas.isImageLoading(imgData.source)) { if(!this._canvas.isImageLoaded(imgData.source) && !this._canvas.isImageLoading(imgData.source)){
// Wait until the image is loaded to callback. // Wait until the image is loaded to callback.
this._canvas.loadImage(imgData.source) this._canvas.loadImage(imgData.source)
this._canvas.imageLoaders[imgData.source] = [callback, imgData] this._canvas.imageLoaders[imgData.source] = [callback, imgData]
@ -542,13 +496,8 @@ class CanvasAPI extends Module {
// Context methods // Context methods
// //
get font() { get font() { return this._ctx.font }
return this._ctx.font set font(value) { return this._ctx.font = value }
}
set font(value) {
return this._ctx.font = value
}
/** /**
* Draws an act on the canvas centered on a point. * Draws an act on the canvas centered on a point.
@ -559,7 +508,7 @@ class CanvasAPI extends Module {
* @param {number} endAngle * @param {number} endAngle
* @param {boolean} counterclockwise * @param {boolean} counterclockwise
*/ */
arc(x, y, radius, startAngle, endAngle, counterclockwise = false) { arc(x, y, radius, startAngle, endAngle, counterclockwise=false) {
this._ctx.beginPath() this._ctx.beginPath()
this._ctx.arc(x, y, radius, startAngle, endAngle, counterclockwise) this._ctx.arc(x, y, radius, startAngle, endAngle, counterclockwise)
this._ctx.stroke() this._ctx.stroke()
@ -572,9 +521,9 @@ class CanvasAPI extends Module {
* @param {number} radius * @param {number} radius
*/ */
disc(x, y, radius) { disc(x, y, radius) {
this._ctx.beginPath() this._ctx.beginPath();
this._ctx.arc(x, y, radius, 0, 2 * Math.PI) this._ctx.arc(x, y, radius, 0, 2 * Math.PI)
this._ctx.fill() this._ctx.fill();
} }
/** /**

View file

@ -16,8 +16,6 @@
* 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.
*/ */
@ -26,30 +24,22 @@ export class Module {
/** /**
* *
* @param {string} name - Name of the API * @param {string} name - Name of the API
* @param {Object.<string, (Interface|string)>} initializationParameters - List of parameters for the initialize function. * @param {(Module|undefined)[]} requires - List of APIs required to initialize this one.
*/ */
constructor(name, initializationParameters = {}) { constructor(name, requires = []) {
console.log(`Loading module ${name}...`) console.log(`Loading module ${name}...`)
this.__name = name this.__check_requirements(requires, name)
this.__initializationParameters = initializationParameters
this.initialized = false
} }
/** /**
* Checks if all requirements are defined. * Checks if all requirements are defined.
* @param {Object.<string, any>} options * @param {(Module|undefined)[]} requires
* @param {string} name
*/ */
initialize(options) { __check_requirements(requires, name) {
if(this.initialized) for(let requirement of requires) {
throw new Error(`Cannot reinitialize module ${this.__name}.`) if(requirement === undefined)
for(const [name, value] of Object.entries(this.__initializationParameters)) { throw new Error(`Requirement ${requires.indexOf(requirement)} of ${name} has not been initialized.`)
if(!options.hasOwnProperty(name))
throw new Error(`Option '${name}' of initialize of module ${this.__name} does not exist.`)
if(typeof value === "function" && value.prototype instanceof Interface)
Interface.check_implementation(value, options[name])
else if(typeof value !== typeof options[name])
throw new Error(`Option '${name}' of initialize of module ${this.__name} is not a '${value}' (${typeof options[name]}).`)
} }
this.initialized = true
} }
} }

View file

@ -36,7 +36,10 @@ const evalVariables = {
export class ExprParserAPI extends Module { export class ExprParserAPI extends Module {
constructor() { constructor() {
super("ExprParser") super("ExprParser", [
/** @type {ObjectsAPI} */
Modules.Objects
])
this.currentVars = {} this.currentVars = {}
this._parser = new Parser() this._parser = new Parser()

View file

@ -18,62 +18,26 @@
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: HistoryInterface, Modules.Latex
themeTextColor: STRING, ])
imageDepth: NUMBER,
fontSize: NUMBER
})
// History QML object // History QML object
this.history = null this.history = null;
this.themeTextColor = "#FF0000" this.themeTextColor = "#ff0000";
this.imageDepth = 2 this.imageDepth = 2;
this.fontSize = 28 this.fontSize = 14;
} }
initialize({ historyObj, themeTextColor, imageDepth, fontSize }) { undo() { this.history.undo() }
super.initialize({ historyObj, themeTextColor, imageDepth, fontSize }) redo() { this.history.redo() }
console.log("Initializing history...") clear() { this.history.clear() }
this.history = historyObj addToHistory(action) { this.history.addToHistory(action) }
this.themeTextColor = themeTextColor unserialize(...data) { this.history.unserialize(...data) }
this.imageDepth = imageDepth serialize() { return this.history.serialize() }
this.fontSize = fontSize
}
undo() {
if(!this.initialized) throw new Error("Attempting undo before initialize!")
this.history.undo()
}
redo() {
if(!this.initialized) throw new Error("Attempting redo before initialize!")
this.history.redo()
}
clear() {
if(!this.initialized) throw new Error("Attempting clear before initialize!")
this.history.clear()
}
addToHistory(action) {
if(!this.initialized) throw new Error("Attempting addToHistory before initialize!")
this.history.addToHistory(action)
}
unserialize(...data) {
if(!this.initialized) throw new Error("Attempting unserialize before initialize!")
this.history.unserialize(...data)
}
serialize() {
if(!this.initialized) throw new Error("Attempting serialize before initialize!")
return this.history.serialize()
}
} }
/** @type {HistoryAPI} */ /** @type {HistoryAPI} */

View file

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

View file

@ -20,17 +20,14 @@ 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"
class IOAPI extends Module { class IOAPI extends Module {
constructor() { constructor() {
super("IO", { super("IO", [
alert: DialogInterface, Modules.Objects,
root: RootInterface, Modules.History
settings: SettingsInterface ])
})
/** /**
* Path of the currently opened file. Empty if no file is opened. * Path of the currently opened file. Empty if no file is opened.
* @type {string} * @type {string}
@ -40,13 +37,12 @@ class IOAPI extends Module {
/** /**
* Initializes module with QML elements. * Initializes module with QML elements.
* @param {RootInterface} root * @param {{width: number, height: number, updateObjectsLists: function()}} rootElement
* @param {SettingsInterface} settings * @param {Settings} settings
* @param {{show: function(string)}} alert * @param {{show: function(string)}} alert
*/ */
initialize({ root, settings, alert }) { initialize(rootElement, settings, alert) {
super.initialize({ root, settings, alert }) this.rootElement = rootElement
this.rootElement = root
this.settings = settings this.settings = settings
this.alert = alert this.alert = alert
} }
@ -56,7 +52,6 @@ class IOAPI extends Module {
* @param {string} filename * @param {string} filename
*/ */
saveDiagram(filename) { saveDiagram(filename) {
if(!this.initialized) throw new Error("Attempting saveDiagram before initialize!")
// Add extension if necessary // Add extension if necessary
if(["lpf"].indexOf(filename.split(".")[filename.split(".").length - 1]) === -1) if(["lpf"].indexOf(filename.split(".")[filename.split(".").length - 1]) === -1)
filename += ".lpf" filename += ".lpf"
@ -98,13 +93,11 @@ class IOAPI extends Module {
* @param {string} filename * @param {string} filename
*/ */
loadDiagram(filename) { loadDiagram(filename) {
if(!this.initialized) throw new Error("Attempting loadDiagram before initialize!")
if(!History.initialized) throw new Error("Attempting loadDiagram before history is initialized!")
let basename = filename.split("/").pop() let basename = filename.split("/").pop()
this.alert.show(qsTranslate("io", "Loading file '%1'.").arg(basename)) this.alert.show(qsTranslate("io", "Loading file '%1'.").arg(basename))
let data = JSON.parse(Helper.load(filename)) let data = JSON.parse(Helper.load(filename))
let error = "" let error = ""
if(data.hasOwnProperty("type") && data["type"] === "logplotv1") { if(Object.keys(data).includes("type") && data["type"] === "logplotv1") {
History.clear() History.clear()
// Importing settings // Importing settings
this.settings.saveFilename = filename this.settings.saveFilename = filename
@ -136,7 +129,7 @@ class IOAPI extends Module {
// Another way would be to change the reference as well, but I feel like the code would be less clean. // 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"]) { for(let objType in data["objects"]) {
if(Object.keys(Objects.types).includes(objType)) { if(Object.keys(Objects.types).indexOf(objType) > -1) {
Objects.currentObjects[objType] = [] Objects.currentObjects[objType] = []
for(let objData of data["objects"][objType]) { for(let objData of data["objects"][objType]) {
/** @type {DrawableObject} */ /** @type {DrawableObject} */
@ -164,7 +157,7 @@ class IOAPI extends Module {
} }
if(error !== "") { if(error !== "") {
console.log(error) console.log(error)
this.alert.show(qsTranslate("io", "Could not load file: ") + error) this.alert.show(qsTranslate("io", "Could not save file: ") + error)
// TODO: Error handling // TODO: Error handling
return return
} }

View file

@ -58,7 +58,10 @@ class LatexRenderResult {
class LatexAPI extends Module { class LatexAPI extends Module {
constructor() { constructor() {
super("Latex") super("Latex", [
/** @type {ExprParserAPI} */
Modules.ExprParser
])
/** /**
* true if latex has been enabled by the user, false otherwise. * true if latex has been enabled by the user, false otherwise.
*/ */

View file

@ -22,7 +22,10 @@ import DefaultGraph from "../preferences/default.mjs"
class PreferencesAPI extends Module { class PreferencesAPI extends Module {
constructor() { constructor() {
super('Preferences') super('Preferences', [
Modules.Canvas,
Modules.Latex
])
this.categories = { this.categories = {
[QT_TRANSLATE_NOOP('settingCategory', 'general')]: General, [QT_TRANSLATE_NOOP('settingCategory', 'general')]: General,