Remoto - VFS: node.js Source File
Remoto - VFS
node.js
Go to the documentation of this file.
1 
2 define( [
3  'remoto!stdlib:js/panes/panes/nodeCanvas/nodeDefines.js',
4  'remoto!stdlib:js/include/objectRegistry.js',
5  'remoto!stdlib:js/widgets/widgetFactory.js',
6  'remoto!stdlib:js/widgets/widgets/groupWidget.js',
7  'remoto!stdlib:js/widgets/widgets/numberWidget.js',
8  'remoto!stdlib:js/panes/panes/nodeCanvas/plug.js',
9  'remoto!stdlib:js/panes/panes/nodeCanvas/cable.js',
10  'remoto!stdlib:js/include/preferences.js',
11  'remoto!stdlib:js/include/utils.js',
12  'remoto!stdlib:js/include/color.js',
13  'remoto!stdlib:js/panes/panes/nodeCanvas/node.css',
14  ],
16  {
17  'use strict';
18 
19  var svgns = "http://www.w3.org/2000/svg";
20 
21  var snapDistance = defines.snapDistance;
22 
47  //node.base = "node.rnd";
48  node.icon = "data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"-7 -7 14 14\"><circle vector-effect=\"non-scaling-stroke\" class=\"nodeBackground\" fill=\"%23FF6666\" stroke=\"%232F2\" cx=\"0\" cy=\"0\" r=\"6\"></circle></svg>";
49 
50  //_path path on the VFS
51  //_cnv canvas to attach to
52  //_attrs attributes to create, from definitions
53  //_in inputs to create
54  //_out outputs to create
55  //_base base definition to extend from
56  //_icon the node's icon
57  function node( _path, _cnv, _attrs, _in, _out, _base, _icon )
58  {
59  //console.log("New NODE!");
60 
61  var THIS = this;
62 
63  var base = {
64  uuid: {
65  index: 0,
66  type: "text",
67  options: {
68  "hidden": true
69  },
70  value: null
71  },
72  node: { index: 1,
73  label: "Node",
74  tip: "Node position and color",
75  type: "group",
76  value: { x: { index: 0,
77  label: "x",
78  type: "float",
79  value: 0,
80  change: this.diffPosition.bind(this),
81  },
82  y: { index: 1,
83  label: "y",
84  type: "float",
85  value: 0,
86  change: this.diffPosition.bind(this),
87  },
88  color: { index: 2,
89  label: "color",
90  type: "color", //a default value that, if not overwritten, will be obvious
91  value: "FF0000",
92  change: ( function(variable,value,widget) { THIS.color = value; } ).bind(this),
93  },
94  }
95  },
96  name: { index: 2,
97  label: "Name",
98  tip: "Node name",
99  type: "text",
100  value: "NODE",
101  change: ( function(variable,value,widget) { THIS.name = value; } ).bind(this),
102  },
103  progress: { index: 3,
104  label: "Progress",
105  tip: "Node progress",
106  type: "float",
107  value: 0,
108  default: 0,
109  change: ( function(variable,value,widget) { THIS.progress = value; } ).bind(this),
110  },
111  };
112  this._attributes = {};
113 
114  this._startPos = {};
115  this._selected = false;
116  this._moving = false;
117  this._boundingBox = {x:0,y:0,width:0,height:0};
118 
119  this._diffing = false;
120  this._firstLoad = true;
121  this._diffTimer = null;
122  this._diff = {};
123 
124  this._canvas = _cnv;
125  this._root = null;
126  this._background = null;
127  this._gradient = null;
128  this._label = null;
129  this._title = null;
130  this._progress = null;
131  this._type = "";
132 
133  this._hideInputLabels = false;
134  this._hideOutputLabels = false;
135 
136  if (!arguments.length) return; //so we can have base class functionality to inherit from
137 
138  var n_id = null;
139  if (!_path)
140  {
141  n_id = utils.uuid();
142  _path = this._canvas._path+"/"+n_id+".rnd";
143  //console.log("node new path",n_id,n_id_p,this._canvas);
144  }
145 
146  this._base = _base || "node.rnd";
147  this._path = utils.cleanPath(_path);
148 
149  this._inputs = {};
150  this._inputsSpec = utils.copyObject(_in);
151  for( var ii in this._inputsSpec ) { delete this._inputsSpec[ii].value; } //we just want the spec, not the values
152 
153  this._outputs = {};
154  this._outputsSpec = utils.copyObject(_out);
155  for( var dd in this._outputsSpec ) { delete this._outputsSpec[dd].value; } //we just want the spec, not the values
156 
157  this._icon = _icon || node.icon;
158  this._layer = this._layer || "nodes";
159 
160  this._nodeClass = this._nodeClass || node; //needs to be after subclass ctor abort, to allow a subclass to define it first
161 
162  this.createAttributes( utils.copyObject( _attrs, base ) ); //let the incoming attributes overwrite the base values... this is a last layer of protection from bad incoming data
163  this.createSVG();
164  this.createInputs(_in || {});
165  this.createOutputs(_out || {});
166  this.position( this.x, this.y );
167  setTimeout( this.resize.bind(this), 0 ); //we don't know the size until it's been added and drawn once.
168 
169  if (n_id)
170  this._attributes.uuid.value = n_id;
171 
172  this._root.trigger("nodecreated", { node:this } );
173  this._root.trigger("undoevent", { type:"Create Node: "+this.name+" "+this._path, undo:this.detach.bind(this), redo:this.attach.bind(this), close:true } );
174 
175  objectRegistry.registerObject( this._path, this, true );
176  }
177 
178  node.prototype = {
179 
188  createSVG: function()
189  {
190  if (this._root) return;
191 
192  //var p = this._canvas._root;
193  var p = this._canvas._layers[this._layer];
194  var g = this._root = $( document.createElementNS(svgns,"g") ).appendTo(p).attr( { class:"node" } );
195 
196  g.hide();
197 
198  g.attr( {
199  "vector-effect":"non-scaling-stroke",
200  //filter:"url(#dropShadow)",
201  } );
202 
203  var b = this._background = $( document.createElementNS(svgns,"rect") ).appendTo(g);
204 
205  b.attr( {
206  rx:3,
207  ry:3,
208  fill:"#"+this.color,
209  "vector-effect":"non-scaling-stroke",
210  //filter:"url(#dropShadow)",
211  "class":"nodeBackground",
212  //"mask":"url(#nodeGradientMask)",
213  } );
214 
215  var prog = this._progress = $( document.createElementNS(svgns,"rect") ).appendTo(g);
216 
217  prog.attr( {
218  rx:3,
219  ry:3,
220  fill:"#00FF00",
221  // fill:"var(--Accent1)",
222  stroke: "none",
223  } );
224 
225  var grad = this._gradient = $( document.createElementNS(svgns,"rect") ).appendTo(g);
226 
227  grad.attr( {
228  rx:3,
229  ry:3,
230  fill:"url(#"+this._canvas._id+"_nodeGradient)",
231  "vector-effect":"non-scaling-stroke",
232  //filter:"url(#dropShadow)",
233  "class":"nodeBackgroundGradient",
234  } );
235 
236  var l = this._label = $( document.createElementNS(svgns,"text") ).appendTo(g);
237 
238  l.text( this.name );
239 
240  l.attr( {
241  "class":"nodeLabel",
242  "text-anchor":"middle",
243  x:0,
244  y:5,
245  } );
246 
247  var t = this._title = $( document.createElementNS(svgns,"title") ).appendTo(g);
248  t.text( this.type );
249 
250  this.color = this.color;
251  this.progress = this.progress;
252 
253  g.data("node",this);
254 
255  this.initHandlers();
256  },
257 
266  initHandlers: function()
267  {
268  var b = this._background;
269  var r = this._root;
270  var THIS = this;
271 
272  b.bind("mouseover", function(evt)
273  {
274  if (!THIS._moving)
275  THIS._root.trigger( "plugcandidate", {node:THIS} );
276  return false;
277  } );
278 
279  b.bind("mouseout", function(evt)
280  {
281  if (!THIS._moving)
282  THIS._root.trigger( "plugcandidate", {plug:null} );
283  return false;
284  } );
285 
286  b.bind("mouseup", function(evt)
287  {
288  THIS._root.trigger( "plugaccept" );
289  //return false; //DON'T stop propagation here... need to get that event to the canvas
290  } );
291 
292  r.bind("plugsplit", function(evt,data)
293  {
294  var p = data.plug;
295  THIS.splitPlug(p);
296  return false;
297  } );
298 
299  r.bind("plugunsplit", function(evt,data)
300  {
301  var p = data.plug;
302  THIS.unsplitPlug(p);
303  return false;
304  } );
305 
306  r.bind("plugconnect plugdisconnect", function(evt,data)
307  {
308  THIS.resize();
309  return false;
310  } );
311 
312  r.bind("plugdestroy", function(evt,data)
313  {
314  var p = data.plug;
315  var a = p._isInput ? THIS._inputs : THIS._outputs;
316  var k = utils.keyFromValue(a,p);
317 
318  if (k) delete a[k];
319 
320  return false;
321  } );
322  },
323 
333  detach: function(createUndo)
334  {
335  //console.trace("detach node!");
336 
337  var bridges = this.bridgeOnDestroy();
338 
339  for (var i in this._inputs)
340  this._inputs[i].detach();
341 
342  for (var o in this._outputs)
343  this._outputs[o].detach();
344 
345  for (var i=0;i<bridges.length;i++)
346  {
347  var b = bridges[i];
348  var c = new cable( this._canvas, this._canvas.getPlug(b.source,plug.OUTPUT,true), this._canvas.getPlug(b.dest,plug.INPUT,true) );
349  c.topStack();
350  }
351 
352  this.selected = false;
353 
354  this._root.detach();
355 
356  this._canvas._root.trigger("nodedetached", {node:this} );
357 
358  if (createUndo)
359  this._canvas._root.trigger("undoevent", { type:"Node Delete: "+this.name+" "+this._path, undo:this.attach.bind(this), redo:this.detach.bind(this) } );
360 
361  objectRegistry.unregisterObject( this._path, this );
362  },
363 
372  attach: function()
373  {
374  var l = this._canvas._layers[this._layer];
375  this._root.appendTo( l );
376 
377  //this._fromLoader = false; //signal to the canvas that a diff should be sent
378  this._canvas._root.trigger("nodeattached", {node:this} );
379  },
380 
389  destroy: function()
390  {
391  //console.log("destroying \""+this.name+"\"...");
392 
393  var bridges = this.bridgeOnDestroy();
394 
395  for (var i in this._inputs)
396  this._inputs[i].destroy();
397 
398  for (var o in this._outputs)
399  this._outputs[o].destroy();
400 
401  for (var i=0;i<bridges.length;i++)
402  {
403  var b = bridges[i];
404  var c = new cable( this._canvas, b.source, b.dest );
405  c.topStack();
406  }
407 
408  this._inputs = null;
409  this._outputs = null;
410 
411  this._root.remove();
412  this._root = null;
413 
414  for (var a in this._attributes)
415  this._attributes[a].destroy();
416  this._attributes = null;
417 
418  this._canvas._root.trigger("nodedestroyed", {node:this} );
419  this._canvas = null;
420 
421  objectRegistry.unregisterObject( this._path, this );
422  },
423 
433  copy: function()
434  {
435  var c = [];
436 
437  var THIS = this;
438  var bv = this.baseValues();
439 
440  var ss = this._canvas._objectLoader.fetchDefinition(
441  this._base,
442  bv,
443  function(spec) {
444  //console.log("paste callback from fetch definition",arguments);
445  return spec.attributes;
446  }
447  );
448 
449  c.push( {
450  step:1,
451  action:"createnode",
452  id:this.id,
453  //id: id,
454  paste:this._nodeClass.bind(null,null,this._canvas,ss,this._inputsSpec,this._outputsSpec,this._base,this._icon),
455  } );
456 
457  var a = [ this._inputs, this._outputs ];
458  a.forEach( function(b)
459  {
460  for (var p in b)
461  if (b[p].connected)
462  c.push( { step:2, action:"createcable", source:b[p]._cable._source.path, dest:b[p]._cable._dest.path } );
463  } );
464 
465  return c;
466  },
467 
477  createInputs: function(_in)
478  {
479  if (!_in) return;
480 
481  //console.log("INS:",_in);
482 
483  var p;
484  var c = utils.keyCountFiltered(_in, function(e) { return _in[e].type ? (_in[e].type.indexOf("<")==-1) : false; } );
485 
486  for (var i in _in)
487  { if (i.indexOf(".") != -1)
488  throw "Can't create plugs with '.' in name. '"+i+"' provided.";
489 
490  p = this._inputs[i] = new plug( this, i, plug.INPUT, _in[i].type );
491  if (c==1)
492  { p.hideLabel();
493  this._hideInputLabels = true;
494  }
495 
496  if (_in[i].value)
497  {
498  var iap;
499  var ia = _in[i].value;
500  ia = utils.isArray( ia ) ? ia : [ ia ];
501 
502  for (var j=0; j<ia.length; j++)
503  {
504  iap = ia[j];
505 
506  //var sp = this._canvas.getPlug( _in[i].value, plug.OUTPUT, true );
507  var sp = this._canvas.getPlug( iap, plug.OUTPUT, true );
508 
509  if (sp)
510  {
511  this._canvas.globalDiffing = true;
512  var c = new cable( this._canvas, sp, p );
513  c.topStack();
514  this._canvas.globalDiffing = false;
515  }
516  //else
517  // console.warn( "Bad plug: "+iap );
518 
519  if (p._splittable)
520  p = this.getPlug( i, plug.INPUT, true );
521  }
522  }
523  }
524  },
525 
535  createOutputs: function(_out)
536  {
537  if (!_out) return;
538 
539  var p;
540  var c = utils.keyCount(_out);
541 
542  for (var o in _out)
543  { if (o.indexOf(".") != -1)
544  throw "Can't create plugs with '.' in name. '"+o+"' provided.";
545 
546  p = this._outputs[o] = new plug( this, o, plug.OUTPUT, _out[o].type );
547  if (c==1)
548  { p.hideLabel();
549  this._hideOutputLabels = true;
550  }
551 
552  if (_out[o].value)
553  {
554  var oap;
555  var oa = _out[o].value;
556  oa = utils.isArray( oa ) ? oa : [ oa ];
557 
558  for (var i=0; i<oa.length; i++)
559  {
560  oap = oa[i];
561 
562  //var dp = this._canvas.getPlug( _out[o].value, plug.INPUT, true );
563  var dp = this._canvas.getPlug( oap, plug.INPUT, true );
564 
565  if (dp)
566  {
567  this._canvas.globalDiffing = true;
568  var c = new cable( this._canvas, p, dp );
569  c.topStack();
570  this._canvas.globalDiffing = false;
571  }
572  //else
573  // console.warn("Bad plug: "+oap);
574 
575  if (p._splittable)
576  p = this.getPlug( o, plug.OUTPUT, true );
577  }
578  }
579  }
580  },
581 
591  createAttributes: function( attrs )
592  {
593  //console.log( "create attributes", attrs );
594 
595  var d,v;
596 
597  //this._diffing = true;
598 
599  if (attrs)
600  {
601  var sorted = utils.sortObjectByMemberAttribute(attrs,"index");
602  //console.log(sorted);
603 
604  for (var __i=0;__i < sorted.length; __i++)
605  {
606  v = sorted[__i].__key;
607  d = attrs[v];
608 
609  if (!d || (typeof d !== "object")) continue;
610 
611  d.variable = v;
612  d.change = d.change || this.changeField.bind(this);
613  //d.change = this.changeField.bind(this);
614 
615  var w = widgetFactory( d );
616 
617  if (w)
618  { this._attributes[v] = w;
619  w.createHTML();
620  //this._formJq.append( w.createHTML() );
621 
622  w.activate();
623  }
624  else
625  {
626  if (preferences.fetch('reportFormErrors')===true)
627  {
628  console.dir(d);
629  //throw "Bad Widget: "+d;
630  }
631  }
632 
633  if (v === "type")
634  this.type = d.label;
635  }
636  }
637 
638  //this._diffing = false;
639 
640  //console.log(this._attributes);
641  },
642 
654  changeField: function(variable,value,widget)
655  {
656  //console.log("CHANGE FIELD!",arguments,this);
657  //console.trace();
658 
659  //create a getter/setter for variables we care about in a subclass
660  if (variable in this)
661  this[variable] = value;
662  //else
663  // console.log("not present:",variable);
664 
665 
666  if (this._diffing || this._canvas.globalDiffing)
667  return;
668 
669  var d = {};
670  var e = {};
671  e[variable] = { value:value };
672  var c = { attributes:e };
673  d[this._path] = c;
674  //console.log(d);
675 
676  utils.copyObject(d,this._diff);
677 
678  var THIS = this;
679  if (!this._diffTimer)
680  {
681  this._diffTimer = setTimeout( function()
682  {
683  //console.log("diff timer!",THIS._diff[THIS._path].attributes.node.value);
684  if (!THIS._firstLoad)
685  THIS._root.trigger( "nodechange", [ "diff", THIS._diff ] ); //record the diff to send to the server
686  THIS._firstLoad = false;
687 
688  THIS._canvas.globalDiffing = true;
689  THIS._canvas._diffing = true;
690  objectRegistry.applyDiff(THIS._path,THIS._diff[THIS._path],null,THIS); //propagate to any other open panes showing the same node
691  THIS._canvas._diffing = false;
692  THIS._canvas.globalDiffing = false;
693 
694  THIS._diffTimer = null;
695  THIS._diff = {};
696  }, 0);
697  }
698  },
699 
710  applySubscription: function(data, metadata)
711  {
712  //theoretically the nodeLoader already created this node.
713  //this case only happens upon reconnect.
714 
715  //console.log("node apply subscription... but node is already created?");
716 
717  //FIXME: apply node settings?
718  this.applyDiff(data,null);
719  },
720 
729  // startDiff: function()
730  // {
731  // //console.log(this.name + " starting diff");
732  // this._diffing = true;
733  // },
734 
743  // stopDiff: function()
744  // { this._diffing = false;
745  // },
746 
757  applyDiff: function(diff, user)
758  {
759  //console.log("node apply diff!",this._path,diff,this._root);
760  //console.log(arguments);
761 
762  if (diff === null)
763  {
764  console.log("node diff was null. returning.");
765  return;
766  }
767 
768  this._diffing = true;
769 
770  if ("attributes" in diff)
771  {
772  var d = diff["attributes"];
773  //console.log(d);
774 
775  for (var a in d)
776  {
777  var v = d[a];
778  if (!(v instanceof Object))
779  v = { value:v };
780 
781  if ("value" in v)
782  {
783  if (a in this._attributes)
784  { //this._attributes[a].immediateValue = d[a].value;
785  this._attributes[a].immediateValue = v.value;
786  }
787  //else
788  // console.log("no attribute '"+a+"'... will not apply diff");
789  }
790  // else
791  // {
792  // console.log( "No 'value' for '"+a+"'" );
793  // console.log( d[a] );
794  // }
795  }
796 
797  //if ("color" in d)
798  // this.color = this._attributes["color"];
799 
800  //if ("type" in d)
801  // this.type = this._attributes.type.value;
802  }
803 
804  if ("inputs" in diff)
805  {
806  var d = diff["inputs"];
807 
808  for (var a in d)
809  {
810  var v = d[a];
811  if (!(v instanceof Object))
812  v = { value:v };
813 
814  if ("value" in v)
815  {
816  if (v.value === null) //delete cable
817  {
818  var dp = this.getPlug( a, plug.INPUT );
819 
820  for (var i=0;i < dp.length; i++)
821  if (dp[i]._cable)
822  dp[i]._cable.destroy();
823  }
824  else //create cable
825  {
826  var iap;
827  var ia = v.value;
828  ia = utils.isArray( ia ) ? ia : [ ia ];
829 
830  //build the list of cables/plugs that exist but need deleting because they're not in the current state
831  var cc = this.getPlug( a, plug.INPUT );
832  var dlist = cc.filter( function(p) { return ( ia.indexOf(p.connectedPlug().pathString) == -1 ); } );
833  dlist.forEach( function(p) { p._cable.destroy(); } );
834 
835  for (var i = 0; i< ia.length; i++)
836  {
837  iap = ia[i];
838  //console.log(this.id+" / "+this._path+" connect input: "+a);
839  //var sp = this._canvas.getPlug( d[a].value, plug.OUTPUT, true );
840  var sp = this._canvas.getPlug( iap, plug.OUTPUT, true );
841  var dp = this.getPlug( a, plug.INPUT, true );
842  //console.log(sp);
843  //console.log(dp);
844 
845  if (sp && dp)
846  {
847  //console.log("created input cable!");
848  var c = new cable( this._canvas, sp, dp );
849  c.topStack();
850  }
851  //else
852  // console.error("Bad source or destination: "+a+" / "+d[a].value);
853  }
854  }
855  }
856  // else
857  // {
858  // console.log( "No 'value' for input '"+a+"'");
859  // console.log( d[a],diff );
860  // }
861  }
862  }
863 
864  if ("outputs" in diff)
865  {
866  var d = diff["outputs"];
867 
868  for (var a in d)
869  {
870  var v = d[a];
871  if (!(v instanceof Object))
872  v = { value:v };
873 
874  if ("value" in v)
875  {
876  if (v.value === null) //delete cable
877  {
878  var sp = this.getPlug( a, plug.OUTPUT );
879 
880  for (var i=0;i<sp.length; i++)
881  if (sp[i]._cable)
882  sp[i]._cable.destroy();
883  }
884  else //create cable
885  {
886  var oap;
887  var oa = v.value;
888  oa = utils.isArray( oa ) ? oa : [ oa ];
889 
890  //build the list of cables/plugs that exist but need deleting because they're not in the current state
891  var cc = this.getPlug( a, plug.OUTPUT );
892  var dlist = cc.filter( function(p) { return ( oa.indexOf(p.connectedPlug().pathString) == -1 ); } );
893  dlist.forEach( function(p) { p._cable.destroy(); } );
894 
895  for (var i = 0; i< oa.length; i++)
896  {
897  oap = oa[i];
898  //console.log(this.id+" / "+this._path+" connect output: "+a);
899  var sp = this.getPlug( a, plug.OUTPUT, true );
900  //var dp = this._canvas.getPlug( d[a].value, plug.INPUT, true );
901  var dp = this._canvas.getPlug( oap, plug.INPUT, true );
902  //console.log(sp);
903  //console.log(dp);
904 
905  if (sp && dp)
906  {
907  //console.log("created output cable!");
908  var c = new cable( this._canvas, sp, dp );
909  c.topStack();
910  }
911  //else
912  // console.error("Bad source or destination: "+a+" / "+d[a].value);
913  }
914  }
915  }
916  // else
917  // {
918  // console.log( "No 'value' for output '"+a+"'");
919  // console.log( d[a] );
920  // }
921  }
922  }
923 
924  if ("selection" in diff)
925  {
926  console.log("node selection change!");
927  }
928 
929  this._diffing = false;
930  },
931 
944  applyRequestSuccess: function(command,id,data,metadata)
945  {
946  //console.log("node applyRequestSuccess",arguments);
947  },
948 
958  //get diff(erence) from defaults
959  baseDiff: function(callback)
960  {
961  /*
962  var d = {
963  "base":"",
964  "type":"",
965  //"help":"",
966  "attributes":{},
967  "inputs":{},
968  "outputs":{}
969  };
970 
971  return d;
972  */
973 
974  //console.log("basediff node",this);
975 
976  this._canvas._objectLoader.fetchDefinition( this._nodeClass.base, {}, this.baseValues.bind(this,callback) );
977  },
978 
991  //the (asynchronous) callback part
992  baseValues: function(callback,definition)
993  {
994  //console.log("Definition: ",definition);
995 
996  var n,v,p;
997  var diff = {
998  //"base":this._nodeClass.base,
999  "base": this._base,
1000  "attributes":{},
1001  "inputs":{},
1002  "outputs":{},
1003  // "icon": this._icon,
1004  };
1005 
1006  //get attributes
1007  for (var a in this._attributes)
1008  {
1009  v = this._attributes[a].nonDefaultValue;
1010  if (v !== undefined)
1011  diff["attributes"][a] = v;
1012  }
1013 
1014  //get inputs
1015  for (var a in this._inputs)
1016  {
1017  p = this._inputs[a];
1018  v = p.diffState;
1019  if (v)
1020  diff["inputs"][p._name] = { value:v };
1021  }
1022 
1023  //get outputs
1024  for (var a in this._outputs)
1025  {
1026  p = this._outputs[a];
1027  v = p.diffState;
1028  if (v)
1029  diff["outputs"][p._name] = { value:v };
1030  }
1031 
1032  //console.log(definition);
1033  //console.log("baseValues",diff);
1034 
1035  if (callback)
1036  callback(diff);
1037  else
1038  return diff;
1039  },
1040 
1051  getConnectedNode: function(input) //return the first node connected to this node
1052  {
1053  var a = input ? this._inputs : this._outputs;
1054 
1055  for (var i in a)
1056  if (a[i].connected)
1057  return a[i]._cable.otherEnd( a[i] )._node;
1058 
1059  return null;
1060  },
1061 
1074  getPlug: function(n,input,firstAvailable)
1075  {
1076  var o = [];
1077  var a = input ? this._inputs : this._outputs;
1078  var t;
1079 
1080  for(var i in a)
1081  { t = a[i];
1082  if (t._name === n)
1083  if (firstAvailable)
1084  { if (t.idle)
1085  return t;
1086  //else
1087  // console.log(n+" not idle.");
1088  }
1089  else
1090  { if (t.connected && t.connectedPlug())
1091  o.push(t);
1092  }
1093  }
1094 
1095  if (o.length === 0)
1096  { //console.trace("could not find plug: "+n);
1097  // console.log(arguments);
1098  // console.log(this);
1099 
1100  return firstAvailable ? null : [];
1101  }
1102 
1103  return o;
1105  //if (n in this._inputs) return this._inputs[p];
1106  //if (n in this._outputs) return this._outputs[p];
1107 
1108  //return null;
1109  },
1110 
1120  splitPlug: function(p)
1121  {
1122  //find key for new plug
1123  var a = p._isInput ? this._inputs : this._outputs;
1124  var k = p._name;
1125  var n = this.splitPlugKey(k,a);
1126  //console.log("new plug would be named: "+n);
1127 
1128  //create new plug
1129  var _p = new plug( this, k, p._isInput, p._originalType );
1130 
1131  if ((p._isInput && this._hideInputLabels) || (!p._isInput && this._hideOutputLabels))
1132  _p.hideLabel();
1133 
1134  if (p._hideSplits)
1135  _p.hide();
1136 
1137  //splice in the new plug
1138  a[n] = _p;
1139  //a = utils.insertKey(a,n,_p,k);
1140  if (p._isInput) this._inputs = a;
1141  else this._outputs = a;
1142 
1143  //update node positions.
1144  this.resize();
1145  },
1146 
1156  unsplitPlug: function(p)
1157  {
1158  //console.trace("unsplit!");
1159 
1160  //find the last idle plug with a name that matches the given plug
1161  var a = p._isInput ? this._inputs : this._outputs;
1162  var k = p._name;
1163  var ks = Object.keys(a);
1164  var u=[];
1165  for (var i = ks.length-1; i>=0; i--) //get the key for idle plugs with the right name in inverse order
1166  if (a[ks[i]]._name == k && a[ks[i]].idle)
1167  u.push( ks[i] );
1168 
1169  if (u.length)
1170  {
1171  if (p._hideSplits) //there is a case where the first of a hidden split plug is revealed because the first plug is disconnected while others are connected.
1172  if (u[1] == ks[0])
1173  { if (ks.length == 2)
1174  { //console.log("show here!");
1175  a[ks[0]].show();
1176  }
1177  else
1178  { //console.log("hide here!");
1179  a[ks[0]].hide();
1180  }
1181  }
1182 
1183  a[u[0]].destroy();
1184  delete a[u[0]];
1185  this.resize();
1186  }
1187  else
1188  { console.log(this);
1189  throw "Could not unspslit the plug... nothing available called '"+k+"'";
1190  }
1191  },
1192 
1204  splitPlugKey: function(k,a)
1205  {
1206  var i = 0;
1207 
1208  while( k+"."+i in a && i<100 )
1209  i++;
1210 
1211  if (i>=100) throw "Can't split plug... too many plugs named '"+k+"'";
1212 
1213  return k+"."+i;
1214  },
1215 
1227  bestPlugCandidate: function(c,pp)
1228  {
1229  var p;
1230  if (c && (p = c.otherEnd(pp)))
1231  {
1232  if (p._node == this) //can't plug a node to itself
1233  return null;
1234 
1235  var t = p.resolvedType(true);
1236  var a = p._isInput ? this._outputs : this._inputs;
1237  //console.log("compat with: "+t);
1238  //console.log(p);
1239 
1240  for (var i in a) //if there's an available (idle) plug of the right type, choose that one
1241  if (a[i].idle && a[i].compatibleType(t))
1242  return a[i];
1243 
1244  for (var i in a) //if there's an occupied plug of the right type, (disconnect?) and return that one
1245  if (a[i].connected && a[i].compatibleType(t))
1246  return a[i];
1247  }
1248 
1249  return null;
1250  },
1251 
1260  showPlugs: function()
1261  {
1262  for (var i in this._inputs)
1263  this._inputs[i].show();
1264 
1265  for (var o in this._outputs)
1266  this._outputs[o].show();
1267  },
1268 
1277  hidePlugs: function()
1278  {
1279  var ikeys = {};
1280  for (var i in this._inputs)
1281  {
1282  var ip = this._inputs[i];
1283  var im = ip._name;
1284 
1285  if (ikeys[im])
1286  ip.hide();
1287  else
1288  ikeys[im] = true;
1289  }
1290 
1291  var okeys = {};
1292  for (var o in this._outputs)
1293  {
1294  var op = this._outputs[o];
1295  var om = op._name;
1296 
1297  if (okeys[om])
1298  op.hide();
1299  else
1300  okeys[om] = true;
1301  }
1302  },
1303 
1313  bridgeOnDestroy: function() //this allows nodes being destroyed to bridge existing connections, like when a dot is destroyed it should bridge the input and output with a new cable
1314  {
1315  var s=null, d=null;
1316 
1317  for (var i in this._inputs)
1318  if (( s = this._inputs[i].connectedPlug() ))
1319  break;
1320 
1321  for (var o in this._outputs)
1322  if (( d = this._outputs[o].connectedPlug() ))
1323  break;
1324 
1325  if (s && d)
1326  if (s.type == d.type)
1327  return [ { source:s.path, dest:d.path } ];
1328 
1329  return []; //return an empty array of bridges
1330  },
1331 
1342  resolvedType: function(_plug) //if an input and an output need to be of the same type, but their type is "*", this is an opportunity to resolve it, like for a dot.
1343  {
1344  return _plug._type;
1345  },
1346 
1356  refreshCableColor: function(_plug) //only continue here if the node has types that have to match, like a dot node.
1357  {
1358  return;
1359  },
1372  diffPosition: function(variable,value,widget)
1373  {
1374  this.position( this.x, this.y );
1375  //this.changeField.apply(arguments) //already done by the group widget
1376  },
1377 
1389  position: function(x,y)
1390  {
1391  if (arguments.length)
1392  {
1393  //console.log("position",arguments);
1394 
1395  x = Math.round(x*10)/10;
1396  y = Math.round(y*10)/10;
1397 
1398  var v = { "x":{value:x}, "y":{value:y} };
1399  //var i = this._attributes._immediate;
1400  //this._attributes._immediate = true;
1401  //this._attributes.node.value = v;
1402  this._attributes.node.immediateValue = v;
1403  //this._attributes._immediate = i;
1404 
1405  //http://wilsonpage.co.uk/preventing-layout-thrashing/ //now requestAnimation frame is sprinkled throughout
1406  var THIS = this;
1407  requestAnimationFrame( function()
1408  {
1409  if (THIS._root)
1410  utils.setAttr( THIS._root[0], { "transform": "translate("+THIS.x+","+THIS.y+")" } );
1411  } );
1412 
1413  for (var i in this._inputs)
1414  this._inputs[i].refreshCables();
1415 
1416  for (var o in this._outputs)
1417  this._outputs[o].refreshCables();
1418  }
1419 
1420  return { x:this.x, y:this.y };
1421  },
1422 
1435  boundsPosition: function(pt,labelPoint,dist) //position of a point on the bounds of this node, based on the position of another point (presumably the center of another node)
1436  {
1437  var boundsPointingTo = function(px, py, x, y, w, h, a)
1438  {
1439  var quad,rx,ry;
1440 
1441  //figure out where the corners of the bb are, in radians, based on the bb being centered on 0,0, and negative x,y
1442  var theta = Math.atan2(-y,-x);
1443  //console.log(x+" "+y);
1444  //console.log(theta);
1445 
1446  //figure out which quadrant we're in, which will identify which side of the bounds we're on
1447  var phi = Math.atan2(py,px);
1448  if ( Math.abs(phi) <= theta ) quad = 0; //right quad
1449  else if ( Math.abs(phi) >= Math.PI - theta ) quad = 2; //left
1450  else if ( phi >= theta ) quad = 1; //lower
1451  else quad = 3; //upper
1452 
1453  a.theta = phi;
1454 
1455  //console.log(phi);
1456  //console.log(quad);
1457 
1458  //find the point on that boundary. Quadrant is clockwise from 3 o'clock.
1459  switch (quad)
1460  { case 0: rx = w/2; ry = Math.tan( phi ) * w/2; break;
1461  case 1: ry = h/2; rx = 1/Math.tan( phi ) * h/2; break;
1462  case 2: rx = -w/2; ry = Math.tan( -phi ) * w/2; break;
1463  case 3: ry = -h/2; rx = -1/Math.tan( phi ) * h/2; break;
1464  }
1465 
1466  //console.log(rx+" "+ry);
1467 
1468  return { x:rx,
1469  y:ry,
1470  };
1471  };
1472 
1473  dist = (dist !== undefined) ? dist : 3;
1474 
1475  //var b = this._background[0].getBBox();
1476  var b = this._boundingBox;
1477  b = utils.extendBBox(b,dist); //extend bb
1478  var a = {theta:0};
1479 
1480  // var pt1 = this._root[0].ownerSVGElement.createSVGPoint();
1481  // var ctm = this._root[0].getTransformToElement( this._canvas._root[0] );
1482  // var ctmi = ctm.inverse();
1483  // pt1.x = pt.x;
1484  // pt1.y = pt.y;
1485  // pt1 = pt1.matrixTransform( ctmi );
1486  var pt1 = {};
1487  pt1.x = pt.x - this.x;
1488  pt1.y = pt.y - this.y;
1489  var pt2 = boundsPointingTo( pt1.x, pt1.y, b.x, b.y, b.width, b.height,a ); //get bb pos based on transforms
1490  pt1.x = pt2.x + this.x;
1491  pt1.y = pt2.y + this.y;
1492  //var ptR = pt1.matrixTransform( ctm );
1493 
1494  var ldist = 12;
1495  labelPoint.x = Math.cos(a.theta) * ldist;
1496  labelPoint.y = Math.sin(a.theta) * ldist;
1497 
1498  return pt1;
1499  //return ptR;
1500  },
1501 
1510  resize: function()
1511  {
1512  this._root.show();
1513 
1514  var paddingw = 3;
1515  var paddingh = 3;
1516  var bb = this._label[0].getBBox();
1517  var b = this._background;
1518  var g = this._gradient;
1519  var p = this._progress;
1520 
1521  var x = -bb.width/2-paddingw*2;
1522  var y = -bb.height/2-paddingh;
1523  var w = bb.width+paddingw*4;
1524  var h = bb.height+paddingh*2;
1525 
1526  var a = {
1527  x:x,
1528  y:y,
1529  width:w,
1530  height:h,
1531  };
1532  b.attr(a);
1533  g.attr(a);
1534  this._boundingBox = a;
1535 
1536  var pw = w*this.progress/100;
1537  var pp = {
1538  x:x,
1539  y:y,
1540  width:pw,
1541  height:h,
1542  };
1543  if (pw === 0)
1544  pp.display = "none";
1545  p.attr(pp);
1546 
1547  var sf = function(k,o) { return (!o[k].connected && !o[k].mask); };
1548  var mf = function(k,o) { return (o[k].mask); };
1549 
1550  var ic = utils.keyCountFiltered(this._inputs, sf );
1551  var s = w/(ic+1);
1552  s = Math.min( s, 10 ); //squeeze a little on wide ones
1553  //var xc = x+s;
1554  var xc = -((ic-1)*s)/2;
1555  for (var i in this._inputs)
1556  { if (this._inputs[i].connected || this._inputs[i].mask) continue;
1557  this._inputs[i].position(xc,y);
1558  xc += s;
1559  }
1560 
1561  var mc = utils.keyCountFiltered(this._inputs, mf );
1562  if (mc > 1)
1563  console.warn("Don't know how to position more than one mask input");
1564  for (var i in this._inputs)
1565  { if (this._inputs[i].connected || !this._inputs[i].mask) continue;
1566  this._inputs[i].position(w/2,0);
1567  }
1568 
1569  var oc = utils.keyCountFiltered(this._outputs, sf );
1570  var s = w/(oc+1);
1571  s = Math.min( s, 10 ); //squeeze a little
1572 
1573  //var xc = x+s;
1574  var xc = -((oc-1)*s)/2;
1575  for (var o in this._outputs)
1576  { if (this._outputs[o].connected) continue;
1577  this._outputs[o].position(xc,y+h);
1578  xc += s;
1579  }
1580  },
1581 
1590  startMove: function()
1591  {
1592  //this._root.attr( {filter:"url(#dropShadowMoving)"} );
1593  //this._background.attr( {filter:"url(#dropShadowMoving)"} );
1594  //this._gradient.attr( {filter:"url(#dropShadowMoving)"} );
1595 
1596  this._startPos.x = this.x;
1597  this._startPos.y = this.y;
1598 
1599  this.topStack();
1600 
1601  this._background.attr( { "class":"nodeBackgroundNoPointer" } );
1602 
1603  this._moving = true;
1604  },
1605 
1615  stopMove: function(noundo)
1616  {
1617  //this._root.attr( {filter:"url(#dropShadow)"} );
1618  //this._background.attr( {filter:"url(#dropShadow)"} );
1619  //this._gradient.attr( {filter:"url(#dropShadow)"} );
1620 
1621  //console.log(this._startPos)
1622  //console.log(this._attributes);
1623 
1624  if (!noundo)
1625  if ("x" in this._startPos && "y" in this._startPos)
1626  if (this._startPos.x !== this.x || this._startPos.y !== this.y)
1627  this._root.trigger("undoevent", { type:"Move Node: "+this.name, undo:this.position.bind(this,this._startPos.x,this._startPos.y), redo:this.position.bind(this,this.x,this.y) } );
1628 
1629  this._background.attr( { "class":"nodeBackground" } );
1630 
1631  this._moving = false;
1632  },
1633 
1644  moveBy: function(x,y)
1645  {
1646  this.position( this._startPos.x + x, this._startPos.y + y );
1647  },
1648 
1660  checkSnap: function(x,y)
1661  {
1662  //snapDistance
1663 
1664  var s={x:null,y:null}
1665  var n,p,dx,dy,adx,ady,sx,sy;
1666  var THIS = this;
1667 
1668  function snapper(a,x,y)
1669  {
1670  for (var i in a)
1671  {
1672  n = a[i].connectedNode();
1673  if (n && !n.selected)
1674  {
1675  p = n.position();
1676  //console.log(this.x+","+this.y+" // "+p.x+","+p.y);//+" // "+x+","+y);
1677 
1678  dx = p.x-(THIS._startPos.x+x);
1679  dy = p.y-(THIS._startPos.y+y);
1680  adx = Math.abs(dx);
1681  ady = Math.abs(dy);
1682  sx = p.x - THIS._startPos.x;
1683  sy = p.y - THIS._startPos.y;
1684 
1685  if ( (adx <= snapDistance) && (s.x===null || sx < s.x) )
1686  s.x = sx;
1687  // else
1688  // s.x = x;
1689 
1690  if ( (ady <= snapDistance) && (s.y===null || sy < s.y) )
1691  s.y = sy;
1692  // else
1693  // s.y = y;
1694  }
1695  }
1696  }
1697 
1698  snapper(this._inputs,x,y);
1699  snapper(this._outputs,x,y);
1700 
1701  //console.log(s);
1702 
1703  return s;
1704  },
1705 
1715  topStack: function(skipPlugs)
1716  {
1717  if (skipPlugs !== true)
1718  {
1719  for (var i in this._inputs)
1720  this._inputs[i].topStack();
1721 
1722  for (var o in this._outputs)
1723  this._outputs[o].topStack();
1724  }
1726  var p = this._canvas._layers[this._layer];
1727  this._root.appendTo( p );
1728  },
1729 
1738  resetInvalids: function()
1739  {
1740  for (var i in this._inputs) this._inputs[i].resetInvalid();
1741  for (var o in this._outputs) this._outputs[o].resetInvalid();
1742  },
1743 
1753  dotsBelow: function()
1754  {
1755  var del = [];
1756  var dest = {};
1757  var p,nm,cp;
1758 
1759  function resType(p)
1760  {
1761  var t = p.resolvedType();
1762  return (t=="*") ? p.resolvedType(true) : t;
1763  }
1764 
1765  var _o = this._outputs;
1766  for (var o in _o) //for all outputs
1767  {
1768  p = _o[o];
1769  if (p.connected) //if connected
1770  { nm = p._name+"."+resType(p);
1771  //console.log(nm);
1772  if (!dest[nm]) dest[nm] = []; //create an array
1773  dest[nm].push( p.connectedPlug().path ); //of the destinations of those connections
1774  del.push(p._cable); //and mark this cable as removable
1775  }
1776  }
1777 
1778  var THIS = this;
1779  var cnv = this._canvas._root;
1780  del.forEach( function(c)
1781  {
1782  var spath = c._source.path;
1783  var dpath = c._dest.path;
1784  var sig = cable.connectID+" "+spath.node+"."+spath.plug+" -> "+dpath.node+"."+dpath.plug;
1785  var paths = {source:spath,dest:dpath};
1786  THIS._root.trigger("undoevent", { //create the undoevent
1787  type: "Remember Cable: "+sig,
1788  signature: sig,
1789  undo: cnv.trigger.bind(cnv,"cableconnect",paths),
1790  redo: cnv.trigger.bind(cnv,"cabledestroy",paths),
1791  }
1792  );
1793  c.destroy(); //and now remove the cable
1794  } );
1795 
1796  if (utils.keyCount(dest) == 0)
1797  for (var o in _o)
1798  { p = _o[o];
1799  nm = p._name;
1800  dest[nm] = [];
1801  }
1802 
1803  return dest; //and return the patches that need to be made
1804  },
1805 
1815  getBBox: function()
1816  {
1817  //var bb = this._background[0].getBBox();
1818  var bb = utils.extendBBox( this._boundingBox, 0 );
1819 
1820  bb.x += this.x;
1821  bb.y += this.y;
1822 
1823  return bb;
1824  },
1825 
1837  //get id() { return this._path; },
1838  get id() { return this._attributes.uuid.value; },
1839 
1846  get name() { return this._attributes.name.value; },
1847 
1854  set name(n) { this._attributes.name.value = n;
1855  if (this._label) //dots don't have a label
1856  this._label.text( n );
1857 
1858  this.resize();
1859  this.position( this.x, this.y );
1860  },
1861 
1868  get type() { return this._type;
1869  },
1870 
1877  set type(n) { this._type = n;
1878  if (this._title)
1879  this._title.text( n );
1880  },
1881 
1882 
1889  get color() { return this._attributes.node.value.color.value; },
1897  set color(c) { this._attributes.node.value.color.value = c;
1898  this._background.attr( {
1899  fill:"#"+this._attributes.node.value.color.value,
1900  } );
1901 
1902  var l = color.fromHTML(c).luminance;
1903 
1904  if (this._label)
1905  this._label.css( {
1906  fill: (l > .6) ? "#000000" : "#FFFFFF",
1907  } );
1908 
1909  return c;
1910  },
1911 
1918  get x() { return this._attributes.node.value.x.value; },
1919 
1926  get y() { return this._attributes.node.value.y.value; },
1927 
1934  get selected() {
1935  return this._selected;
1936  },
1937 
1944  set selected(s) {
1945  this._selected = s;
1946 
1947  if (s)
1948  this._root.attr( {"class":"node node-selected"} );
1949  else
1950  this._root.attr( {"class":"node"} );
1951 
1952  return this._selected;
1953  },
1954 
1961  get progress() {
1962  if ("progress" in this._attributes)
1963  return this._attributes.progress.value;
1964  else
1965  return 0;
1966  },
1967 
1974  set progress(p) {
1975  //console.log("update progress!",p);
1976 
1977  if (!("progress" in this._attributes))
1978  {
1979  this._progress.attr({
1980  display:"none"
1981  });
1982  //console.warn("tried to set progress when progress was not an existing attribute.");
1983  return;
1984  }
1985 
1986 
1987  p = Math.min(100,Math.max(0,p));
1988  this._attributes.progress.value = p;
1989 
1990  if (p > 0)
1991  {
1992  var b = this._boundingBox;
1993  var bw = b.width;
1994  this._progress.attr({
1995  width:(bw*p/100),
1996  display:null
1997  });
1998  }
1999  else
2000  {
2001  this._progress.attr({
2002  display:"none"
2003  });
2004  }
2005 
2006  return p;
2007  },
2008 
2021  isAncestorOf: function(n)
2022  {
2023  return (n.isChildOf(this));
2024  },
2040  isChildOf: function(n)
2041  {
2042  for (var c in this._inputs)
2043  if (this._inputs[c].isConnectedTo(n))
2044  return true;
2045 
2046  return false;
2047  },
2048  };
2049 
2050  return node;
2051  }
2052 );
2053 
applySubscription(data, metadata)
setter color
a setter DOCME
setter selected
a setter DOCME
cable(_cnv, _source, _dest, notrigger)
topStack()
destroy(createUndo)
Javascript color object class.
fromHTML(html)
setter value
a setter DOCME
getter id
returns the number of milliseconds since midnight January 1, 1970 UTC
resolvedType(_plug)
bridgeOnDestroy()
boundsPosition(pt, labelPoint, dist)
setter user
a setter DOCME
changeField(variable, value, widget)
A grouping widget which will put child widgets in a row instead of the normal form column.
getter position
A getter, returns this._position.
setter widget
a setter DOCME
createInputs(_in)
isAncestorOf(n)
stopMove(noundo)
createOutputs(_out)
getPlug(n, input, firstAvailable)
showPlugs()
show all hidden plugs, which would be split plugs that are not connected
node(_path, _cnv, _attrs, _in, _out, _base, _icon)
getConnectedNode(input)
splitPlug(p)
createAttributes(attrs)
diffPosition(variable, value, widget)
baseValues(callback, definition)
return the values of this node that differ from the default value
initHandlers()
hidePlugs()
show all hidden plugs, which would be split plugs that are not connected
bestPlugCandidate(c, pp)
baseDiff(callback)
dotsBelow()
getBBox()
getter x
a getter DOCME
getter y
a getter DOCME
moveBy(x, y)
isChildOf(_path, _cnv, _attrs, _in, _out)
startMove()
setter name
a setter DOCME
splitPlugKey(k, a)
resetInvalids()
unsplitPlug(p)
checkSnap(x, y)
setter type
a setter DOCME
The numberWidget is a form field for numerical input.
applyDiff(id, diff, user, except)
registerObject(id, o, nosubscribe)
Register an object in the registry, and call the pathAddedCallback.
unregisterObject(id, o, silent, nounsubscribe, now)
Unregister an object from the registry.
attach()
Attach the tabs's children.
detach()
Detach the tab's children.
isConnectedTo(_node)
getter connected
a getter DOCME
connectedPlug()
getter type
a getter DOCME
plug(_node, _name, _isInput, _type)
refreshCableColor()
getter mask
a getter DOCME
getter idle
a getter DOCME
compatibleType(type)
applyRequestSuccess()
If an adminstrator has deleted his own preferences, this function will be called on success.
fetch(v, _default)
progress(c, t, f, w, hoc)
resize()
Does nothing.
applyDiff(diff, user)
Utility functions for javascript clients.
keyCountFiltered(o, f)
Return the number of keys on an object that match a filter.
uuid()
Generate a universally unique identifier.
cleanPath(path)
Clean and normalize a resource path.
copyObject(source, dest)
Create a deep copy of a JSON object, extending the destination object recursively.
keyCount(o)
Return the number of keys in an object.
sortObjectByMemberAttribute(o, attr)
Sort an object by the value of an attribute of members of the object.
setAttr(e, a)
A DOM manipulation function to work around a jQuery bug.
extendBBox(b, a)
Extend bounding box b by an amount a.
isArray(a)
Determine if an object is a javascript array.
keyFromValue(o, val)
Given a value, retrieve the key for a potential entry in an object.
metadata(paths)
A factory and registry for loaded widget definitions.
widgetFactory(d)
Create a widget, if possible, or return a widgetLoader.
Definition: pythonUtils.h:14