/**
* Project : AMDA-NG4
* Name : CalculatorUI.js
* @class amdaDesktop.ExplorerModule
* @extends Ext.util.Observable
* @brief Calculator Plugin used in SearchUI and ParameterUI
* @author elena
*/
/**
* @plugin calculator
* @extends Ext.util.Observable
* @ptype calculator
*/
var CalculatorData = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '(', ')', '[', ']', '+', '-', '*', '/', '^', '.', '>', '>=', '=', '!=', '<=', '<', '&', '|'];
Ext.define('amdaUI.PromptArgsWin', {
extend: 'Ext.window.Window',
fieldsForm: null,
applyCallback: null,
constructor: function(config) {
this.init(config);
this.callParent(arguments);
},
promptArgs : function(prompts, fn, scope) {
var me = this;
if (fn)
this.applyCallback = Ext.Function.bind(fn, scope);
else
this.applyCallback = null;
this.fieldsForm.removeAll();
prompts.each(function(promptObj, index) {
switch (promptObj.get('type')) {
case 'float':
me.addFloatField(promptObj.get('prompt'), index, promptObj.get('default'));
break;
case 'list':
me.addComboField(promptObj.get('prompt'), promptObj.get('subtype'), index, promptObj.get('default'));
break;
case 'boolean':
me.addBooleanField(promptObj.get('prompt'), index, promptObj.get('default'));
break;
case 'string':
me.addStringField(promptObj.get('prompt'), index, promptObj.get('default'));
break;
default:
console.log('Unknown arg type: ' + promptObj.get('type'));
break;
}
});
this.show();
},
addFloatField: function(label, index, defaultVal) {
var field = this.fieldsForm.add({
xtype: 'numberfield',
name: 'arg' + index,
fieldLabel: label,
decimalPrecision : 20,
allowBlank: false,
hideTrigger: true,
keyNavEnabled: false,
mouseWheelEnabled: false
});
if (defaultVal != "") {
defaultVal = parseFloat(defaultVal);
if (!isNaN(defaultVal)) {
field.setValue(defaultVal);
}
}
},
addComboField: function(label, listtype, index, defaultVal) {
var argslistStore = amdaUI.CalculatorUI.argslistStore;
if (!argslistStore) {
return;
}
var valuesStore = null;
argslistStore.each(function(arglist) {
if (arglist.get('id') == listtype) {
valuesStore = arglist.valuesStore;
}
});
if (!valuesStore) {
return;
}
var listData = [];
valuesStore.each(function (item) {
listData.push({'key': item.get('key'), 'value': item.get('value'), 'info': item.get('info')});
});
var comboStore = Ext.create('Ext.data.Store', {
fields: ['key', 'value', 'info'],
data : listData
});
var field = this.fieldsForm.add({
xtype: 'combo',
name: 'arg' + index,
fieldLabel: label,
store: comboStore,
queryMode: 'local',
displayField: 'value',
valueField: 'key',
editable: false,
listConfig: {
getInnerTpl: function() {
return '
{value}
';
}
}
});
if (defaultVal != "") {
field.setValue(defaultVal);
}
},
addBooleanField: function(label, index, defaultVal) {
var field = this.fieldsForm.add({
xtype: 'checkbox',
name: 'arg' + index,
boxLabel: label,
inputValue: '1',
uncheckedValue: '0'
});
if (defaultVal != "") {
field.setValue(defaultVal != '0');
}
},
addStringField: function(label, index, defaultVal) {
var field = this.fieldsForm.add({
xtype: 'textfield',
name: 'arg' + index,
fieldLabel: label
});
if (defaultVal != "") {
field.setValue(defaultVal);
}
},
init : function(config) {
this.fieldsForm = Ext.create('Ext.form.Panel', {
layout: {
type: 'vbox',
pack: 'start',
align: 'stretch',
defaultMargins: {top: 5, right: 5, bottom: 0, left: 5}
}
});
var myConf = {
layout: 'fit',
title: 'Function arguments',
modal: true,
closeAction: 'hide',
width: 300,
height: 200,
items: [
this.fieldsForm
],
buttons: [
{
text: 'Apply',
scope : this,
handler: function(bt,event) {
if (this.applyCallback)
this.applyCallback(this.fieldsForm.getValues());
this.close();
},
},
{
text: 'Cancel',
scope : this,
handler: function(bt,event) {
this.close();
}
}
]
};
Ext.apply(this, Ext.apply(arguments, myConf));
}
});
Ext.define('amdaUI.CalculatorUI', {
extend: 'Ext.util.Observable',
requires: [
'amdaModel.Constant',
'amdaModel.Function',
'amdaUI.PromptArgsWin'
],
alias: 'plugin.calculator',
statics: {
constantStore: null,
functionStore: null,
argslistStore: null
},
win: null,
promptArgsWin: null,
constructor: function (config) {
Ext.apply(this, config);
this.callParent(arguments);
},
init: function (cmp)
{
this.promptArgsWin = Ext.create('amdaUI.PromptArgsWin', {});
this.hostCmp = cmp;
this.hostCmp.on({
scope: this,
added: function () {
this.hostCmp.ownerCt.on({
render: this.onRender,
show: this.onShow,
hide: this.onHide,
scope: this});
}
});
},
onRender: function ()
{
this.win = new Ext.Window({
width: 350,
height: 170,
x: 380, y: 0,
baseCls: 'x-panel',
title: 'Tools For ' + this.context + ' Construction',
layout: 'fit',
closable: false,
collapsible: true,
constrain: true,
floating: true,
ghost: false,
renderTo: this.hostCmp.id,
items: this.getFormConfig(),
listeners: {
boxready: function (w)
{
if (w.y + w.height > myDesktopApp.desktop.el.getHeight())
w.el.setY((myDesktopApp.desktop.el.getHeight() - w.height > 0) ? myDesktopApp.desktop.el.getHeight() - w.height : 0);
}
},
getConstrainVector: function (constrainTo) {
var me = this;
if (me.constrain || me.constrainHeader) {
constrainTo = constrainTo || (me.floatParent && me.floatParent.getTargetEl()) || me.container || me.el.getScopeParent();
return (me.constrainHeader ? me.header.el : me.el).getConstrainVector(constrainTo);
}
}
});
//load constants store
if (!amdaUI.CalculatorUI.constantStore)
{
amdaUI.CalculatorUI.constantStore = Ext.create('Ext.data.Store', {model: 'amdaModel.Constant'});
amdaUI.CalculatorUI.constantStore.load({
scope: this,
callback: function (records, operation, success)
{
this.createAllConstantBtns();
}
});
} else
this.createAllConstantBtns();
//load functions store
if (!amdaUI.CalculatorUI.functionStore)
{
amdaUI.CalculatorUI.functionStore = Ext.create('Ext.data.Store', {model: 'amdaModel.Function'});
amdaUI.CalculatorUI.functionStore.load({
scope: this,
callback: function (records, operation, success)
{
this.createAllBtns();
}
});
} else
this.createAllBtns();
if (!amdaUI.CalculatorUI.argslistStore)
{
amdaUI.CalculatorUI.argslistStore = Ext.create('Ext.data.Store', {model: 'amdaModel.ArgList'});
amdaUI.CalculatorUI.argslistStore.load({
scope: this,
callback: function (records, operation, success)
{
}
});
}
},
onShow: function () {
this.win.show();
var parentWin = this.hostCmp.findParentByType('window');
if (parentWin.getId() === myDesktopApp.dynamicModules.param.id) {
this.win.setPosition(335, 10);
} else {
var posX = parentWin.getWidth() - this.win.getWidth() - 30;
var posY = parentWin.getHeight() - this.win.getHeight() - 110 - 30/*20*/;
this.win.setPosition(posX, posY);//(420,290);
}
},
onHide: function ()
{
this.win.hide();
},
getFormConfig: function () {
return {
xtype: 'tabpanel',
border: false, frame: true, plain: true,
enableTabScroll: true,
defaults: {frame: true, border: false, plain: true, autoScroll: true},
activeTab: 0,
items: [{
title: 'Calculator', layout: 'column',
defaults: {xtype: 'button', columnWidth: .11},
items: this.getItems('Calculator')
}, {
title: 'Constants', xtype: 'tabpanel', //iconCls: 'tabs',
enableTabScroll: true, tabPosition: 'bottom',
defaults: {frame: true, border: false, plain: true, layout: 'column', autoScroll: true},
activeTab: 0,
id: 'calc_tab_const_id'
}, {
title: 'Math', xtype: 'tabpanel', //iconCls: 'tabs',
enableTabScroll: true, tabPosition: 'bottom',
defaults: {frame: true, border: false, plain: true, layout: 'column', autoScroll: true},
activeTab: 0,
id: 'calc_tab_math_id'
},
{
title: 'Statistics', xtype: 'tabpanel', //iconCls: 'tabs',
enableTabScroll: true, tabPosition: 'bottom',
defaults: {frame: true, border: false, plain: true, layout: 'column', autoScroll: true},
activeTab: 0,
id: 'calc_tab_stat_id'
},
{
title: 'Space', xtype: 'tabpanel', //iconCls: 'tabs',
enableTabScroll: true, tabPosition: 'bottom',
defaults: {frame: true, border: false, plain: true, layout: 'column', autoScroll: true},
activeTab: 0,
id: 'calc_tab_func_id'
}
]
};
},
/**
* Prompt any argument to user before processing Formula
* @param sel selected text
* @param currentBtn calculator button pressed
* @param params array of parameters
*/
preProcessFormula: function (sel, currentBtn, params) {
if (currentBtn.initialConfig.args != 0 && currentBtn.initialConfig.prompts.count() > 0) {
// Prompt for user precision and process the result using a callback
/*Ext.Msg.prompt('Argument', currentBtn.initialConfig.prompts.getAt(0).get('prompt'), function (bt2, text) {
if (bt2 === 'ok') {
var afterParamsText = text + ")";
//TODO: more than one args and prompt
this.processFormula(sel, currentBtn, params, afterParamsText);
}
}, this);*/
this.promptArgsWin.promptArgs(currentBtn.initialConfig.prompts, function (values) {
var afterParamsText = '';
Ext.Object.each(values, function(key, value) {
afterParamsText += (afterParamsText == '' ? '' : ',');
afterParamsText += value;
});
afterParamsText += ')';
this.processFormula(sel, currentBtn, params, afterParamsText);
}, this);
} else {
if ( currentBtn.initialConfig.prompt_param != "" ) {
Ext.Msg.alert('Input Parameters', currentBtn.initialConfig.prompt_param);
}
this.processFormula(sel, currentBtn, params, ")");
}// endif prompt
},
processFormula: function (sel, currentBtn, params, afterParamsText) {
// calculation of the required number of parameters
var nbParams = currentBtn.initialConfig.args;
var remainingParams = nbParams - params.length;
var fnText = currentBtn.text.split('(');
var newConstruction = sel.beforeText + fnText[0] + "(";
// if there at least one parameter selected
if (params.length) {
for (var i = 0; i < params.length; i++) {
if (i > 0) {
newConstruction += ",";
}
newConstruction += params[i];
}
}
// we keep position
var afterParameterPos = newConstruction.length;
for (var i = 0; i < remainingParams-1; i++) {
newConstruction += ',';
}
if(afterParamsText != ")")
newConstruction += ','
newConstruction += afterParamsText;
var caretPos = newConstruction.length;
newConstruction += sel.afterText;
this.hostCmp.constructionField.setValue(newConstruction);
// If we haven't the right number of selected parameters
if (remainingParams > 0) {
var stringParamRequired = (remainingParams) + " parameter(s)";
Ext.Msg.alert('Caution', 'you\'ll have to add ' + stringParamRequired + ' to apply this function',
function () {
// set Caret Position at placement of required parameter in function
this.hostCmp.constructionField.setCaretPosition(afterParameterPos);
}, this
);
} else if (remainingParams < 0) {
var stringParamRequired = (-remainingParams) + " parameter(s)";
Ext.Msg.show({
title: 'Caution',
msg: 'you\'ll have to remove ' + stringParamRequired + ' to apply this function',
buttons: Ext.Msg.OK,
// animEl: 'elId',
icon: Ext.MessageBox.WARNING,
fn:function () {
// set Caret Position at placement of required parameter in function
this.hostCmp.constructionField.setCaretPosition(afterParameterPos);
},
scope: this
});
} else {
// set Caret Position after inserted Text
this.hostCmp.constructionField.setCaretPosition(caretPos);
}
},
/**
* This method construct an array of arguments into selected text
* @param selectedText the selection to parse
* @param parseIndex the index to start parsing
* @return the arguments array
*/
parseArgsInFormula: function (selectedText, parseIndex) {
if (!selectedText || selectedText == "") {
return [];
} else {
var params = [];
var startIndex = parseIndex;
var curIndex = parseIndex;
var openBrace = 0;
var sep = 0;
var closeBrace = 0;
// while there is a separator
while (sep != -1) {
openBrace = selectedText.indexOf("(", curIndex);
sep = selectedText.indexOf(",", curIndex);
closeBrace = selectedText.indexOf(")", curIndex);
// if there's an open bracket and no close bracket or inversely
if (openBrace != -1 && closeBrace == -1 || openBrace == -1 && closeBrace != -1) {
// invalid selection
return -1;
}
// if there's a separator and opening brackets into selection
if (sep != -1 && openBrace != -1) {
// if brace is before separator
if (openBrace < sep) {
curIndex = this.getEndBracket(selectedText, openBrace + 1);
if (curIndex === -1) {
return -1;
}
} else {// else separator is before brace
params.push(selectedText.substring(startIndex, sep));
startIndex = curIndex = sep + 1;
}
// if there's only separators into selection
} else if (sep != -1) {
params.push(selectedText.substring(startIndex, sep));
startIndex = curIndex = sep + 1;
}
}
params.push(selectedText.substring(startIndex, selectedText.length));
return params;
}
},
getEndBracket: function (string, indOpenBrace) {
// we search for the corresponding end brace (after open bracket)
var currentIndex = indOpenBrace;
var nextCloseBrace = 0;
var nextOpenBrace = 0;
var braceLevel = 1;
while (nextCloseBrace !== -1 && braceLevel !== 0) {
// get index of next opening bracket
nextOpenBrace = string.indexOf("(", currentIndex);
// get index of next closing bracket
nextCloseBrace = string.indexOf(")", currentIndex);
// if both exist
if (nextOpenBrace != -1 && nextCloseBrace != -1) {
// if opening bracket is before closing one
if (nextOpenBrace < nextCloseBrace) {
currentIndex = nextOpenBrace + 1;
braceLevel++;
} else { // if closing bracket is before opening one
currentIndex = nextCloseBrace + 1;
braceLevel--;
}
// if there's only a next opening bracket
} else if (nextOpenBrace != -1 && nextCloseBrace == -1) {
currentIndex = nextOpenBrace + 1;
braceLevel++;
// if there's only a next closing bracket
} else if (nextOpenBrace == -1 && nextCloseBrace != -1) {
currentIndex = nextCloseBrace + 1;
braceLevel--;
}
}
// if no level imbrication left return index after closing bracket of block else -1
return braceLevel == 0 ? currentIndex : -1;
},
getCalculatorBtn: function ()
{
var btns = [];
Ext.each(CalculatorData, function (c) {
btns.push({
text: c,
scope: this,
handler: function (b, e) {
// keep selection into construction field
var selection = this.hostCmp.constructionField.getSelection();
// the new value of construction field
var newConstruction = "";
// replacement of selection into construction field by text of clicked button
newConstruction = selection.beforeText + b.text;
var caretPos = newConstruction.length;
newConstruction += selection.afterText;
this.hostCmp.constructionField.setValue(newConstruction);
// set Caret Position after inserted Text
this.hostCmp.constructionField.setCaretPosition(caretPos);
}
})
},
this
);
return btns;
},
createAllBtns: function ()
{
// group math
amdaUI.CalculatorUI.functionStore.filter('group', 'math');
this.createFunctionBtns('MathFunctions', 'Simple Maths','#calc_tab_math_id');
this.createFunctionBtns('VectorFunctions', 'Vector Functions', '#calc_tab_math_id');
this.createFunctionBtns('Tetrahedron', 'Tetrahedron', '#calc_tab_math_id');
amdaUI.CalculatorUI.functionStore.clearFilter();
// group space
amdaUI.CalculatorUI.functionStore.filter('group', 'space');
this.createFunctionBtns('PhysicsFunctions', 'Derived', '#calc_tab_func_id');
this.createFunctionBtns('ModelFunctions', 'Models', '#calc_tab_func_id');
this.createFunctionBtns('AmdaFunctions', 'TimeShift', '#calc_tab_func_id');
this.createFunctionBtns('FramesFunctions', 'Frames', '#calc_tab_func_id');
this.createFunctionBtns('FilterFunctions', 'Filter', '#calc_tab_func_id');
amdaUI.CalculatorUI.functionStore.clearFilter();
// group stat
amdaUI.CalculatorUI.functionStore.filter('group', 'stat');
this.createFunctionBtns('TimeFunctions', 'Statistics', '#calc_tab_stat_id');
this.createFunctionBtns('FunctionsSliding', 'Statistics/Sliding','#calc_tab_stat_id');
amdaUI.CalculatorUI.functionStore.clearFilter();
},
createAllConstantBtns: function ()
{
this.createConstantBtns('Space', 'Planets Constants');
this.createConstantBtns('Physics', 'Physics Constants');
this.createConstantBtns('Units', 'Units Conversion');
},
createConstantBtns: function (item, tabTitle)
{
var constTab = this.win.query('#calc_tab_const_id');
if (constTab.length < 1)
return;
switch (item)
{
case 'Space' :
amdaUI.CalculatorUI.constantStore.filter('kind', 'space');
break;
case 'Physics' :
amdaUI.CalculatorUI.constantStore.filter('kind', 'physics');
break;
case 'Units' :
amdaUI.CalculatorUI.constantStore.filter('kind', 'units');
break;
}
var crtTab = constTab[0].add(
{
title: tabTitle,
defaults: {xtype: 'button', columnWidth: .20}
});
amdaUI.CalculatorUI.constantStore.each(function (c) {
crtTab.add(
{
text: c.get('name'),
tooltip: c.get('units') == '' ? c.get('info') + '
' + c.get('value') :
c.get('info') + '
' + c.get('value') + ' ' + c.get('units'),
scope: this,
handler: function (b, e) {
// keep selection into construction field
var selection = this.hostCmp.constructionField.getSelection();
// the new value of construction field
var newConstruction = "";
// replacement of selection into construction field by text of clicked button
newConstruction = selection.beforeText + '@' + b.text;
var caretPos = newConstruction.length;
newConstruction += selection.afterText;
this.hostCmp.constructionField.setValue(newConstruction);
// set Caret Position after inserted Text
this.hostCmp.constructionField.setCaretPosition(caretPos);
}
});
}, this);
//clear filter
amdaUI.CalculatorUI.constantStore.clearFilter();
},
createFunctionBtns: function (item, tabTitle, tabID)
{
var funcTab = this.win.query(tabID);
if (funcTab.length < 1)
return;
var width = .20;
switch (item)
{
case 'MathFunctions' :
amdaUI.CalculatorUI.functionStore.filter('kind', 'math');
break;
case 'AmdaFunctions' :
amdaUI.CalculatorUI.functionStore.filter('kind', 'amda');
width = .33;
break;
case 'TimeFunctions' :
amdaUI.CalculatorUI.functionStore.filter('kind', 'time');
width = .33;
break;
case 'FunctionsSliding' :
amdaUI.CalculatorUI.functionStore.filter('kind', 'sliding');
break;
case 'VectorFunctions' :
amdaUI.CalculatorUI.functionStore.filter('kind', 'vector');
break;
case 'Tetrahedron' :
amdaUI.CalculatorUI.functionStore.filter('kind', 'tetrahedron');
break;
case 'ModelFunctions' :
amdaUI.CalculatorUI.functionStore.filter('kind', 'model');
width = .45;
break;
case 'PhysicsFunctions' :
amdaUI.CalculatorUI.functionStore.filter('kind', 'physics');
width = .25;
break;
case 'FramesFunctions' :
amdaUI.CalculatorUI.functionStore.filter('kind', 'frames');
width = .45;
break;
case 'FilterFunctions' :
amdaUI.CalculatorUI.functionStore.filter('kind', 'filter');
width = .45;
break;
}
var crtTab = funcTab[0].add(
{
title: tabTitle,
defaults: {xtype: 'button', columnWidth: width}
});
amdaUI.CalculatorUI.functionStore.each(function (f) {
crtTab.add(
{
text: f.get('name'),
args: f.get('args'),
defaultArgs: f.get('default_args'),
prompts: f.prompts(),
prompt_param: f.get('prompt_param'),
tooltip: f.get('info_brief'),
scope: this,
handler: function (b, e) {
var selection = this.hostCmp.constructionField.getSelection();
var selectedText = selection && selection.text != "" ? Ext.util.Format.trim(selection.text) : null;
if (selectedText && selectedText !== "") {
selectedText.replace("[", "(");
selectedText.replace("{", "(");
selectedText.replace("}", ")");
selectedText.replace("]", ")");
}
var params = [];
var raw_args = b.initialConfig.defaultArgs;
if (raw_args && raw_args != "") {
params = b.initialConfig.defaultArgs.split(',');
}
// Formula Parsing for arguments
// var params = this.parseArgsInFormula(selectedText, 0);
// var params = selectedText ? selectedText.split(',') : [];
if (params === -1) {
Ext.Msg.alert("Invalid Selection", "Action aborted");
} else {
// calculation of the required number of parameters
var nbParams = b.initialConfig.args;
this.preProcessFormula(selection, b, params);
/**
if (params.length > nbParams)
{
// Show a dialog using config options:
Ext.Msg.show({
title: 'Caution',
msg: 'you have selected more than ' + nbParams + ' parameter(s) to apply this function
Only the first will be kept, others will be deleted',
buttons: Ext.Msg.OKCANCEL,
// animEl: 'elId',
icon: Ext.MessageBox.WARNING,
fn: function (bt1) {
if (bt1 === 'ok') {
this.preProcessFormula(selection, b, params);
}
},
scope: this
});
} else
{
this.preProcessFormula(selection, b, params);
}*/
}
}
});
},
this
);
//clear filter
amdaUI.CalculatorUI.functionStore.clearFilter();
},
getItems: function (item)
{
switch (item)
{
case 'Calculator':
return this.getCalculatorBtn();
default:
break;
}
return [];
}
});