Compare commits
4 commits
16efe31b5f
...
7f548796f2
Author | SHA1 | Date | |
---|---|---|---|
7f548796f2 | |||
15b87fc15d | |||
77ae54fa18 | |||
5da8dcefe5 |
22 changed files with 388 additions and 52 deletions
|
@ -74,6 +74,7 @@ Repeater {
|
|||
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()) {
|
||||
history.addToHistory(new HistoryLib.EditedProperty(
|
||||
|
@ -230,8 +231,8 @@ Repeater {
|
|||
dictionaryMode: paramTypeIn(propertyType, ['Dict'])
|
||||
keyType: dictionaryMode ? propertyType.keyType : 'string'
|
||||
valueType: propertyType.valueType
|
||||
preKeyLabel: (dictionaryMode ? propertyType.preKeyLabel : propertyType.label).replace(/\{name\}/g, obj.name)
|
||||
postKeyLabel: (dictionaryMode ? propertyType.postKeyLabel : '').replace(/\{name\}/g, obj.name)
|
||||
preKeyLabel: (dictionaryMode ? propertyType.preKeyLabel : propertyType.label).replace(/\{name\}/g, obj.name).replace(/\{name_\}/g, obj.name.substring(obj.name.indexOf("_")+1))
|
||||
postKeyLabel: (dictionaryMode ? propertyType.postKeyLabel : '').replace(/\{name\}/g, obj.name).replace(/\{name_\}/g, obj.name.substring(obj.name.indexOf("_")+1))
|
||||
keyRegexp: dictionaryMode ? propertyType.keyFormat : /^.+$/
|
||||
valueRegexp: propertyType.format
|
||||
forbidAdding: propertyType.forbidAdding
|
||||
|
@ -267,7 +268,7 @@ Repeater {
|
|||
return commentComponent
|
||||
else if(propertyType == 'boolean')
|
||||
return checkboxComponent
|
||||
else if(propertyType == 'Expression')
|
||||
else if(paramTypeIn(propertyType, ['Expression']))
|
||||
return expressionEditorComponent
|
||||
else if(paramTypeIn(propertyType, textTypes))
|
||||
return textEditorComponent
|
||||
|
|
|
@ -58,6 +58,9 @@ D.Dialog {
|
|||
width: 350
|
||||
height: 400
|
||||
|
||||
// Disable closing on return/enter, causing issues with autocomplete.
|
||||
onActionChosen: if(action.key == Qt.Key_Enter || action.key == Qt.Key_Return) action.accepted = false
|
||||
|
||||
Label {
|
||||
id: dlgTitle
|
||||
anchors.left: parent.left
|
||||
|
|
|
@ -49,7 +49,7 @@ ScrollView {
|
|||
id: objectsListView
|
||||
model: Object.keys(Objects.types)
|
||||
//width: implicitWidth //objectListList.width - (implicitHeight > objectListList.parent.height ? 20 : 0)
|
||||
implicitHeight: contentItem.childrenRect.height + footer.height + 10
|
||||
implicitHeight: contentItem.childrenRect.height + footerItem.height + 10
|
||||
|
||||
delegate: ListView {
|
||||
id: objTypeList
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
/**
|
||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||
* Copyright (C) 2022 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 2.12
|
||||
import QtQuick.Controls 2.12
|
||||
|
||||
/*!
|
||||
\qmltype AutocompletionCategory
|
||||
\inqmlmodule eu.ad5001.LogarithmPlotter.Setting
|
||||
\brief ListView representing a category of autocompletion.
|
||||
|
||||
\sa ExpressionEditor
|
||||
*/
|
||||
ListView {
|
||||
id: listFiltered
|
||||
/*!
|
||||
\qmlproperty int AutocompletionCategory::itemStartIndex
|
||||
Start index of the first element in this list compared to the global autocompletion index.
|
||||
*/
|
||||
property int itemStartIndex: 0
|
||||
/*!
|
||||
\qmlproperty int AutocompletionCategory::itemSelected
|
||||
The global autocompletion index.
|
||||
*/
|
||||
property int itemSelected: 0
|
||||
|
||||
/*!
|
||||
\qmlproperty string AutocompletionCategory::category
|
||||
Name of the category.
|
||||
*/
|
||||
property string category: ""
|
||||
|
||||
/*!
|
||||
\qmlproperty var AutocompletionCategory::categoryItems
|
||||
List of items in this category. To be filtered by the autocomplete to filters.
|
||||
*/
|
||||
property var categoryItems: []
|
||||
|
||||
/*!
|
||||
\qmlproperty var AutocompletionCategory::autocompleteGenerator
|
||||
Javascript function taking the name of the item to create an autocompletion item (dictionary with
|
||||
a 'text', 'annotation' 'autocomplete', and 'cursorFinalOffset' keys.
|
||||
*/
|
||||
property var autocompleteGenerator: (item) => {return {'text': item, 'autocomplete': item, 'annotation': '', 'cursorFinalOffset': 0}}
|
||||
|
||||
/*!
|
||||
\qmlproperty string AutocompletionCategory::baseText
|
||||
Text to autocomplete.
|
||||
*/
|
||||
property string baseText: ""
|
||||
width: parent.width
|
||||
visible: model.length > 0
|
||||
implicitHeight: contentItem.childrenRect.height
|
||||
model: parent.visible ? categoryItems.filter((item) => item.includes(baseText)).map(autocompleteGenerator) : []
|
||||
|
||||
header: Column {
|
||||
width: listFiltered.width
|
||||
spacing: 2
|
||||
topPadding: 5
|
||||
bottomPadding: 5
|
||||
|
||||
Text {
|
||||
leftPadding: 5
|
||||
text: listFiltered.category
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
height: 1
|
||||
color: 'black'
|
||||
width: parent.width
|
||||
}
|
||||
}
|
||||
|
||||
delegate: Rectangle {
|
||||
property bool selected: index + listFiltered.itemStartIndex == listFiltered.itemSelected
|
||||
|
||||
width: listFiltered.width
|
||||
height: Math.max(autocompleteText.height, annotationText.height)
|
||||
color: selected ? sysPalette.highlight : 'transparent'
|
||||
|
||||
Text {
|
||||
id: autocompleteText
|
||||
topPadding: 2
|
||||
bottomPadding: 2
|
||||
leftPadding: 15
|
||||
text: listFiltered.model[index].text
|
||||
color: parent.selected ? sysPalette.highlightedText : sysPalette.windowText
|
||||
}
|
||||
|
||||
Text {
|
||||
id: annotationText
|
||||
anchors.right: parent.right
|
||||
topPadding: 2
|
||||
bottomPadding: 2
|
||||
rightPadding: 15
|
||||
text: listFiltered.model[index].annotation
|
||||
color: parent.selected ? sysPaletteIn.highlightedText : sysPaletteIn.windowText
|
||||
}
|
||||
}
|
||||
}
|
|
@ -19,9 +19,10 @@
|
|||
import QtQuick.Controls 2.12
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Dialogs 1.3 as D
|
||||
import eu.ad5001.LogarithmPlotter.Popup 1.0 as Popup
|
||||
import eu.ad5001.LogarithmPlotter.Popup 1.0 as P
|
||||
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
|
||||
|
||||
|
||||
|
@ -30,7 +31,7 @@ import "../js/parsing/parsing.js" as Parsing
|
|||
\inqmlmodule eu.ad5001.LogarithmPlotter.Setting
|
||||
\brief Setting to edit strings and numbers.
|
||||
|
||||
\sa EditorDialog, Settings, Icon
|
||||
\sa EditorDialog, AutocompletionCategory
|
||||
*/
|
||||
Item {
|
||||
id: control
|
||||
|
@ -60,6 +61,11 @@ Item {
|
|||
Used to prevent circular dependency.
|
||||
*/
|
||||
property string self: ""
|
||||
/*!
|
||||
\qmlproperty var ExpressionEditor::variables
|
||||
Accepted variables for the expression.
|
||||
*/
|
||||
property var variables: []
|
||||
/*!
|
||||
\qmlproperty string ExpressionEditor::placeholderText
|
||||
Value of the editor.
|
||||
|
@ -151,6 +157,8 @@ Item {
|
|||
focus: true
|
||||
selectByMouse: true
|
||||
|
||||
property var tokens: parent.tokens(text)
|
||||
|
||||
Keys.priority: Keys.BeforeItem // Required for knowing which key the user presses.
|
||||
|
||||
onEditingFinished: {
|
||||
|
@ -165,7 +173,44 @@ Item {
|
|||
}
|
||||
}
|
||||
|
||||
//onTextEdited: acPopupContent.itemSelected = 0
|
||||
|
||||
onActiveFocusChanged: {
|
||||
if(activeFocus)
|
||||
autocompletePopup.open()
|
||||
else
|
||||
autocompletePopup.close()
|
||||
}
|
||||
|
||||
Keys.onUpPressed: function(event) {
|
||||
if(acPopupContent.itemSelected == 0)
|
||||
acPopupContent.itemSelected = acPopupContent.itemCount-1
|
||||
else
|
||||
acPopupContent.itemSelected = acPopupContent.itemSelected-1
|
||||
event.accepted = true
|
||||
}
|
||||
|
||||
Keys.onDownPressed: function(event) {
|
||||
if(acPopupContent.itemSelected == Math.min(acPopupContent.itemCount-1))
|
||||
acPopupContent.itemSelected = 0
|
||||
else
|
||||
acPopupContent.itemSelected = acPopupContent.itemSelected+1
|
||||
event.accepted = true
|
||||
}
|
||||
|
||||
Keys.onPressed: function(event) {
|
||||
// Autocomplete popup events
|
||||
//console.log("Pressed key:", event.key, Qt.Key_Return, Qt.Key_Enter, event.text)
|
||||
if((event.key == Qt.Key_Enter || event.key == Qt.Key_Return) && acPopupContent.itemCount > 0) {
|
||||
acPopupContent.autocomplete()
|
||||
event.accepted = true
|
||||
} else
|
||||
acPopupContent.itemSelected = 0
|
||||
/*if(event.key == Qt.Key_Left) { // TODO: Don't reset the position when the key moved is still on the same word
|
||||
if(!acPopupContent.identifierTokenTypes.includes())
|
||||
}*/
|
||||
|
||||
|
||||
if(event.text in openAndCloseMatches) {
|
||||
let start = selectionStart
|
||||
insert(selectionStart, event.text)
|
||||
|
@ -181,11 +226,137 @@ Item {
|
|||
verticalAlignment: TextInput.AlignVCenter
|
||||
horizontalAlignment: control.label == "" ? TextInput.AlignLeft : TextInput.AlignHCenter
|
||||
textFormat: Text.StyledText
|
||||
text: colorize(editor.text)
|
||||
text: colorize(parent.tokens)
|
||||
color: sysPalette.windowText
|
||||
//font.pixelSize: parent.font.pixelSize
|
||||
//opacity: editor.activeFocus ? 0 : 1
|
||||
}
|
||||
|
||||
Popup {
|
||||
id: autocompletePopup
|
||||
x: 0
|
||||
y: parent.height
|
||||
closePolicy: Popup.NoAutoClose
|
||||
|
||||
width: editor.width
|
||||
height: acPopupContent.height
|
||||
padding: 0
|
||||
|
||||
Column {
|
||||
id: acPopupContent
|
||||
width: parent.width
|
||||
|
||||
readonly property var identifierTokenTypes: [
|
||||
Parsing.TokenType.VARIABLE,
|
||||
Parsing.TokenType.FUNCTION,
|
||||
Parsing.TokenType.CONSTANT
|
||||
]
|
||||
property var currentToken: getTokenAt(editor.tokens, editor.cursorPosition)
|
||||
visible: currentToken != null && identifierTokenTypes.includes(currentToken.type)
|
||||
|
||||
// Focus handling.
|
||||
readonly property var lists: [variablesList, constantsList, functionsList, executableObjectsList, objectsList]
|
||||
readonly property int itemCount: variablesList.model.length, constantsList.model.length + functionsList.model.length + executableObjectsList.model.length + objectsList.model.length
|
||||
property int itemSelected: 0
|
||||
|
||||
/*!
|
||||
\qmlmethod var ExpressionEditor::autocompleteAt(int idx)
|
||||
Returns the autocompletion text information at a given position.
|
||||
The information contains key 'text' (description text), 'autocomplete' (text to insert)
|
||||
and 'cursorFinalOffset' (amount to add to the cursor's position after the end of the autocomplete)
|
||||
*/
|
||||
function autocompleteAt(idx) {
|
||||
if(idx >= itemCount) return ""
|
||||
let startIndex = 0
|
||||
for(let list of lists) {
|
||||
if(idx < startIndex + list.model.length)
|
||||
return list.model[idx-startIndex]
|
||||
startIndex += list.model.length
|
||||
}
|
||||
}
|
||||
/*!
|
||||
\qmlmethod var ExpressionEditor::autocomplete()
|
||||
Autocompletes with the current selected word.
|
||||
*/
|
||||
function autocomplete() {
|
||||
let autotext = autocompleteAt(itemSelected)
|
||||
let startPos = currentToken.startPosition
|
||||
editor.remove(startPos, startPos+currentToken.value.length)
|
||||
editor.insert(startPos, autotext.autocomplete)
|
||||
editor.cursorPosition = startPos+autotext.autocomplete.length+autotext.cursorFinalOffset
|
||||
}
|
||||
|
||||
AutocompletionCategory {
|
||||
id: variablesList
|
||||
|
||||
category: qsTr("Variables")
|
||||
itemStartIndex: 0
|
||||
itemSelected: parent.itemSelected
|
||||
categoryItems: control.variables
|
||||
autocompleteGenerator: (item) => {return {
|
||||
'text': item, 'annotation': '',
|
||||
'autocomplete': item + " ", 'cursorFinalOffset': 0
|
||||
}}
|
||||
baseText: parent.visible ? parent.currentToken.value : ""
|
||||
}
|
||||
|
||||
AutocompletionCategory {
|
||||
id: constantsList
|
||||
|
||||
category: qsTr("Constants")
|
||||
itemStartIndex: variablesList.model.length
|
||||
itemSelected: parent.itemSelected
|
||||
categoryItems: Parsing.CONSTANTS_LIST
|
||||
autocompleteGenerator: (item) => {return {
|
||||
'text': item, 'annotation': '',
|
||||
'autocomplete': item + " ", 'cursorFinalOffset': 0
|
||||
}}
|
||||
baseText: parent.visible ? parent.currentToken.value : ""
|
||||
}
|
||||
|
||||
AutocompletionCategory {
|
||||
id: functionsList
|
||||
|
||||
category: qsTr("Functions")
|
||||
itemStartIndex: variablesList.model.length + constantsList.model.length
|
||||
itemSelected: parent.itemSelected
|
||||
categoryItems: Parsing.FUNCTIONS_LIST
|
||||
autocompleteGenerator: (item) => {return {
|
||||
'text': item, 'annotation': '',
|
||||
'autocomplete': item+'()', 'cursorFinalOffset': -1
|
||||
}}
|
||||
baseText: parent.visible ? parent.currentToken.value : ""
|
||||
}
|
||||
|
||||
AutocompletionCategory {
|
||||
id: executableObjectsList
|
||||
|
||||
category: qsTr("Executable Objects")
|
||||
itemStartIndex: variablesList.model.length + constantsList.model.length + functionsList.model.length
|
||||
itemSelected: parent.itemSelected
|
||||
categoryItems: Objects.getObjectsName("ExecutableObject").filter(obj => obj != self)
|
||||
autocompleteGenerator: (item) => {return {
|
||||
'text': item, 'annotation': `${Objects.currentObjectsByName[item].constructor.displayType()}`,
|
||||
'autocomplete': item+'()', 'cursorFinalOffset': -1
|
||||
}}
|
||||
baseText: parent.visible ? parent.currentToken.value : ""
|
||||
}
|
||||
|
||||
AutocompletionCategory {
|
||||
id: objectsList
|
||||
|
||||
category: qsTr("Objects")
|
||||
itemStartIndex: executableObjectsList.model.length + variablesList.model.length + constantsList.model.length + functionsList.model.length
|
||||
itemSelected: parent.itemSelected
|
||||
categoryItems: Object.keys(Objects.currentObjectsByName).filter(obj => obj != self)
|
||||
autocompleteGenerator: (item) => {return {
|
||||
'text': item, 'annotation': `${Objects.currentObjectsByName[item].constructor.displayType()}`,
|
||||
'autocomplete': item+'.', 'cursorFinalOffset': 0
|
||||
}}
|
||||
baseText: parent.visible ? parent.currentToken.value : ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
|
@ -202,7 +373,7 @@ Item {
|
|||
}
|
||||
}
|
||||
|
||||
Popup.InsertCharacter {
|
||||
P.InsertCharacter {
|
||||
id: insertPopup
|
||||
|
||||
x: Math.round((parent.width - width) / 2)
|
||||
|
@ -228,7 +399,6 @@ Item {
|
|||
// Check if the expression is valid, throws error otherwise.
|
||||
if(!expr.allRequirementsFullfilled()) {
|
||||
let undefVars = expr.undefinedVariables()
|
||||
console.log(JSON.stringify(undefVars), undefVars.join(', '))
|
||||
if(undefVars.length > 1)
|
||||
throw new Error(qsTranslate('error', 'No object found with names %1.').arg(undefVars.join(', ')))
|
||||
else
|
||||
|
@ -245,15 +415,41 @@ Item {
|
|||
}
|
||||
|
||||
/*!
|
||||
\qmlmethod var ExpressionEditor::colorize(string expressionText)
|
||||
Creates an HTML colorized string of the incomplete \c expressionText.
|
||||
\qmlmethod var ExpressionEditor::tokens(string expressionText)
|
||||
Generates a list of tokens from the given.
|
||||
*/
|
||||
function tokens(text) {
|
||||
let tokenizer = new Parsing.Tokenizer(new Parsing.Input(text), true, false)
|
||||
let tokenList = []
|
||||
let token
|
||||
while((token = tokenizer.next()) != null)
|
||||
tokenList.push(token)
|
||||
return tokenList
|
||||
}
|
||||
|
||||
/*!
|
||||
\qmlmethod var ExpressionEditor::getTokenAt(var tokens, int position)
|
||||
Gets the token at the given position within the text.
|
||||
Returns null if out of bounds.
|
||||
*/
|
||||
function getTokenAt(tokenList, position) {
|
||||
let currentPosition = 0
|
||||
for(let token of tokenList)
|
||||
if(position <= (currentPosition + token.value.length))
|
||||
return token
|
||||
else
|
||||
currentPosition += token.value.length
|
||||
return null
|
||||
}
|
||||
|
||||
/*!
|
||||
\qmlmethod var ExpressionEditor::colorize(var tokenList)
|
||||
Creates an HTML colorized string of the given tokens.
|
||||
Returns the colorized and escaped expression if possible, null otherwise..
|
||||
*/
|
||||
function colorize(text) {
|
||||
let tokenizer = new Parsing.Tokenizer(new Parsing.Input(text), true, false)
|
||||
function colorize(tokenList) {
|
||||
let parsedText = ""
|
||||
let token
|
||||
while((token = tokenizer.next()) != null) {
|
||||
for(let token of tokenList) {
|
||||
switch(token.type) {
|
||||
case Parsing.TokenType.VARIABLE:
|
||||
parsedText += `<font color="${colorScheme.VARIABLE}">${token.value}</font>`
|
||||
|
|
|
@ -5,3 +5,4 @@ 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
|
||||
|
|
|
@ -75,7 +75,7 @@ class EditedPosition extends C.Action {
|
|||
}
|
||||
|
||||
export() {
|
||||
return [this.targetName, this.targetType, this.targetProperty,
|
||||
return [this.targetName, this.targetType,
|
||||
this.previousXValue.toEditableString(), this.newXValue.toEditableString(),
|
||||
this.previousYValue.toEditableString(), this.newYValue.toEditableString()]
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ var evalVariables = { // Variables not provided by expr-eval.js, needs to be pro
|
|||
"pi": Math.PI,
|
||||
"π": Math.PI,
|
||||
"inf": Infinity,
|
||||
"infinity": Infinity,
|
||||
"Infinity": Infinity,
|
||||
"∞": Infinity,
|
||||
"e": Math.E,
|
||||
|
|
|
@ -148,7 +148,9 @@ function expression(tokens) {
|
|||
|
||||
switch(type) {
|
||||
case ExprEval.INUMBER:
|
||||
if (typeof item.value === 'number' && item.value < 0) {
|
||||
if(item.value == Infinity) {
|
||||
nstack.push("\\infty")
|
||||
} else if(typeof item.value === 'number' && item.value < 0) {
|
||||
nstack.push(par(item.value));
|
||||
} else if(Array.isArray(item.value)) {
|
||||
nstack.push('[' + item.value.map(ExprEval.escapeValue).join(', ') + ']');
|
||||
|
|
|
@ -58,11 +58,12 @@ function getObjectsName(objType) {
|
|||
* @return {array} List of names of the objects.
|
||||
*/
|
||||
if(objType == "ExecutableObject") {
|
||||
var types = getExecutableTypes()
|
||||
var elementNames = ['']
|
||||
types.forEach(function(elemType){
|
||||
// NOTE: QMLJS does not support flatMap.
|
||||
// return getExecutableTypes().flatMap(elemType => currentObjects[elemType].map(obj => obj.name))
|
||||
let types = getExecutableTypes()
|
||||
let elementNames = ['']
|
||||
for(let elemType of types)
|
||||
elementNames = elementNames.concat(currentObjects[elemType].map(obj => obj.name))
|
||||
})
|
||||
return elementNames
|
||||
}
|
||||
if(currentObjects[objType] == undefined) return []
|
||||
|
|
|
@ -30,9 +30,10 @@
|
|||
* Creates a new name for an object, based on the \c allowedLetters.
|
||||
* If variables with each of the allowedLetters is created, a subscript
|
||||
* number is added to the name.
|
||||
* @param {string} prefix - Prefix to the name.
|
||||
* @return {string} New unused name for a new object.
|
||||
*/
|
||||
function getNewName(allowedLetters) {
|
||||
function getNewName(allowedLetters, prefix='') {
|
||||
// Allows to get a new name, based on the allowed letters,
|
||||
// as well as adding a sub number when needs be.
|
||||
var newid = 0
|
||||
|
@ -40,7 +41,7 @@ function getNewName(allowedLetters) {
|
|||
do {
|
||||
var letter = allowedLetters[newid % allowedLetters.length]
|
||||
var num = Math.floor((newid - (newid % allowedLetters.length)) / allowedLetters.length)
|
||||
ret = letter + (num > 0 ? Utils.textsub(num-1) : '')
|
||||
ret = prefix + letter + (num > 0 ? Utils.textsub(num-1) : '')
|
||||
newid += 1
|
||||
} while(ret in Objects.currentObjectsByName)
|
||||
return ret
|
||||
|
@ -80,10 +81,10 @@ class DrawableObject {
|
|||
* List of properties used in the Object Editor.
|
||||
*
|
||||
* Properties are set with key as property name and
|
||||
* value as it's type name (e.g 'Expression', 'string'...),
|
||||
* value as it's type name (e.g 'numbers', 'string'...),
|
||||
* an Enum for enumerations, an ObjectType for DrawableObjects
|
||||
* with a specific type, a List instance for lists, a
|
||||
* Dictionary instance for dictionaries...
|
||||
* Dictionary instance for dictionaries, an Expression for expressions...
|
||||
*
|
||||
* If the property is to be translated, the key should be passed
|
||||
* through the QT_TRANSLATE_NOOP macro in that form:
|
||||
|
|
|
@ -30,7 +30,7 @@ class Function extends Common.ExecutableObject {
|
|||
static displayType(){return qsTr('Function')}
|
||||
static displayTypeMultiple(){return qsTr('Functions')}
|
||||
static properties() {return {
|
||||
[QT_TRANSLATE_NOOP('prop','expression')]: 'Expression',
|
||||
[QT_TRANSLATE_NOOP('prop','expression')]: new P.Expression('x'),
|
||||
[QT_TRANSLATE_NOOP('prop','definitionDomain')]: 'Domain',
|
||||
[QT_TRANSLATE_NOOP('prop','destinationDomain')]: 'Domain',
|
||||
'comment1': QT_TRANSLATE_NOOP(
|
||||
|
|
|
@ -35,7 +35,7 @@ class GainBode extends Common.ExecutableObject {
|
|||
static properties() {return {
|
||||
[QT_TRANSLATE_NOOP('prop','om_0')]: new P.ObjectType('Point'),
|
||||
[QT_TRANSLATE_NOOP('prop','pass')]: P.Enum.BodePass,
|
||||
[QT_TRANSLATE_NOOP('prop','gain')]: 'Expression',
|
||||
[QT_TRANSLATE_NOOP('prop','gain')]: new P.Expression(),
|
||||
[QT_TRANSLATE_NOOP('prop','labelPosition')]: P.Enum.Position,
|
||||
[QT_TRANSLATE_NOOP('prop','labelX')]: 'number',
|
||||
[QT_TRANSLATE_NOOP('prop','omGraduation')]: 'boolean'
|
||||
|
|
|
@ -32,7 +32,7 @@ class PhaseBode extends Common.ExecutableObject {
|
|||
static displayTypeMultiple(){return qsTr('Bode Phases')}
|
||||
static properties() {return {
|
||||
[QT_TRANSLATE_NOOP('prop','om_0')]: new P.ObjectType('Point'),
|
||||
[QT_TRANSLATE_NOOP('prop','phase')]: 'Expression',
|
||||
[QT_TRANSLATE_NOOP('prop','phase')]: new P.Expression(),
|
||||
[QT_TRANSLATE_NOOP('prop','unit')]: new P.Enum('°', 'deg', 'rad'),
|
||||
[QT_TRANSLATE_NOOP('prop','labelPosition')]: P.Enum.Position,
|
||||
[QT_TRANSLATE_NOOP('prop','labelX')]: 'number'
|
||||
|
|
|
@ -30,8 +30,8 @@ class Point extends Common.DrawableObject {
|
|||
static displayTypeMultiple(){return qsTr('Points')}
|
||||
|
||||
static properties() {return {
|
||||
[QT_TRANSLATE_NOOP('prop','x')]: 'Expression',
|
||||
[QT_TRANSLATE_NOOP('prop','y')]: 'Expression',
|
||||
[QT_TRANSLATE_NOOP('prop','x')]: new P.Expression(),
|
||||
[QT_TRANSLATE_NOOP('prop','y')]: new P.Expression(),
|
||||
[QT_TRANSLATE_NOOP('prop','labelPosition')]: P.Enum.Position,
|
||||
[QT_TRANSLATE_NOOP('prop','pointStyle')]: new P.Enum('●', '✕', '+')
|
||||
}}
|
||||
|
|
|
@ -34,12 +34,12 @@ class RepartitionFunction extends Common.ExecutableObject {
|
|||
'comment',
|
||||
'Note: Specify the probability for each value.'
|
||||
),
|
||||
[QT_TRANSLATE_NOOP('prop','probabilities')]: new P.Dictionary('string', 'float', /^-?[\d.,]+$/, /^-?[\d\.,]+$/, 'P({name} = ', ') = '),
|
||||
[QT_TRANSLATE_NOOP('prop','probabilities')]: new P.Dictionary('string', 'float', /^-?[\d.,]+$/, /^-?[\d\.,]+$/, 'P({name_} = ', ') = '),
|
||||
}}
|
||||
|
||||
constructor(name = null, visible = true, color = null, labelContent = 'name + value',
|
||||
beginIncluded = true, drawLineEnds = true, probabilities = {'0': '0'}, labelPosition = 'above', labelX = 1) {
|
||||
if(name == null) name = Common.getNewName('XYZUVW')
|
||||
if(name == null) name = Common.getNewName('XYZUVW', "F_")
|
||||
super(name, visible, color, labelContent)
|
||||
this.beginIncluded = beginIncluded
|
||||
this.drawLineEnds = drawLineEnds
|
||||
|
@ -56,14 +56,16 @@ class RepartitionFunction extends Common.ExecutableObject {
|
|||
|
||||
|
||||
getReadableString() {
|
||||
var keys = Object.keys(this.probabilities).sort((a,b) => a-b);
|
||||
return `F_${this.name}(x) = P(${this.name} ≤ x)\n` + keys.map(idx => `P(${this.name}=${idx})=${this.probabilities[idx]}`).join("; ")
|
||||
let keys = Object.keys(this.probabilities).sort((a,b) => a-b);
|
||||
let varname = this.name.substring(this.name.indexOf("_")+1)
|
||||
return `${this.name}(x) = P(${varname} ≤ x)\n` + keys.map(idx => `P(${varname}=${idx})=${this.probabilities[idx]}`).join("; ")
|
||||
}
|
||||
|
||||
getLatexString() {
|
||||
let keys = Object.keys(this.probabilities).sort((a,b) => a-b);
|
||||
let varName = Latex.variable(this.name)
|
||||
return `\\begin{array}{l}F_{${varName}}(x) = P(${varName} \\le x)\\\\` + keys.map(idx => `P(${varName}=${idx})=${this.probabilities[idx]}`).join("; ") + '\\end{array}'
|
||||
let funcName = Latex.variable(this.name)
|
||||
let varName = Latex.variable(this.name.substring(this.name.indexOf("_")+1))
|
||||
return `\\begin{array}{l}{${funcName}}(x) = P(${varName} \\le x)\\\\` + keys.map(idx => `P(${varName}=${idx})=${this.probabilities[idx]}`).join("; ") + '\\end{array}'
|
||||
}
|
||||
|
||||
execute(x = 1) {
|
||||
|
@ -83,7 +85,7 @@ class RepartitionFunction extends Common.ExecutableObject {
|
|||
getLabel() {
|
||||
switch(this.labelContent) {
|
||||
case 'name':
|
||||
return `P(${this.name} ≤ x)`
|
||||
return `${this.name}(x)`
|
||||
case 'name + value':
|
||||
return this.getReadableString()
|
||||
case 'null':
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
.import "../math/latex.js" as Latex
|
||||
|
||||
|
||||
class SommeGainsBode extends Common.DrawableObject {
|
||||
class SommeGainsBode extends Common.ExecutableObject {
|
||||
static type(){return 'Somme gains Bode'}
|
||||
static displayType(){return qsTr('Bode Magnitudes Sum')}
|
||||
static displayTypeMultiple(){return qsTr('Bode Magnitudes Sum')}
|
||||
|
|
|
@ -30,8 +30,8 @@ class Text extends Common.DrawableObject {
|
|||
static displayType(){return qsTr('Text')}
|
||||
static displayTypeMultiple(){return qsTr('Texts')}
|
||||
static properties() {return {
|
||||
[QT_TRANSLATE_NOOP('prop','x')]: 'Expression',
|
||||
[QT_TRANSLATE_NOOP('prop','y')]: 'Expression',
|
||||
[QT_TRANSLATE_NOOP('prop','x')]: new P.Expression(),
|
||||
[QT_TRANSLATE_NOOP('prop','y')]: new P.Expression(),
|
||||
[QT_TRANSLATE_NOOP('prop','labelPosition')]: P.Enum.Positioning,
|
||||
[QT_TRANSLATE_NOOP('prop','text')]: 'string',
|
||||
'comment1': QT_TRANSLATE_NOOP(
|
||||
|
|
|
@ -16,6 +16,13 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
class Expression {
|
||||
constructor(...variables) {
|
||||
this.type = 'Expression'
|
||||
this.variables = variables
|
||||
}
|
||||
}
|
||||
|
||||
class Enum {
|
||||
constructor(...values) {
|
||||
this.type = 'Enum'
|
||||
|
|
|
@ -26,3 +26,6 @@ var Input = Common.InputExpression
|
|||
var TokenType = TK.TokenType
|
||||
var Token = TK.Token
|
||||
var Tokenizer = TK.ExpressionTokenizer
|
||||
|
||||
var FUNCTIONS_LIST = Reference.FUNCTIONS_LIST
|
||||
var CONSTANTS_LIST = Reference.CONSTANTS_LIST
|
||||
|
|
|
@ -64,6 +64,8 @@ const FUNCTIONS = {
|
|||
"tan": Math.tan,
|
||||
"tanh": Math.tanh,
|
||||
"trunc": Math.trunc,
|
||||
"integral": () => 0, // TODO: Implement
|
||||
"derivative": () => 0,
|
||||
}
|
||||
const FUNCTIONS_LIST = Object.keys(FUNCTIONS);
|
||||
// TODO: Complete
|
||||
|
|
|
@ -41,9 +41,10 @@ var TokenType = {
|
|||
}
|
||||
|
||||
class Token {
|
||||
constructor(type, value) {
|
||||
constructor(type, value, startPosition) {
|
||||
this.type = type;
|
||||
this.value = value;
|
||||
this.startPosition = startPosition
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -65,7 +66,7 @@ class ExpressionTokenizer {
|
|||
while(!this.input.atEnd() && WHITESPACES.includes(this.input.peek())) {
|
||||
included += this.input.next();
|
||||
}
|
||||
return new Token(TokenType.WHITESPACE, included)
|
||||
return new Token(TokenType.WHITESPACE, included, this.input.position-included.length)
|
||||
}
|
||||
|
||||
readString() {
|
||||
|
@ -80,7 +81,7 @@ class ExpressionTokenizer {
|
|||
included += this.input.next();
|
||||
}
|
||||
this.input.skip(delimitation)
|
||||
let token = new Token(TokenType.STRING, included)
|
||||
let token = new Token(TokenType.STRING, included, this.input.position-included.length)
|
||||
token.limitator = delimitation
|
||||
return token
|
||||
} else {
|
||||
|
@ -98,7 +99,7 @@ class ExpressionTokenizer {
|
|||
}
|
||||
included += this.input.next();
|
||||
}
|
||||
return new Token(TokenType.NUMBER, included)
|
||||
return new Token(TokenType.NUMBER, included, this.input.position-included.length)
|
||||
}
|
||||
|
||||
readOperator() {
|
||||
|
@ -106,7 +107,7 @@ class ExpressionTokenizer {
|
|||
while(!this.input.atEnd() && OPERATORS.includes(this.input.peek())) {
|
||||
included += this.input.next();
|
||||
}
|
||||
return new Token(TokenType.OPERATOR, included)
|
||||
return new Token(TokenType.OPERATOR, included, this.input.position-included.length)
|
||||
}
|
||||
|
||||
readIdentifier() {
|
||||
|
@ -115,11 +116,11 @@ class ExpressionTokenizer {
|
|||
identifier += this.input.next();
|
||||
}
|
||||
if(Reference.CONSTANTS_LIST.includes(identifier.toLowerCase())) {
|
||||
return new Token(TokenType.CONSTANT, identifier.toLowerCase())
|
||||
return new Token(TokenType.CONSTANT, identifier.toLowerCase(), this.input.position-identifier.length)
|
||||
} else if(Reference.FUNCTIONS_LIST.includes(identifier.toLowerCase())) {
|
||||
return new Token(TokenType.FUNCTION, identifier.toLowerCase())
|
||||
return new Token(TokenType.FUNCTION, identifier.toLowerCase(), this.input.position-identifier.length)
|
||||
} else {
|
||||
return new Token(TokenType.VARIABLE, identifier)
|
||||
return new Token(TokenType.VARIABLE, identifier, this.input.position-identifier.length)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -133,12 +134,12 @@ class ExpressionTokenizer {
|
|||
if(NUMBER_CHARS.includes(c)) return this.readNumber();
|
||||
if(IDENTIFIER_CHARS.includes(c.toLowerCase())) return this.readIdentifier();
|
||||
if(OPERATORS.includes(c)) return this.readOperator();
|
||||
if(Reference.CONSTANTS_LIST.includes(c)) return new Token(TokenType.CONSTANT, c);
|
||||
if(PUNCTUTATION.includes(c)) return new Token(TokenType.PUNCT, this.input.next());
|
||||
if(Reference.CONSTANTS_LIST.includes(c)) return new Token(TokenType.CONSTANT, this.input.next(), this.input.position-1);
|
||||
if(PUNCTUTATION.includes(c)) return new Token(TokenType.PUNCT, this.input.next(), this.input.position-1);
|
||||
if(this.errorOnUnknown)
|
||||
this.input.throw("Unknown token character " + c)
|
||||
else
|
||||
return new Token(TokenType.UNKNOWN, this.input.next());
|
||||
return new Token(TokenType.UNKNOWN, this.input.next(), this.input.position-1);
|
||||
}
|
||||
|
||||
peek() {
|
||||
|
|
Loading…
Reference in a new issue