2022-10-18 21:24:58 +00:00
/ * *
* LogarithmPlotter - 2 D 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 . Controls 2.12
import QtQuick 2.12
import QtQuick . Dialogs 1.3 as D
import eu . ad5001 . LogarithmPlotter . Popup 1.0 as Popup
import "../js/mathlib.js" as MathLib
2022-10-18 23:16:54 +00:00
import "../js/utils.js" as Utils
import "../js/parsing/parsing.js" as Parsing
2022-10-18 21:24:58 +00:00
/ * !
\ qmltype ExpressionEditor
\ inqmlmodule eu . ad5001 . LogarithmPlotter . Setting
\ brief Setting to edit strings and numbers .
\ sa EditorDialog , Settings , Icon
* /
Item {
id: control
height: 30
/ * !
\ qmlsignal ExpressionEditor: : changed ( var newValue )
Emitted when the value of the expression has been changed .
The corresponding handler is \ c onChanged .
* /
signal changed ( var newValue )
/ * !
\ qmlproperty string ExpressionEditor: : defValue
Default editable expression value of the editor .
* /
property string defValue
/ * !
\ qmlproperty string ExpressionEditor: : value
Value of the editor .
* /
property alias value: editor . text
/ * !
\ qmlproperty string ExpressionEditor: : self
Object or context of the expression to be edited .
Used to prevent circular dependency .
* /
property string self: ""
/ * !
\ qmlproperty string ExpressionEditor: : placeholderText
Value of the editor .
* /
property alias placeholderText: editor . placeholderText
/ * !
\ qmlproperty string ExpressionEditor: : label
Label of the editor .
* /
property string label
/ * !
\ qmlproperty string ExpressionEditor: : icon
Icon path of the editor .
* /
property string icon: ""
/ * !
\ qmlproperty string ExpressionEditor: : openAndCloseMatches
Characters that when pressed , should be immediately followed up by their closing character .
2022-10-18 23:16:54 +00:00
TODO: Make it configurable .
2022-10-18 21:24:58 +00:00
* /
readonly property var openAndCloseMatches: {
"(" : ")" ,
"[" : "]" ,
"'" : "'" ,
'"' : '"'
}
2022-10-18 23:16:54 +00:00
/ * !
\ qmlproperty string ExpressionEditor: : colorScheme
Color scheme of the editor , currently based on Breeze Light .
TODO: Make it configurable .
* /
readonly property var colorScheme: {
'NORMAL' : "#1F1C1B" ,
'VARIABLE' : "#0057AE" ,
'CONSTANT' : "#5E2F00" ,
'FUNCTION' : "#644A9B" ,
'OPERATOR' : "#A44EA4" ,
'STRING' : "#9C0E0E" ,
'NUMBER' : "#805C00"
}
2022-10-18 21:24:58 +00:00
Icon {
id: iconLabel
anchors.top: parent . top
anchors.topMargin: icon == "" ? 0 : 3
source: control . visible && icon != "" ? "../icons/" + control.icon : ""
width: height
height: icon == "" || ! visible ? 0 : 24
color: sysPalette . windowText
}
Label {
id: labelItem
anchors.left: iconLabel . right
anchors.leftMargin: icon == "" ? 0 : 5
height: parent . height
anchors.top: parent . top
verticalAlignment: TextInput . AlignVCenter
//color: sysPalette.windowText
text: visible ? qsTranslate ( "control" , "%1: " ) . arg ( control . label ) : ""
visible: control . label != ""
}
D . MessageDialog {
id: parsingErrorDialog
title: qsTranslate ( "expression" , "LogarithmPlotter - Parsing error" )
text: ""
function showDialog ( propName , propValue , error ) {
text = qsTranslate ( "expression" , "Error while parsing expression for property %1:\n%2\n\nEvaluated expression: %3" ) . arg ( propName ) . arg ( error ) . arg ( propValue )
open ( )
}
}
2022-10-18 23:16:54 +00:00
2022-10-18 21:24:58 +00:00
TextField {
id: editor
anchors.top: parent . top
anchors.left: labelItem . right
anchors.leftMargin: 5
width: control . width - ( labelItem . visible ? labelItem . width + 5 : 0 ) - iconLabel . width - 5
height: parent . height
verticalAlignment: TextInput . AlignVCenter
horizontalAlignment: control . label == "" ? TextInput.AlignLeft : TextInput . AlignHCenter
2022-10-18 23:53:53 +00:00
//font.pixelSize: 14
2022-10-18 21:24:58 +00:00
text: control . defValue
2022-10-18 23:16:54 +00:00
color: "transparent" //sysPalette.windowText
2022-10-18 21:24:58 +00:00
focus: true
selectByMouse: true
Keys.priority: Keys . BeforeItem // Required for knowing which key the user presses.
onEditingFinished: {
if ( insertButton . focus || insertPopup . focus ) return
let value = text
if ( value != "" && value . toString ( ) != defValue ) {
let expr = parse ( value )
if ( expr != null ) {
control . changed ( expr )
defValue = expr . toEditableString ( )
}
}
}
Keys.onPressed: function ( event ) {
if ( event . text in openAndCloseMatches ) {
let start = selectionStart
insert ( selectionStart , event . text )
insert ( selectionEnd , openAndCloseMatches [ event . text ] )
cursorPosition = start + 1
event . accepted = true
}
}
2022-10-18 23:16:54 +00:00
Text {
id: colorizedEditor
anchors.fill: editor
verticalAlignment: TextInput . AlignVCenter
horizontalAlignment: control . label == "" ? TextInput.AlignLeft : TextInput . AlignHCenter
textFormat: Text . StyledText
text: colorize ( editor . text )
color: sysPalette . windowText
2022-10-18 23:53:53 +00:00
//font.pixelSize: parent.font.pixelSize
2022-10-18 23:16:54 +00:00
//opacity: editor.activeFocus ? 0 : 1
}
2022-10-18 21:24:58 +00:00
}
Button {
id: insertButton
text: "α "
anchors.right: parent . right
anchors.rightMargin: 5
anchors.verticalCenter: parent . verticalCenter
width: 20
height: width
onClicked: {
insertPopup . open ( )
insertPopup . focus = true
}
}
Popup . InsertCharacter {
id: insertPopup
x: Math . round ( ( parent . width - width ) / 2 )
y: Math . round ( ( parent . height - height ) / 2 )
onSelected: function ( c ) {
editor . insert ( editor . cursorPosition , c )
insertPopup . close ( )
2022-10-19 15:37:38 +00:00
focus = false
2022-10-18 21:24:58 +00:00
editor . focus = true
}
}
/ * !
\ qmlmethod var ExpressionEditor: : parse ( string newExpression )
Parses the \ c newExpression as an expression , checks for errors , shows them if any .
Returns the parsed expression if possible , null otherwise . .
* /
function parse ( newExpression ) {
let expr = null
try {
expr = new MathLib . Expression ( value . toString ( ) )
// 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
throw new Error ( qsTranslate ( 'error' , 'No object found with name %1.' ) . arg ( undefVars . join ( ', ' ) ) )
}
if ( expr . requiredObjects ( ) . includes ( control . self ) )
throw new Error ( qsTranslate ( 'error' , 'Object cannot be dependent on itself.' ) )
// TODO: Check for recursive dependencies.
} catch ( e ) {
// Error in expression
parsingErrorDialog . showDialog ( propertyName , newExpression , e . message )
}
return expr
}
2022-10-18 23:16:54 +00:00
/ * !
\ qmlmethod var ExpressionEditor: : colorize ( string expressionText )
Creates an HTML colorized string of the incomplete \ c expressionText .
Returns the colorized and escaped expression if possible , null otherwise . .
* /
function colorize ( text ) {
let tokenizer = new Parsing . Tokenizer ( new Parsing . Input ( text ) , true , false )
let parsedText = ""
let token
while ( ( token = tokenizer . next ( ) ) != null ) {
switch ( token . type ) {
case Parsing.TokenType.VARIABLE:
parsedText += ` < font color = "${colorScheme.VARIABLE}" > $ { token . value } < / f o n t > `
break ;
case Parsing.TokenType.CONSTANT:
parsedText += ` < font color = "${colorScheme.CONSTANT}" > $ { token . value } < / f o n t > `
break ;
case Parsing.TokenType.FUNCTION:
parsedText += ` < font color = "${Utils.escapeHTML(colorScheme.FUNCTION)}" > $ { token . value } < / f o n t > `
break ;
case Parsing.TokenType.OPERATOR:
parsedText += ` < font color = "${colorScheme.OPERATOR}" > $ { Utils . escapeHTML ( token . value ) } < / f o n t > `
break ;
case Parsing.TokenType.NUMBER:
parsedText += ` < font color = "${colorScheme.NUMBER}" > $ { Utils . escapeHTML ( token . value ) } < / f o n t > `
break ;
case Parsing.TokenType.STRING:
parsedText += ` < font color = "${colorScheme.STRING}" > $ { token . limitator } $ { Utils . escapeHTML ( token . value ) } $ { token . limitator } < / f o n t > `
break ;
case Parsing.TokenType.WHITESPACE:
case Parsing.TokenType.PUNCT:
default:
parsedText += Utils . escapeHTML ( token . value ) . replace ( / /g , ' ' )
break ;
}
}
return parsedText
}
2022-10-18 21:24:58 +00:00
}