/** * 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 []; } });