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