/**
* 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', '(', ')', '[', ']', '+', '-', '*', '/', '^', '.', '>', '<', '&', '|'];
// var CalculatorData = ['1','2','3','4','5','6','7','8','9','0','(',')','[',']','+','-','*','/','^', '.','>','>=', '=', '<=', '<', '&', '|'];
Ext.define('amdaUI.CalculatorUI', {
extend: 'Ext.util.Observable',
requires: [
'amdaModel.Constant',
'amdaModel.Function'
],
alias: 'plugin.calculator',
statics: {
constantStore: null,
functionStore: null
},
win: null,
constructor: function (config) {
Ext.apply(this, config);
this.callParent(arguments);
},
init: function (cmp)
{
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.createAllFunctionBtns();
}
});
} else
this.createAllFunctionBtns();
},
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: 'Functions', 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.prompt != "") {
// Prompt for user precision and process the result using a callback
Ext.Msg.prompt('Argument', currentBtn.initialConfig.prompt, function (bt2, text) {
if (bt2 === 'ok') {
var afterParamsText = "," + text + ")";
//TODO: more than one args and prompt
this.processFormula(sel, currentBtn, params, afterParamsText);
}
}, this);
} else {
this.processFormula(sel, currentBtn, params, ")");
}// endif prompt
},
processFormula: function (sel, currentBtn, params, afterParamsText) {
// calculation of the required number of parameters
var nbParams = currentBtn.initialConfig.params;
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 < nbParams; i++) {
if (i > 0) {
newConstruction += ",";
}
newConstruction += params[i];
}
}
// we keep position
var afterParameterPos = newConstruction.length;
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 (params.length < nbParams) {
var stringParamRequired = currentBtn.initialConfig.params + " 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 {
// 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;
},
createAllFunctionBtns: function ()
{
this.createFunctionBtns('MathFunctions', 'Simple Maths');
this.createFunctionBtns('VectorFunctions', 'Vector Functions');
this.createFunctionBtns('TimeFunctions', 'Statistics');
this.createFunctionBtns('FunctionsSliding', 'Statistics/Sliding');
this.createFunctionBtns('PhysicsFunctions', 'Physics');
this.createFunctionBtns('AmdaFunctions', 'Special');
},
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)
{
var funcTab = this.win.query('#calc_tab_func_id');
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');
break;
case 'TimeFunctions' :
amdaUI.CalculatorUI.functionStore.filter('kind', 'time');
break;
case 'FunctionsSliding' :
amdaUI.CalculatorUI.functionStore.filter('kind', 'sliding');
break;
case 'VectorFunctions' :
amdaUI.CalculatorUI.functionStore.filter('kind', 'vector');
break;
case 'PhysicsFunctions' :
amdaUI.CalculatorUI.functionStore.filter('kind', 'physics');
width = .25;
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'),
params: f.get('params'),
prompt: f.get('prompt'),
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("]", ")");
}
// 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.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 [];
}
});