Skip to content
Snippets Groups Projects
GeomViewer.controller.js 28.88 KiB
sap.ui.define(['sap/ui/core/mvc/Controller',
               'sap/ui/core/Control',
               'sap/ui/model/json/JSONModel',
               'sap/m/Text',
               'sap/m/CheckBox',
               'sap/ui/layout/Splitter',
               'sap/ui/layout/SplitterLayoutData',
               "sap/ui/core/ResizeHandler"
],function(Controller, CoreControl, JSONModel, mText, mCheckBox, MCSplitter, SplitterLayoutData, ResizeHandler) {

   "use strict";
   
   CoreControl.extend("eve.ColorBox", { // call the new Control type "my.ColorBox" and let it inherit from sap.ui.core.Control

      // the control API:
      metadata : {
         properties : {           // setter and getter are created behind the scenes, incl. data binding and type validation
            "color" : {type: "sap.ui.core.CSSColor", defaultValue: "#fff"} // you can give a default value and more
         }
      },

      // the part creating the HTML:
      renderer : function(oRm, oControl) { // static function, so use the given "oControl" instance instead of "this" in the renderer function
         // if (!oControl.getVisible()) return;
         
         oRm.write("<div"); 
         oRm.writeControlData(oControl);  // writes the Control ID and enables event handling - important!
         oRm.addStyle("background-color", oControl.getColor());  // write the color property; UI5 has validated it to be a valid CSS color
         oRm.writeStyles();
         oRm.addClass("eveColorBox");      // add a CSS class for styles common to all control instances
         oRm.writeClasses();              // this call writes the above class plus enables support for Square.addStyleClass(...)
         oRm.write(">"); 
         oRm.write("</div>"); // no text content to render; close the tag
      }

/*      
      // an event handler:
      onclick : function(evt) {   // is called when the Control's area is clicked - no further event registration required
         sap.ui.require([
            'sap/ui/unified/ColorPickerPopover'
         ], function (ColorPickerPopover) {
            if (!this.oColorPickerPopover) {
               this.oColorPickerPopover = new ColorPickerPopover({
                  change: this.handleChange.bind(this)
               });
            }
            this.oColorPickerPopover.setColorString(this.getColor());
            this.oColorPickerPopover.openBy(this);
         }.bind(this));
      },

      handleChange: function (oEvent) {
         var newColor = oEvent.getParameter("colorString");
         this.setColor(newColor);
         // TODO: fire a "change" event, in case the application needs to react explicitly when the color has changed
         // but when the color is bound via data binding, it will be updated also without this event
      }
*/      
      
   });
   
   CoreControl.extend("eve.GeomDraw", { 

      metadata : {
         properties : {           // setter and getter are created behind the scenes, incl. data binding and type validation
            "color" : {type: "sap.ui.core.CSSColor", defaultValue: "#fff"} // you can give a default value and more
         }
      },

      // the part creating the HTML:
      renderer : function(oRm, oControl) { // static function, so use the given "oControl" instance instead of "this" in the renderer function
         oRm.write("<div"); 
         oRm.writeControlData(oControl);  // writes the Control ID and enables event handling - important!
         // oRm.addStyle("background-color", oControl.getColor());  // write the color property; UI5 has validated it to be a valid CSS color
         oRm.addStyle("width", "100%");  
         oRm.addStyle("height", "100%");  
         oRm.addStyle("overflow", "hidden");  
         oRm.writeStyles();
         oRm.writeClasses();              // this call writes the above class plus enables support for Square.addStyleClass(...)
         oRm.write(">"); 
         oRm.write("</div>"); // no text content to render; close the tag
      },

      onAfterRendering: function() {
         ResizeHandler.register(this, this.onResize.bind(this));
         this.geom_painter = null;
      },
      
      onResize: function() {
         if (this.resize_tmout) clearTimeout(this.resize_tmout);
         this.resize_tmout = setTimeout(this.onResizeTimeout.bind(this), 100); // minimal latency
      },
      
      onResizeTimeout: function() {
         delete this.resize_tmout;
         if (this.geo_painter)
            this.geo_painter.CheckResize();
      }
   });

   return Controller.extend("eve.GeomViewer", {
      onInit: function () {
         
         this.websocket = this.getView().getViewData().conn_handle;
         
         this.websocket.SetReceiver(this);
         this.websocket.Connect();
         
         this.data = { Nodes: null };
         
         this.model = new JSONModel(this.data);
         this.getView().setModel(this.model);
         
         // PART 2: instantiate Control and place it onto the page

         this.creator = new JSROOT.EVE.EveElements();
         
         this.creator.useIndexAsIs = (JSROOT.GetUrlOption('useindx') !== null);
         
         if (JSROOT.GetUrlOption('nobrowser') !== null) {
            // remove complete area - plan geometry drawing
            this.getView().byId("mainSplitter").removeAllContentAreas();
         } else {
         
            var t = this.getView().byId("treeTable");
         
            var vis_selected_handler = this.visibilitySelected.bind(this);
         
            t.addColumn(new sap.ui.table.Column({
               label: "Description",
               template: new sap.ui.layout.HorizontalLayout({
                  content: [
                     new mCheckBox({ enabled: true, visible: true, selected: "{node_visible}", select: vis_selected_handler }), 
                     new eve.ColorBox({color:"{color}", visible: "{color_visible}" }),
                     new mText({text:"{title}", wrapping: false })
                  ]
               })
             }));
         
            // catch re-rendering of the table to assign handlers 
            t.addEventDelegate({
               onAfterRendering: function() { this.assignRowHandlers(); }
            }, this);
         
         } 
         
         // geometry painter 
         this.geomControl = new eve.GeomDraw({color:"#f00"});
         this.getView().byId("mainSplitter").addContentArea(this.geomControl);
      },
      
      /** invoked when visibility checkbox clicked */
      visibilitySelected: function(oEvent) {
         var nodeid = this.getRowNodeId(oEvent.getSource());
         if (nodeid<0) {
            console.error('Fail to identify nodeid');
            return;
         }

         var msg = "SETVI" + (oEvent.getParameter("selected") ? "1:" : "0:") + JSON.stringify(nodeid);
         
         // send info message to client to change visibility  
         this.websocket.Send(msg);
      },
      
      assignRowHandlers: function() {
         var rows = this.getView().byId("treeTable").getRows();
         for (var k=0;k<rows.length;++k) {
            rows[k].$().hover(this.onRowHover.bind(this, rows[k], true), this.onRowHover.bind(this, rows[k], false));
         }
      },
      
      /** function called then mouse-hover event over the row is invoked
       * Used to highlight correspondent volume on geometry drawing 
       */
      onRowHover: function(row, is_enter) {
         // property of current entry, not used now
         var ctxt = row.getBindingContext(),  
             prop = ctxt ? ctxt.getProperty(ctxt.getPath()) : null; 

         // remember current element with hover stack
         this._hover_stack = (is_enter && prop && prop.end_node) ? this.getRowStack(row) : null; 
         
         if (!this.geo_painter) return;
         
         var found_mesh = this.geo_painter.HighlightMesh(null, 0x00ff00, null, undefined, this._hover_stack, true);
         
         // request given stack
         if (this._hover_stack && !found_mesh) 
            this.submitSearchQuery(this._hover_stack, true);
      },
      
      /** Return nodeid for the row */
      getRowNodeId: function(row) {
         var ctxt = row.getBindingContext();
         var ttt = ctxt ? ctxt.getProperty(ctxt.getPath()) : null;
         return ttt && (ttt.id!==undefined) ? ttt.id : -1;
      },
      
      /** try to produce stack out of row path */
      getRowStack: function(row) {
         var ctxt = row.getBindingContext();
         if (!ctxt) return null;
         
         var path = ctxt.getPath(), lastpos = 0, ids = [];
         
         while (lastpos>=0) {
            lastpos = path.indexOf("/chlds", lastpos+1);
            
            var ttt = ctxt.getProperty(path.substr(0,lastpos));
            
            if (!ttt || (ttt.id===undefined)) {
               // it is not an error - sometime TableTree does not have displayed items
               // console.error('Fail to extract node id for path ' + path.substr(0,lastpos) + ' full path ' + ctxt.getPath());
               return null;
            }
            
            ids.push(ttt.id);
         }
         
         return this.geo_clones.MakeStackByIds(ids);
      },
      
      /** Callback from geo painter when mesh object is highlighted. Use for update of TreeTable */
      HighlightMesh: function(active_mesh, color, geo_object, geo_index, geo_stack) {
         var rows = this.getView().byId("treeTable").getRows(), best_cmp = 0, best_indx = 0;
         
         for (var i=0;i<rows.length;++i) {
            rows[i].$().css("background-color", "");
            if (geo_stack) {
               var cmp = JSROOT.GEO.CompareStacks(geo_stack, this.getRowStack(rows[i]));
               if (cmp > best_cmp) { best_cmp = cmp; best_indx = i; }
            }
         }
         
         if (best_cmp > 0)
            rows[best_indx].$().css("background-color", best_cmp == geo_stack.length ? "yellow" : "lightgrey");
      },
      
      /** Extract shapes from binary data using appropriate draw message 
       * Draw message is vector of REveGeomVisisble objects, including info where shape is in raw data */
      extractRawShapes: function(draw_msg, msg, offset) {
         
         for (var cnt=0;cnt < draw_msg.length;++cnt) {
            var item = draw_msg[cnt], rd = item.ri;

            // entry may be provided without shape - it is ok
            if (!rd) continue;
            
            if (rd.server_shape) {
               item.server_shape = rd.server_shape;
               continue;
            }

            // reconstruct render data
            var off = offset + rd.rnr_offset;
            
            if (rd.vert_size) {
               rd.vtxBuff = new Float32Array(msg, off, rd.vert_size);
               off += rd.vert_size*4;
            }

            if (rd.norm_size) {
               rd.nrmBuff = new Float32Array(msg, off, rd.norm_size);
               off += rd.norm_size*4;
            }

            if (rd.index_size) {
               rd.idxBuff = new Uint32Array(msg, off, rd.index_size);
               off += rd.index_size*4;
            }
             
            // shape handle is similar to created in JSROOT.GeoPainter
            item.server_shape = rd.server_shape = { geom: this.creator.makeEveGeometry(rd), nfaces: (rd.index_size-2)/3, ready: true };
         }
      },
      
      /** Entry point for all data from server */
      OnWebsocketMsg: function(handle, msg, offset) {
         if (typeof msg != "string") {
            
            if (this.draw_msg) {
               // here we should decode render data
               this.extractRawShapes(this.draw_msg, msg, offset);
               // this is just start drawing, main work will be done asynchronous
               this.startDrawing(this.draw_msg);
               delete this.draw_msg;
            } else if (this.found_msg) {
               this.extractRawShapes(this.found_msg, msg, offset);
               this.processSearchReply("BIN");
               delete this.found_msg;
            } else if (this.append_msg) {
               this.extractRawShapes(this.append_msg, msg, offset);
               this.appendNodes(this.append_msg);
               delete this.append_msg;
            } else {
               console.error('not process binary data len=' + (msg ? msg.byteLength : 0))
            }
            
            return;
         }
         
         var mhdr = msg.substr(0,6);
         msg = msg.substr(6);

         // console.log(mhdr, msg.length, msg.substr(0,70), "...");

         switch (mhdr) {
         case "DESCR:": 
            this.parseDescription(msg); 
            break;
         case "MODIF:": 
            this.modifyDescription(msg); 
            break;
         case "GDRAW:":
            this.last_draw_msg = this.draw_msg = JSROOT.parse(msg); // use JSROOT.parse while refs are used
            this.setNodesDrawProperties(this.draw_msg);
            break;
         case "APPND:":
            this.append_msg = JSROOT.parse(msg); // use JSROOT.parse while refs are used
            this.setNodesDrawProperties(this.append_msg); // set properties
            break;
         case "FOUND:": 
            this.processSearchReply(msg, false);
            break;
         case "SHAPE:":
            this.processSearchReply(msg, true);
            break
         default:
            console.error('Non recognized msg ' + mhdr + ' len=' + msg.length);
         } 
      },
      
      /** Format REveGeomNode data to be able use it in list of clones */
      formatNodeElement: function(elem) {
         elem.kind = 2; // special element for geom viewer, used in TGeoPainter
         var m = elem.matr;
         delete elem.matr;
         if (!m || !m.length) return;
         
         if (m.length == 16) {
            elem.matrix = m;
         } else {
            var nm = elem.matrix = new Array(16);
            for (var k=0;k<16;++k) nm[k] = 0;
            nm[0] = nm[5] = nm[10] = nm[15] = 1;
         
            if (m.length == 3) {
               // translation martix
               nm[12] = m[0]; nm[13] = m[1]; nm[14] = m[2];
            } else if (m.length == 4) {
               // scale matrix
               nm[0] = m[0]; nm[5] = m[1]; nm[10] = m[2]; nm[15] = m[3];
            } else if (m.length == 9) {
               // rotation matrix
               nm[0] = m[0]; nm[4] = m[1]; nm[8]  = m[2]; 
               nm[1] = m[3]; nm[5] = m[4]; nm[9]  = m[5]; 
               nm[2] = m[6]; nm[6] = m[7]; nm[10] = m[8]; 
            } else {
               console.error('wrong number of elements in the matrix ' + m.length);
            }
         }
      },
      
      /** Parse and format base geometry description, initialize hierarchy browser */
      parseDescription: function(msg) {
         var descr = JSON.parse(msg);
         
         var nodes = descr.fDesc;

         // we need to calculate matrixes here
         for (var cnt = 0; cnt < nodes.length; ++cnt) 
            this.formatNodeElement(nodes[cnt]);
         
         var clones = new JSROOT.GEO.ClonedNodes(null, nodes);
         clones.name_prefix = clones.GetNodeName(0); 

         this.assignClones(clones, descr.fDrawOptions);

         this.buildTree();
      },
      
      /** When single node element is modified from server side */ 
      modifyDescription: function(msg) {
         var newitem = JSON.parse(msg);
         
         if (!newitem || !this.geo_clones) return;
         
         this.formatNodeElement(newitem);
         
         var item = this.geo_clones.nodes[newitem.id];
         
         if (!item)
            return console.error('Fail to find item ' + newitem.id);
         
         item.vis = newitem.vis;
         item.matrix = newitem.matrix;

         var dnode = this.originalCache ? this.originalCache[newitem.id] : null;

         if (dnode) {
            // here we can modify only node which was changed
            
            dnode.title = newitem.name;
            dnode.color_visible = false;
            dnode.node_visible = newitem.vis != 0;
            
            this.model.refresh();
            
         } else {
            // rebuild complete tree for TreeBrowser
            this.buildTree();
            // set all available properties
            this.setNodesDrawProperties(this.last_draw_msg);
         }
         
         if (!item.vis && this.geo_painter)
            this.geo_painter.RemoveDrawnNode(item.id);
      },
      
      // here try to append only given stack to the tree
      // used to build partial tree with visible objects
      appendStackToTree: function(tnodes, stack, color) {
         var prnt = null, node = null;
         for (var i=-1;i<stack.length;++i) {
            var indx = (i<0) ? 0 : node.chlds[stack[i]];
            node = this.geo_clones.nodes[indx];
            var tnode = tnodes[indx];
            if (!tnode)
               tnodes[indx] = tnode = { title: node.name, id: indx, color_visible: false, node_visible: true };
            
            if (prnt) {
               if (!prnt.chlds) prnt.chlds = [];
               if (prnt.chlds.indexOf(tnode) < 0)
                  prnt.chlds.push(tnode);
            }
            prnt = tnode;
         }
         
         prnt.end_node = true;
         prnt.color = color ? "rgb(" + color + ")" : "";
         prnt.color_visible = prnt.color.length > 0;
      },

      buildTreeNode: function(cache, indx) {
         var tnode = cache[indx];
         if (tnode) return tnode;

         var node = this.geo_clones.nodes[indx];
         
         cache[indx] = tnode = { title: node.name, id: indx, color_visible: false, node_visible: node.vis != 0 };
         
         if (node.chlds && (node.chlds.length>0)) {
            tnode.chlds = [];
            for (var k=0;k<node.chlds.length;++k) 
               tnode.chlds.push(this.buildTreeNode(cache, node.chlds[k]));
         } else {
            tnode.end_node = true;
         }
         
         return tnode;
      },
      
      /** Build complete tree of all existing nodes. 
       * Produced structure can be very large, therefore later one should move this functionality to the server */
      buildTree: function() {
         if (!this.geo_clones) return;
         
         this.originalCache = [];
         
         this.data.Nodes = [ this.buildTreeNode(this.originalCache, 0) ];
         
         this.originalNodes = this.data.Nodes; 
         
         this.model.refresh();
      },
      
      /** Set draw properties of nodes which are displayed, currently only draw colors */
      setNodesDrawProperties: function(draw_msg) {
         if (!this.data.Nodes) return;
         for (var k=0;k<draw_msg.length;++k) {
            var item = draw_msg[k];
            var dnode = this.data.Nodes[0];
            for (var n=0;n<item.stack.length;++n)
               dnode = dnode.chlds[item.stack[n]];
            dnode.color = item.color ? "rgb(" + item.color + ")" : "";
            dnode.color_visible = dnode.color.length > 0;
         }
         this.model.refresh(); // refresh browser
      },
      
      /** search main drawn nodes for matches */ 
      findMatchesFromDraw: function(func) {
         var matches = [];
         
         if (this.last_draw_msg) 
            for (var k=0;k<this.last_draw_msg.length;++k) {
               var item = this.last_draw_msg[k];
               var res = this.geo_clones.ResolveStack(item.stack);
               if (func(res.node)) 
                  matches.push({ stack: item.stack, color: item.color });
            }
         
         return matches;
      },
      
      /** try to show selected nodes. With server may be provided shapes */ 
      showFoundNodes: function(matches, append_more, with_binaries) {
         
         if (typeof matches == "string") {
            this.byId("treeTable").collapseAll();
            this.data.Nodes = [ { title: matches } ];
            this.model.refresh();
            if (this.geo_painter) {
               if (append_more) this.geo_painter.appendMoreNodes(null);
               this.geo_painter.changeGlobalTransparency();
            }
            return;
         }
         
         // fully reset search selection
         if ((matches === null) || (matches === undefined)) {
            this.byId("treeTable").collapseAll();
            this.data.Nodes = this.originalNodes || null;
            this.model.refresh();
            this.byId("treeTable").expandToLevel(1);
          
            if (this.geo_painter) {
               if (append_more) this.geo_painter.appendMoreNodes(matches);
               this.geo_painter.changeGlobalTransparency();
            }
            return;
         }
         
         if (!matches || (matches.length == 0)) {
            this.data.Nodes = null;
            this.model.refresh();
         } else {
            var nodes = [];
            for (var k=0;k<matches.length;++k) 
               this.appendStackToTree(nodes, matches[k].stack, matches[k].color);
            this.data.Nodes = [ nodes[0] ];
            this.model.refresh();
            if (matches.length < 100)
               this.byId("treeTable").expandToLevel(99);
         }
         
         if (this.geo_painter) {
            if (append_more && with_binaries) this.geo_painter.appendMoreNodes(matches);
            
            if ((matches.length>0) && (matches.length<100)) { 
               var dflt = Math.max(this.geo_painter.options.transparency, 0.98);
               this.geo_painter.changeGlobalTransparency(function(node) {
                  if (node.stack) 
                     for (var n=0;n<matches.length;++n)
                        if (JSROOT.GEO.IsSameStack(node.stack, matches[n].stack))
                           return 0;
                  return dflt;
               });
            } else {
               this.geo_painter.changeGlobalTransparency();
            }
         }
      },
      
      appendNodes: function(nodes) {
         if (this.geo_painter) this.geo_painter.prepareObjectDraw(nodes, "__geom_viewer_append__");
      },
      
      showMoreNodes: function(matches) {
         if (!this.geo_painter) return; 
         this.geo_painter.appendMoreNodes(matches);
         if (this._hover_stack)
            this.geo_painter.HighlightMesh(null, 0x00ff00, null, undefined, this._hover_stack, true);
      },

      OnWebsocketClosed: function() {
         // when connection closed, close panel as well
         console.log('CLOSE WINDOW WHEN CONNECTION CLOSED');
         
         if (window) window.close();
      },
      
      onAfterRendering: function() {
         if (this.geo_clones) this.createGeoPainter();
      },
      
      createGeoPainter: function() {
         this.geo_painter = JSROOT.Painter.CreateGeoPainter(this.geomControl.getDomRef(), null, this.draw_options);
         this.geomControl.geo_painter = this.geo_painter; 

         this.geo_painter.AddHighlightHandler(this);
         this.geo_painter.ActivateInBrowser = this.activateInTreeTable.bind(this);            

         if (this.geo_clones) 
            this.geo_painter.assignClones(this.geo_clones);
         
         if (this.geo_visisble) {
            this.geo_painter.prepareObjectDraw(this.geo_visisble, "__geom_viewer_selection__");
            delete this.geo_visisble;
         }
      },
      
      assignClones: function(clones, drawopt) {
         this.geo_clones = clones;
         this.draw_options = drawopt;

         if (this.geo_painter) {
            // this.geo_painter.options = this.geo_painter.decodeOptions(drawopt);
            this.geo_painter.assignClones(this.geo_clones);
         } else {
            this.createGeoPainter();
         } 
      },
      
      startDrawing: function(visible) {
         if (this.geo_painter)
            this.geo_painter.prepareObjectDraw(visible, "__geom_viewer_selection__");
         else
            this.geo_visisble = visible;
      },
      
      /** method called from geom painter when specific node need to be activated in the browser 
       * Due to complex indexing in TreeTable it is not trivial to select special node */
      activateInTreeTable: function(itemnames, force) {
         if (!force || !itemnames) return; 
            
         var stack = this.geo_clones.FindStackByName(itemnames[0]);
         if (!stack) return;
         
         function test_match(st) {
            if (!st || (st.length > stack.length)) return -1;
            for (var k=0;k<stack.length;++k) {
               if (k>=st.length) return k;
               if (stack[k] !== st[k]) return -1; // either stack matches completely or not at all
            }
            return stack.length; 
         }
         
         // now start searching items
         
         var tt = this.getView().byId("treeTable"),
             rows = tt.getRows(), best_match = -1, best_row = 0;
         
         for (var i=0;i<rows.length;++i) {
            
            var rstack = this.getRowStack(rows[i]);
            var match = test_match(rstack);
            
            if (match > best_match) {
               best_match = match;
               best_row = rows[i].getIndex();
            }
         }

         // start from very beginning
         if (best_match < 0) {
            tt.collapseAll();
            best_match = 0; 
            best_row = 0;
         }
         
         if (best_match < stack.length) {
            var ii = best_row;
            // item should remain as is, but all childs can be below limit
            tt.expand(ii);
            
            while (best_match < stack.length) {
               ii += stack[best_match++] + 1; // stack is index in child array, can use it here
               if (ii > tt.getFirstVisibleRow() + tt.getVisibleRowCount()) {
                  tt.setFirstVisibleRow(Math.max(0, ii - Math.round(tt.getVisibleRowCount()/2)));
               }
               tt.expand(ii);
            }
         }
      },

      /** called when new portion of data received from server */
      processSearchReply: function(msg, is_shape) {
         // not waiting search - ignore any replies
         if (!this.waiting_search) return;
         
         var lst = [], has_binaries = false;
         
         if (msg == "BIN") { 
            has_binaries = true; 
            lst = this.found_msg; is_shape = this.found_shape; 
            delete this.found_msg; 
            delete this.found_shape;
         } else if (msg == "NO") { 
            lst = "Not found"; 
         } else if (msg.substr(0,7) == "TOOMANY") { 
            lst = "Too many " + msg.substr(8); 
         } else {
            lst = JSROOT.parse(msg);
            for (var k=0;k<lst.length;++k)
               if (lst[k].ri) { // wait for binary render data
                  this.found_msg = lst;
                  this.found_shape = is_shape;
                  return;
               }
         }
         
         if (is_shape) {
            this.showMoreNodes(lst);
         } else {
            this.showFoundNodes(lst, true, has_binaries);
         }
         
         if (this.next_search) {
            this.websocket.Send(this.next_search);
            delete this.next_search;
         } else { 
            this.waiting_search = false;
         }
      },
      
      /** Submit node search query to server, ignore in offline case */ 
      submitSearchQuery: function(query, from_handler) {
         
         // ignore query in file description mode 
         if (this.websocket.kind == "file") return;
         
         if (!from_handler) {
            // do not submit immediately, but after very short timeout
            // if user types very fast - only last selection will be shown
            if (this.search_handler) clearTimeout(this.search_handler);
            this.search_handler = setTimeout(this.submitSearchQuery.bind(this, query, true), 1000);
            return;
         }
         
         delete this.search_handler;
         
         if (!query) {
            // if empty query specified - restore geometry drawing and ignore any possible reply from server
            this.waiting_search = false;
            delete this.next_search;
            this.showFoundNodes(null);
            return;
         }
         
         if (typeof query == "string")
            query = "SEARCH:" + query;
         else
            query = "GET:" + JSON.stringify(query);
         
         if (this.waiting_search) {
            // do not submit next search query when prvious not yet proceed
            this.next_search = query;
            return;
         }
         
         this.websocket.Send(query);
         this.waiting_search = true;
      },
      
      /** when new query entered in the seach field */
      onSearch : function(oEvt) {
         var query = oEvt.getSource().getValue();
         if (this.websocket.kind != "file") {
            this.submitSearchQuery(query);
         } else if (query) {
            this.showFoundNodes(
               this.findMatchesFromDraw(function(node) {
                  return node.name.indexOf(query)==0;
               })
            );
         } else {
            this.showFoundNodes(null);
         }
      },

      /** Reload geometry description and base drawing, normally not required */
      onRealoadPress: function (oEvent) {
         this.websocket.Send("RELOAD");
      },
      
      /** Quit ROOT session */
      onQuitRootPress: function(oEvent) {
         this.websocket.Send("QUIT_ROOT");
      }

   });
   
});