Remoto - VFS: paneManager.js Source File
Remoto - VFS
paneManager.js
Go to the documentation of this file.
1 
2 
4 // Pane Manager //
6 
7 define( [
8  'require',
9  'include/browserDetect',
10  'remoto!stdlib:js/include/utils.js',
11  'remoto!stdlib:js/include/keyboard.js',
12  'include/modal/modal',
13  "remoto!stdlib:js/paneManager/paneMenu.js",
14  'remoto!stdlib:js/include/objectRegistry.js',
15  'remoto!stdlib:js/include/preferences.js',
16  'remoto!stdlib:js/panes/panes/paneLoader.js',
17  'remoto!stdlib:js/panes/panes/paneLoader.svg',
18  'remoto!stdlib:js/paneManager/images/icons/mergeSplitter.gif',
19  'remoto!stdlib:js/paneManager/images/icons/splitHorizontal.gif',
20  'remoto!stdlib:js/paneManager/images/icons/splitVertical.gif',
21  'jquery/ui/jquery.ui.tabs2',
22  'remoto!stdlib:js/paneManager/paneManager.css',
23  ],
24 
25  function(require,browserDetect,utils,keyboard,modal,menuManager,objectRegistry,preferences,paneLoader,paneLoaderImg,mergeSplitterImg,splitHorizontalImg,splitVerticalImg)
26  {
27  'use strict';
28 
29  var _paneManager = null;
30 
32  // Pane Manager //
34 
54  function paneManager()
55  {
56  _paneManager = this;
57 
58  this._jq = $();
59  this._root = null;
60  this._primaryCell = null;
61  this._currentPane = null;
62  this._singlePane = false;
63  this._singlePaneBorderless = false;
64  this._singlePaneSelected = -1;
65  this._nonSingleLayout = null;
66  this._firstLoadFlag = true;
67  this._interfacePath = null;
68  this._config = null;
69 
70  this._panes = {};
71 
72  //keyboard.registerCommand("space", this.toggleSinglePane.bind(this) );
73  //keyboard.registerCommand("shift-space", this.toggleSinglePaneBorderless.bind(this) );
74  //keyboard.registerCommand("meta-t,alt-t", tabswitcher.prototype.addTabPrompt );
75  keyboard.registerCommand("shift-meta-l", function() { $(window).trigger("switchuser"); } );
76  keyboard.registerCommand("shift-meta-p", function() { $(window).trigger("openUserPreferences"); } );
77 
78  keyboard.registerCommand("shift-meta-f,F11", utils.toggleFullScreen );
79 
80  keyboard.registerCommand("DEL,BACKSPACE",false,false,false,1); //prevent delete/backspace from causing the back button to trigger
81  keyboard.registerCommand("meta-s,ctrl-s,alt-s",false,false,false,1);
82 
83  keyboard.registerCommand("shift-meta-o", function() { objectRegistry.printRegistry(); } );
84 
85  $(window).bind("resize", this.resize.bind(this) );
86  $(document).bind("contextmenu", false ); //disable default context menu
87  }
88 
89  paneManager.prototype = {
90 
99  reset: function()
100  {
101  //console.trace("RESET PANE MANAGER!");
102 
103  this._jq.remove();
104  this._jq = $();
105  this._root = null;
106  this._primaryCell = null;
107  this._currentPane = null;
108  this._singlePane = false;
109  this._singlePaneBorderless = false;
110  this._singlePaneSelected = -1;
111  this._nonSingleLayout = null;
112  this._firstLoadFlag = true;
113  this._config = null;
114 
115  this._panes = {};
116 
117  //to ensure that resize events are kept if window events are reset
118  $(window).unbind("resize");
119  $(window).bind("resize", this.resize.bind(this) );
120 
121  objectRegistry.unregisterObject( this._interfacePath, this, false, false, true );
122  },
123 
135  set config(c) { return this._config = c; },
136 
143  get config() { return this._config; },
144 
154  applySubscription: function(data,metadata)
155  {
156  this.applyLayout(data);
157  },
158 
166  applyDiff: function(diff,user)
167  {
168  if (!diff)
169  {
170  console.warn("Received null layout update. Returning.");
171  return;
172  }
173 
174  this.applyLayout(diff);
175  },
176 
188  /*
189  singlePaneLayout: function(layout,borderless)
190  {
191 
192  var l = { type: "tabswitcher",
193  primary: true,
194  selected: -1,
195  //singlePane: true, //DO NOT ADD THIS! It will cause an infinite loop on startup
196  borderless: borderless,
197  children: [],
198  };
199 
200  //if (!layout)
201  {
202  var i=0,s;
203  var biggestSize = 0;
204  var biggest = 0;
205  for (var t in this._panes)
206  for (var p in this._panes[t])
207  for (var j=0; j<this._panes[t][p].length;j++)
208  { if (this._panes[t][p][j]._parent) //only panes with parents are allowed (there are cases like paneTransitioner who create panes that should not be included here).
209  {
210  l.children.push( this._panes[t][p][j].saveLayout() );
211  if (this._panes[t][p][j] === this._currentPane)
212  l.selected = i;
213  s = this._panes[t][p][j].area();
214  if (s > biggestSize)
215  { biggestSize = s;
216  biggest = i;
217  }
218 
219  i++;
220  }
221  }
222  }
223 
224  function __push(l)
225  {
226  return {
227  path: l.path,
228  type: l.type,
229  name: l.name,
230  }
231  }
232 
233  function __find(l)
234  {
235  var t = [];
236 
237  if (l.type === "pane" || l.keepType)
238  t.push( __push(l) );
239 
240  if (l.children)
241  for (var i=0; i<l.children.length; i++)
242  t = t.concat( __find( l.children[i] ) );
243 
244  return t;
245  }
246 
247  if (l.children.length === 0)
248  { l.children = __find(layout);
249  console.log(l);
250  console.log(layout);
251  }
252 
253  if (l.selected === -1)
254  { //no pane has been set as current...
255  //probably the page has just loaded
256  //select the biggest pane
257  l.selected = biggest;
258  }
259 
260  return l;
261  },
262  */
263 
273  applyLayout: function(_layout)
274  {
275  //console.log("apply layout",_layout,this._panes);
276 
277  //save out existing panes (and keep event handlers on detached things)
278  for (var p in this._panes)
279  if (this._panes[p]._parent)
280  this._panes[p].detach();
281 
282  //The select tab menu is an internal feature... it needs to be reset if we're rebuilding the layout
283  for (var i=0;i<tabs.tabsSelectMenus.length;i++)
284  menuManager.removeMenu(tabs.tabsSelectMenus[i]);
285  tabs.tabsSelectMenus = [];
286  for (var i=0;i<tabswitcher.tabswitcherSelectMenus.length;i++)
287  menuManager.removeMenu(tabswitcher.tabswitcherSelectMenus[i]);
288  tabswitcher.tabswitcherSelectMenus = [];
289 
290  this._root = this.buildLayout( _layout ); //build this tree based on the layout
291 
292  if (this._singlePane && this._firstLoadFlag)
293  this._root._selected = this._singlePaneSelected;
294 
295  //if the layout indicates that singlePane is true, build as singlePane.
296  if (_layout.singlePane && this._firstLoadFlag)
297  {
298  this._singlePaneSelected = _layout.singlePaneSelected;
299 
300  if (_layout.singlePaneBorderless)
301  this.toggleSinglePaneBorderless(null,true);
302  else
303  this.toggleSinglePane(null,true);
304 
305  return;
306  }
307 
308  //the single pane loader will have already triggered, to consider firstLoad over, and mark as false.
309  this._firstLoadFlag = false;
310 
311  //remove DOM from interface (and remove event handlers for non-detached things, but not if we are mounting a pane as root, which will already have been detached)
312  if (!(this._root instanceof pane))
313  this._jq.remove();
314  //else
315  // console.log("root is pane");
316 
317 
318  //create layout DOM
319  this._jq = this._root.createHTML(); //FIXME: it seems like panes should createHTML in their constructor, which should happen in buildLayout()
320 
321  //attach the new DOM
322  $(window.paneManagerTag || "body").append(this._jq);
323 
324  //activate splitter handles and tabs, etc.
325  this._root.activate();
326 
327  //check existing panes to make sure they are attached to the DOM... if not, add them to the primaryCell
328  if (!this._primaryCell) //if no primary cell, find first one. Better than a crash.
329  this._primaryCell = this._root.find(null,tabs);
330 
331  this.garbageCollect();
332 
333  this.resize();
334  },
335 
336 
346  garbageCollect: function()
347  {
348  //garbage collect unmounted cells
349  //console.log("GARBAGE COLLECT");
350 
351  var _attach = [];
352  var _destroy = [];
353 
354  for (var p in this._panes)
355  {
356  if (!this._panes[p].attached())
357  _destroy.push( this._panes[p] );
358  else
359  _attach.push( this._panes[p] );
360  }
361 
362  //console.log("DESTROY: ",_destroy);
363  //console.log("ATTACH: ",_attach);
364 
365  _destroy.forEach( function(o) { o.destroy(); } );
366  _attach.forEach( function(o) { o.attach(); } );
367  },
368 
377  initialize: function(path) //create an instance of each basic object, and throw it away, so that things like menus can be initialized.
378  {
379  this._nonSingleLayout = null;
380  this._interfacePath = path;
381 
382  new tabswitcher();
383  new tabs();
384  new splitter();
385  new pane();
386 
387  objectRegistry.registerObject( this._interfacePath, this );
388  },
389 
398  saveCallback: null,
399 
409  saveLayout: function()
410  {
411  if (!this._root)
412  return null;
413 
414  var l = this._singlePane ? this._nonSingleLayout : this._root.saveLayout();
415 
416  if (this._singlePane)
417  {
418  l.singlePane = true;
419  l.singlePaneSelected = this._root._selected;
420  l.singlePaneBorderless = this._singlePaneBorderless;
421  }
422 
423  //console.log("SAVE LAYOUT");
424  //console.log(l);
425 
426  if (this.saveCallback)
427  this.saveCallback(l);
428 
429  return l;
430  },
431 
442  buildLayout: function(_layout)
443  {
444  //console.log("Building layout...");
445  //console.log(_layout);
446 
447  this._primaryCell = null;
448  //this._currentPane = null;
449 
450  var _node = null;
451 
452  switch(_layout.type)
453  {
454  case "tabswitcher": _node = new tabswitcher(_layout); break;
455  case "splitter": _node = new splitter(_layout); break;
456  case "tabs": _node = new tabs(_layout); break;
457  case "pane": _node = this.fetchPane(_layout);
458  if (!_node._parent)
459  _node._parent = true; //a root pane should have a parent so that it can properly detach. 'true' is an illegal parent for anything useful, but for now it allows applyLayout() to do its thing.
460  break;
461 
462  default: _node = this.fetchPane(_layout);
463  // _node._parent = true; break;
464  // console.log( "Unknown pane type: '"+_layout.type+"'" );
465  // console.log(_layout);
466  break;
467  }
468 
469  if (_node)
470  _node.createHTML();
471  else
472  { console.warn( "Unknown paneManager pane type: '"+_layout.type+"'" );
473  console.log(_layout);
474  }
475 
476  return _node;
477  },
478 
488  objectFactory: function(layout) { return null },
489 
499  objectDestroy: function(pane) { return null },
500 
501  /*
502  createPane: function(layout,object,parent)
503  {
504  var p,n = null;
505  var t = (parent) ? this._root.find(parent,tabs) : this._primaryCell;
506 
507  if (t)
508  { p = new pane(layout,object);
509  n = t.appendChild( p,true );
510  this.applyLayout( this.saveLayout() );
511  }
512  else
513  console.error("No primaryCell in the _paneManager. Tab was not added.");
514 
515  return n;
516  },
517  */
518 
531  fetchPane: function(c,o)
532  {
533  if (!c.id) c.id = utils.uuid();
534 
535  var id = c.id;
536  var path = c.path;
537  var p;
538 
539  //console.log("fetchPane creating new pane: "+id+" ("+t+")",this._panes);
540 
541  if (this._panes[id])
542  { this._panes[id].applySettings(c); //apply settings to the fetched pane
543  return this._panes[id];
544  }
545 
546  return new pane(c,o); //registers itself in the constructor.
547  },
548 
562  findPane: function(c,index)
563  {
564  if (arguments.length < 2)
565  index = 0;
566 
567  if (!c.id) c.id = utils.cleanPath(c.path);
568 
569  var id = c.id;
570  var t = c.metadata ? c.metadata.type : c.type;
571 
572  if (this._panes[t])
573  if (this._panes[t][id])
574  if (this._panes[t][id][index])
575  return this._panes[t][id][index];
576 
577  return null;
578  },
579 
588  pathAddedCallback: null, //callback for if a new path is introduced to the interface (for subscribing, for instance)
589 
598  pathRemovedCallback: null, //callback for if a path is fully removed from the interface (for unsubscribing, for instance)
599 
608  pathSubmitCallback: null, //callback to let the paneManager submit to files in the VFS
609 
641  pathCreateCallback: null, //callback to let the paneManager create new files in the VFS
642 
668  pathDeleteCallback: null,
669 
679  pathList: function() //when a reconnection occurs, this is the list of current panes, which will need to be resubscribed
680  {
681  var l = [];
682 
683  for (var i in this._panes)
684  l.push( this._panes[l]._path );
685 
686  l = [...new Set(l)];
687 
688  return l;
689  },
690 
698  registerPane: function(p)
699  {
700  //console.log("register pane: "+p._id,p);
701 
702  var id = p._id;
703  var path = utils.cleanPath(p._path);
704 
705  this._panes[id] = p;
706 
707  if (path)
709  },
710 
720  unregisterPane: function(p)
721  {
722  var id = p._id;
723  var path = p._path;
724 
725  if (this._panes[id])
726  {
727  delete this._panes[id];
728 
729  if (path)
731  }
732  else
733  throw "Registered pane matching object with id='"+id+"' was not found.";
734  },
735 
736 
747  /*
748  toggleSinglePane: function(e,nosave)
749  {
750  //console.log(e);
751 
752  this._singlePane = !this._singlePane;
753  this._singlePaneBorderless = false;
754 
755  if (this._singlePane)
756  {
757  //console.log('single...');
758  this._nonSingleLayout = this._root.saveLayout();
759  this.applyLayout( this.singlePaneLayout(this._nonSingleLayout) );
760  //$(window).trigger("footerhide",true);
761  }
762  else
763  {
764  //console.log('non-single...');
765  this.applyLayout( this._nonSingleLayout );
766  $(window).trigger("footerhide",false);
767  }
768 
769  if (!nosave)
770  this.saveLayout();
771 
772  $(window).trigger("singlepane",this._singlePane);
773  $(window).trigger("singlepaneborderless",this._singlePaneBorderless);
774  },
775  */
776 
787  /*
788  toggleSinglePaneBorderless: function(e,nosave)
789  {
790  this._singlePane = !this._singlePane;
791  this._singlePaneBorderless = true;
792 
793  if (this._singlePane)
794  {
795  this._nonSingleLayout = this._root.saveLayout();
796  this.applyLayout( this.singlePaneLayout(this._nonSingleLayout,true) );
797  $(window).trigger("footerhide",true);
798  }
799  else
800  {
801  this.applyLayout( this._nonSingleLayout );
802  $(window).trigger("footerhide",false);
803  }
804 
805  if (!nosave)
806  this.saveLayout();
807 
808  $(window).trigger("singlepane",this._singlePane);
809  $(window).trigger("singlepaneborderless",this._singlePaneBorderless);
810  },
811  */
812 
826  openApplicationLayout: function(event,context,template,metadata)
827  {
828  //console.log(arguments);
829 
830  metadata = metadata || {};
831 
832  var _template = utils.copyObject(template);
833  _template = utils.resolveContextValues(_template,context);
834  //console.log(_template);
835  //console.log(context);
836 
837  var t = null;
838  containerSelector:
839  switch(metadata.where)
840  {
841  case "modal": this.openModal( _template.path, _template.title || utils.fileNameFromPath(_template.path), _template.size, context );
842  return;
843 
844  case "modalForm": this.openModalForm( _template.path, _template.form, _template.title || utils.fileNameFromPath(_template.path), _template.size, context, metadata, _template.accept || "Submit", _template.dismiss || "Close" );
845  return;
846 
847  case "nearest": if (metadata.sourcepane) //search for a sibling that's either a tabs or tabswitcher
848  {
849  //console.log( metadata.sourcepane );
850  //console.trace(_template);
851 
852  var p = metadata.sourcepane._parent;
853  var f,n;
854  while(p)
855  { //console.log(p);
856 
857  if (!metadata.alwaysNewTab)
858  {
859  //console.trace(_template);
860  var k = _template.path || _template.id || _template.name;
861  n = p.find(k)
862  //console.log("searching for: "+k,p,n);
863  if (n)
864  {
865  //a tab with the same name or id or path was found... select it instead of creating a new one
866  if (n._parent && n._parent.selectTabHandler)
867  {
868  n._parent.selectTabHandler(n);
869  //console.log("SAME NAME: "+_template.name);
870  return;
871  }
872  }
873  }
874 
875  f = p.find(null,[tabswitcher,tabs]);// || p.find(null,tabs);
876  //console.log(f);
877  if (f)
878  { t = f;
879  break containerSelector; //you don't see that every day!
880  }
881  p = p._parent;
882  }
883  }
884  //else fall to default case...
885  t = tabswitcher.currentTab;
886  break;
887 
888  case "root":
889  default: t = tabswitcher.currentTab;
890  };
891 
892  if (t)
893  {
894  //console.log(t);
895  // try
896  // {
897  var l = this.buildLayout(_template);
898  if (l)
899  {
900  //console.log("Adding: '"+l._name+"'");
901  //console.log(l);
902 
903  t._children.push( l );
904  t._selected = t._children.length - 1;
905 
906  //console.log( this._root.saveLayout() );
907 
908  // this.applyLayout( this._root.saveLayout() ); //just apply for debugging
909  this.applyLayout( this.saveLayout() ); //actually save
910  }
911  //else
912  // should already have error message from buildLayout
913  // }
914  // catch(e)
915  // {
916  // console.error(e);
917  // }
918  }
919  else
920  { console.error("Don't have a main tabswitcher to attach to... skipping template request.");
921  console.log(arguments);
922  }
923 
924  return false;
925  },
926 
939  openModal: function(path,title,size,context)
940  {
941  size = size || { width:700, height:400 };
942  size.width = Math.max( size.width || 0, 200 ); //ensure the window has size
943  if (size.height !== "expand")
944  size.height = Math.max( size.height || 0, 100 ); //ensure the window has size
945 
946  var resize = size.resize === false ? false : true;
947 
948  var PP = null;
949 
950  var events = {
951  "dismiss": function()
952  {
953  if (PP && PP.destroy)
954  PP.destroy();
955 
956  return false;
957  },
958  };
959 
960  var m = {
961  exec: function(_modal)
962  {
963  var p = new pane( { id: path,
964  path: path,
965  type: "pane",
966  context: context
967  } );
968 
969  PP = p; //in case the window is killed upon reconnection...
970 
971  //var buttons = { "Close": function() { p.destroy(); return _modal.execEvent("dismiss"); } };
972  var buttons = { "Close": function() { return _modal.execEvent("dismiss"); } };
973 
974  var w = modal.createWindow(_modal,size.width || 700,size.height || 400,title,resize,buttons,"Close","Close");
975 
976  w.window.hide(); //the form hasn't loaded yet... don't show it
977 
978  w.content.append( p.createHTML() );
979 
980  w.content.bind("paneloaded", function(evt,p) {
981  //console.log(p._object._help);
982  if (p._object && p._object._help)
983  {
984  //console.log(p._object._help);
985 
986  //var h = $("<span class='ui-icon ui-icon-lightbulb' style='display:inline-block; position:absolute; top:5px; right:5px' />");
987  var h = $("<span class='ui-icon ui-icon-info' style='display:inline-block; position:absolute; top:5px; right:5px' />");
988 
989  h.bind( {
990  mouseenter: function() { h.addClass("ui-state-hover"); return false; },
991  mouseleave: function() { h.removeClass("ui-state-hover"); return false; },
992  click: function() {
993  //console.log(typeof p._object._help);
994  if (typeof p._object._help === "string")
995  modal.alert( p._object._help, false, "Help" );
996  else if (typeof p._object._help === "function")
997  p._object._help();
998  return false;
999  }
1000  } );
1001 
1002  w.header.append(h);
1003  }
1004 
1005  w.window.show(); //now show it
1006 
1007  return false;
1008  } );
1009  }
1010  }
1011 
1012  modal.exec( m, events, true );
1013  },
1014 
1029  openModalForm: function(path,form,title,size,context,metadata,accept,dismiss)
1030  {
1031  var THIS = this;
1032 
1033  size = size || { width:700, height:400 };
1034  size.width = Math.max( size.width || 0, 200 ); //ensure the window has size
1035  if (size.height !== "expand")
1036  size.height = Math.max( size.height || 0, 100 ); //ensure the window has size
1037 
1038  var resize = size.resize === false ? false : true;
1039 
1040  metadata = metadata || {};
1041  accept = accept || "Submit";
1042  dismiss = dismiss || "Close";
1043 
1044  var events = {
1045  "dismiss": function(data) {
1046  //console.trace("dismiss",arguments);
1047  if (data !== "loaderCallback")
1048  if (metadata.dismissCallback)
1049  return metadata.dismissCallback();
1050  return false;
1051  },
1052  "submit": function(values) {
1053  //console.trace("submit",arguments);
1054  //return;
1055  var c = {};
1056  c[path] = values;
1057  switch (metadata.action)
1058  {
1059  case "submit": THIS.pathSubmitCallback( c );
1060  if (metadata.submitCallback)
1061  metadata.submitCallback(values);
1062  break;
1063 
1064  case "create": THIS.pathCreateCallback( c );
1065  if (metadata.createCallback)
1066  metadata.createCallback(values);
1067  else if (metadata.submitCallback)
1068  metadata.submitCallback(values);
1069  break;
1070 
1071  default: console.warn("no metadata.action provided... assuming 'create', and skipping callback",metadata);
1072  THIS.pathCreateCallback( c );
1073  if (metadata.createCallback)
1074  metadata.createCallback(values);
1075  else if (metadata.submitCallback)
1076  metadata.submitCallback(values);
1077  break;
1078  }
1079  },
1080  };
1081 
1082  var m = {
1083  exec: function(_modal)
1084  {
1085  var p = _paneManager.objectFactory( { type:"form" }, loadedCallback);
1086  //console.log(p);
1087 
1088  var buttons = {};
1089 
1090  buttons[accept] = function()
1091  { //console.log("MODAL FORM SUBMIT: "+path);
1092  var valid = p.validate();
1093  if (valid)
1094  return _modal.execEvent("submit",p.getValues());
1095  else
1096  return false;
1097  };
1098 
1099  buttons[dismiss] = function()
1100  { p.destroy();
1101  return _modal.execEvent("dismiss");
1102  };
1103 
1104  var w = modal.createWindow(_modal,size.width || 700,size.height || 400,title,resize,buttons,accept,dismiss,p._help);
1105 
1106  w.content.append( $("<div class='pane'>").append( p.createHTML() ) ); //wrap this in a .pane so the css is consistent with other cases
1107  //w.content.append( p.createHTML() );
1108  p.applyDiff( form );
1109  //w.content.find("input,select").first().focus();
1110  w.content.find("input[type=text]").first().focus();
1111  }
1112  }
1113 
1114  //this will be called if the objectFactory has to load the pane type
1115  function loadedCallback()
1116  {
1117  //console.log("loadedCallback",arguments);
1118  //console.log(mo);
1119  mo.execEvent("dismiss","loaderCallback"); //close the loader window
1120  modal.exec( m, events, true ); //open the newly loaded pane
1121  }
1122 
1123  var mo = modal.exec( m, events, true );
1124  },
1125 
1134  resize: function()
1135  {
1136  if (this._root)
1137  this._root.resize();
1138  },
1139  }
1140 
1147  // Tab Switcher //
1149 
1174  function tabswitcher(layout)
1175  {
1176  layout = layout || {children:[]}; //to allow a default constructor
1177 
1178  this._tid = utils.uuid();
1179  this._name = layout.name || "Tabswitcher";
1180  this._children = this.createChildren(layout.children);
1181  this._icon = layout.icon || null;
1182  this._title = layout.title || null;
1183  this._html = null;
1184  this._menuButton = null;
1185  this._parent = null;
1186  this._activated = false;
1187  this._trigger = layout.trigger;
1188  this._selected = layout.selected || 0;
1189  this._selected = Math.min(this._selected,this._children.length-1);
1190  this._sortable = layout.sortable === false ? false : true;
1191  this._showMenu = layout.showMenu === false ? false : true;
1192  this._tabContextMenus = layout.tabContextMenus === false ? false : true;
1194  this._borderless = layout.borderless || false;
1195 
1196  tabswitcher.currentTab = this; //the assumption (which may be incorrect) is that there is only ever one main tabswitcher. This may need to change at some point!
1197 
1198  var closeIcon = "<span class='paneMenuIcon' class='test test'><svg xmlns='http://www.w3.org/2000/svg' fill='var(--FontColorMain)' viewBox='0 0 14 14'><g><polygon points='11.58 5.5 9.08 2.99 6.75 5.32 4.42 2.99 1.92 5.5 4.25 7.82 1.92 10.15 4.42 12.66 6.75 10.33 9.08 12.66 11.58 10.15 9.25 7.82 11.58 5.5'/></g></svg></span>";
1199 
1200 
1201  if (layout.primary)
1202  _paneManager._primaryCell = this;
1203 
1204  if (!menuManager.getMenu("__tabswitcherSelectMenu_"+this._tid))
1205  {
1206  tabswitcher.tabswitcherSelectMenus.push("__tabswitcherSelectMenu_"+this._tid);
1207  var m = menuManager.createMenu("__tabswitcherSelectMenu_"+this._tid);
1208  //tabs will be dynamically added as created.
1209  }
1210 
1211  if (!menuManager.getMenu("__tabswitcherMenu"))
1212  { var m = menuManager.createMenu("__tabswitcherMenu");
1213  /* m.addItem( { //path
1214  label: "Add Tab",
1215  callback: this.addTabPrompt,
1216  //data
1217  test: true,
1218  icon: "<span class='ui-icon ui-icon-circle-plus' style='display:inline-block; position:relative; top:2px; left:-1px' />",
1219  } );
1220  */
1221  m.addItem( { //path
1222  label: "Close Current Tab",
1223  callback: this.closeCurrentTab,
1224  //data
1225  test: function() { return tabswitcher.currentTab._children.length>0; },
1226  //icon: "<span class='ui-icon ui-icon-circle-close' style='display:inline-block; position:relative; top:2px; left:-1px' />",
1227  // icon: "<span class='paneMenuIcon'>&#10006;</span>",
1228  icon: closeIcon,
1229  } );
1230  /* m.addItem( { //path
1231  label: "Rename Current Tab",
1232  callback: this.renameTab,
1233  //data
1234  test: function() { return tabswitcher.currentTab._children.length>0; },
1235  icon: "<span class='ui-icon ui-icon-tag' style='display:inline-block; position:relative; top:2px; left:-1px' />",
1236  } );
1237  */
1238  m.addItem( { //path
1239  label: "Close All Tabs",
1240  callback: this.closeAllTabs,
1241  //data
1242  test: function() { return tabswitcher.currentTab._children.length>0; },
1243  //icon: "<span class='ui-icon ui-icon-circle-close' style='display:inline-block; position:relative; top:2px; left:-1px' />",
1244  // icon: "<span class='paneMenuIcon'>&#10006;</span>",
1245  icon: closeIcon,
1246  } );
1247  m.addSubMenu( "Select Tab", tabswitcher.currentSelectTabswitcherMenuID, "<span class='paneMenuIcon'></span>" );
1248  }
1249 
1250  if (!menuManager.getMenu("__tabswitcherTabMenu"))
1251  {
1252  var m = menuManager.createMenu("__tabswitcherTabMenu");
1253  /* m.addItem( {
1254  label: "Rename Tab",
1255  //callback: this.renameTab,
1256  callback: function() { console.log(arguments); alert("would rename here!"); },
1257  test: true,
1258  icon: "<span class='ui-icon ui-icon-pencil' style='display:inline-block; position:relative; top:2px; left:-1px' />",
1259  } );
1260  */
1261  m.addItem( { //path
1262  label: "Close Tab",
1263  callback: this.closeTab,
1264  //data
1265  test: true,
1266  //icon: "<span class='ui-icon ui-icon-circle-close' style='display:inline-block; position:relative; top:2px; left:-1px' />",
1267  // icon: "<span class='paneMenuIcon'>&#10006;</span>",
1268  icon: closeIcon,
1269  } );
1270  }
1271  }
1272 
1273  tabswitcher.currentTab = null;
1274  tabswitcher.tabswitcherSelectMenus = [];
1275  tabswitcher.currentSelectTabswitcherMenuID = function() { return "__tabswitcherSelectMenu_"+tabswitcher.currentTab._tid; };
1276  tabswitcher.TAG = "aa";
1277 
1278  tabswitcher.prototype = {
1279 
1289  createHTML: function()
1290  {
1291  if (this._html) return this._html;
1292 
1293  var n = $("<div class='tabswitcher' id='"+this._tid+"'/>");
1294  var t = $("<ul class='tabswitcher-header'/>");
1295  t.css( {
1296  // "background-color": "var(--BG0)",
1297  "box-shadow": "inset 0px -2px 2px var(--ShadowColor)",
1298  "-webkit-box-shadow": "inset 0px -2px 2px var(--ShadowColor)",
1299  } );
1300  t.data("tabswitcherPane",this);
1301  n.append(t);
1302 
1303  //paneMenu button
1304  var b = $("<div class='tabsmenu'>").html("<span>&#9662;</span>");
1305  b.bind("click", this.openMenu.bind(this));
1306  t.append(b);
1307 
1308  if (!this._showMenu) //can't just not create it, because later methods use a child index counter offset
1309  b.hide();
1310 
1311  var m = menuManager.getMenu("__tabswitcherSelectMenu_"+this._tid);
1312 
1313  var c,j;
1314  for (var i=0;i<this._children.length;i++)
1315  {
1316  c = this._children[i];
1317  var cn = c._name.substring(0,30);
1318  cn = "<div class='tabLabel'>" + cn + "</div>";
1319 
1320  //var tt = ((c._icon?"<img src='"+c._icon+"' height='12px' style='position:relative; top:1px; margin-right:2px'/>"+cn:cn)||"[unnamed]");
1321  var tt = cn;
1322  if(c._icon)
1323  {
1324  //console.log(c._icon);
1325  if(c._icon.startsWith("data:image/svg+xml;utf8,"))
1326  {
1327  tt = c._icon.replace("data:image/svg+xml;utf8,","") + cn;
1328  }
1329  else
1330  {
1331  //tt = ((c._icon?"<img src='"+c._icon+"' height='12px' style='position:relative; top:1px; margin-right:2px'/>"+cn:cn)||"[unnamed]");
1332  tt = ((c._icon?"<img src='"+c._icon+"' />"+cn:cn)||"[unnamed]");
1333  }
1334  }
1335 
1336  if (c._layout)
1337  //j = $("<li><"+tabswitcher.TAG+" href='#"+c._tid+"' title='"+c._path+"'>"+(c._layout.icon?"<img src='"+c._layout.icon+"' height='12px' style='position:relative; top:1px; margin-right:-3px'/>":c._name||"[unnamed]")+"</a></li>");
1338  j = $("<li><"+tabswitcher.TAG+" href='#"+c._tid+"'>"+tt+"</a></li>");
1339  else
1340  j = $("<li><"+tabswitcher.TAG+" id='tab_"+c._tid+"' href='#"+c._tid+"'>"+tt+"</a></li>");
1341  j.attr("title",c._title);
1342  j.data("pane",c);
1343  // j.bind( "dblclick", this.renameTab.bind(this) );
1344  t.append( j );
1345  n.append( c.createHTML() );
1346 
1347  m.addItem( {
1348  //path:
1349  label: c._name,
1350  callback: this.selectTabHandler.bind(this,c),
1351  //data:
1352  //test:
1353  icon: c._icon,
1354  //submenu:
1355  } );
1356  }
1357 
1358  this._html = n;
1359  this._menuButton = b;
1360 
1361  return this._html;
1362  },
1363 
1374  createChildren: function(children)
1375  {
1376  var ca = [];
1377  var c,p;
1378 
1379  children = children || []; //extra safety
1380 
1381  for (var i=0; i<children.length; i++)
1382  {
1383  p = null;
1384  c = children[i];
1385 
1386  switch(c.type)
1387  {
1388  case "splitter": p = new splitter(c); break;
1389  case "tabs": p = new tabs(c); break;
1390  case "pane": p = _paneManager.fetchPane(c); break;
1391  default: p = _paneManager.fetchPane(c); break;
1392  }
1393 
1394  if (p)
1395  {
1396  p._parent = this;
1397  ca.push( p );
1398  }
1399  else
1400  console.warn( "Unknown tabswitcher pane type: '"+c.type+"'" );
1401  }
1402 
1403  return ca;
1404  },
1405 
1414  activate: function()
1415  {
1416  for (var i=0;i<this._children.length;i++)
1417  this._children[i].activate();
1418 
1419  if (this._activated) return;
1420 
1421  //initialize the (jQuery) tabs
1422  var TABS = this;
1423  this._html.tabs2( { event: "mousedown", //"pointerdown", //"touchstart click",
1424  active: this._selected,
1425  add: function(event, ui) {
1426  $(tabswitcher.TABS+"[href="+ui.tab.hash+"]").trigger("click"); //select the new tab
1427  },
1428  activate: function(event,ui) {
1429  TABS._selected = TABS._html.tabs2("option","active");
1430  _paneManager.saveLayout();
1431 
1432  //console.log( TABS._html.tabs2("option","active") );
1433 
1434  if ( TABS._children[TABS._selected] )
1435  { var p = _paneManager._currentPane = TABS._children[TABS._selected];
1436  p.resize();
1437  if (p.triggerCurrentPane)
1438  p.triggerCurrentPane();
1439  }
1440  },
1441  } );
1442 
1443  this._html.find( ".ui-tabs2-panel" ).css( {top:"",bottom:""} );
1444  if (this._borderless)
1445  this._html.find( "[aria-hidden=false]" ).css( {top:"0px",bottom:"0px"} );
1446 
1447  //_paneManager.__tabswitcherStartIndex;
1448  if (this._sortable)
1449  this._html.children("ul.ui-tabs2-nav").sortable( {
1450  appendTo: "",
1451  zIndex: "1000",
1452  // connectWith: "ul.ui-tabs2-nav",
1453  items: "li",
1454  helper: "clone",
1455  opacity: 0.92,
1456  scroll: false,
1457  distance: 5,
1458  axis: 'x',
1459  // tolerance: "pointer",
1460  containment: 'parent',
1461  placeholder: "tabs-placeholder",
1462  forcePlaceholderSize: true,
1463  start: function(event,ui)
1464  {
1465  _paneManager.__tabswitcherStartIndex = ui.item.index() - 1; //-1 for the menu button.
1466  },
1467  stop: function(event,ui)
1468  {
1469  var oldIndex = _paneManager.__tabswitcherStartIndex;
1470  var newIndex = ui.item.index() - 1;
1471 
1472  if (oldIndex == newIndex) return;
1473 
1474  var s = TABS._children[TABS._selected];
1475 
1476  //console.log(TABS._children[0]._name+" "+TABS._children[1]._name+" "+TABS._children[2]._name+" "+TABS._children[3]._name);
1477  var item = TABS._children.splice( oldIndex, 1 ); //move out the old item
1478  TABS._children.splice( newIndex, 0, item[0] );
1479  //console.log(TABS._children[0]._name+" "+TABS._children[1]._name+" "+TABS._children[2]._name+" "+TABS._children[3]._name);
1480 
1481  for (TABS._selected=0;TABS._children[TABS._selected]!==s;TABS._selected++); //update _selected
1482  //console.log(TABS._selected);
1484  _paneManager.applyLayout( _paneManager.saveLayout());
1485  },
1486  } );
1487 
1488  this._html.children("ul.ui-tabs2-nav").bind("contextmenu", function(e)
1489  { _paneManager._primaryCell = tabswitcher.currentTab = TABS;
1490  if (preferences.fetch("contextMenus",true))
1491  menuManager.openMenu( "__tabswitcherMenu", e, "B" );
1492  return false;
1493  } );
1494 
1495  if (this._tabContextMenus)
1496  this._html.children("ul.ui-tabs2-nav").children("li").bind("contextmenu", function(e)
1497  {
1498  var t = $(e.currentTarget);
1499  var i = t.index() - 1; //minus one for menu button
1500  tabswitcher.currentTab = TABS._children[i];
1501  if (preferences.fetch("contextMenus",true))
1502  if (!tabswitcher.currentTab._noClose)
1503  menuManager.openMenu( "__tabswitcherTabMenu", e, "B" );
1504  return false;
1505  } );
1506 
1507  this._html.bind( "triggerNavigation", function(e,dir)
1508  {
1509  //console.log("NAV REQUEST 2!");
1510  //console.log(arguments);
1511 
1512  var i = TABS._selected;
1513 
1514  switch (dir)
1515  {
1516  case "previous": i--;
1517  if (i >= 0)
1518  TABS._html.tabs2( "option", "active", i );
1519  else
1520  TABS._html.parent().trigger("triggerNavigation","previous");
1521 
1522  break;
1523 
1524  case "next": i++;
1525  if (i < TABS._children.length)
1526  TABS._html.tabs2( "option", "active", i );
1527  else
1528  TABS._html.parent().trigger("triggerNavigation","next");
1529  break;
1530 
1531  default: console.warn("bad navigation direction!");
1532  console.warn(arguments);
1533  break;
1534  }
1535 
1536  return false;
1537  } );
1538 
1539  this._activated = true;
1540  },
1541 
1548  deactivate: function()
1549  {
1550  //if (!this._activated) return;
1551 
1552  for (var i=0;i<this._children.length;i++)
1553  this._children[i].deactivate();
1554 
1555  //FIXME: deactivate tabs?
1556 
1557  this._activated = false;
1558  },
1559 
1568  triggerCurrentPane: function()
1569  {
1570  if (this._trigger)
1571  {
1572  for (var t in this._trigger)
1573  {
1574  switch(t)
1575  {
1576  case "select": switch(this._trigger[t])
1577  {
1578  case "first": this._html.tabs2( "option", "active", 0 ); break;
1579  case "next": console.log("next tab!"); break;
1580  case "previous": console.log("previous tab!"); break;
1581  break;
1582 
1583  //no default case... let it fall to vfsClient
1584  }
1585  break;
1586  }
1587  }
1588 
1589  //if (this.attached())
1590  this._html.trigger("triggerCurrentPane", this._trigger);
1591  }
1592 
1593  var t = this._children[this._selected];
1594 
1595  if (t.triggerCurrentPane)
1596  { //console.log("tabswitcher tab has trigger... doing it!");
1597  t.triggerCurrentPane();
1598  }
1599  },
1600 
1610  openMenu: function(e)
1611  {
1612  tabswitcher.currentTab = this;
1613 
1614  if (preferences.fetch("contextMenus",true))
1615  menuManager.openMenu( "__tabswitcherMenu", this._menuButton, "B" );
1616 
1617  return false;
1618  },
1619 
1629  addTabPrompt: function(e)
1630  {
1631  var THIS = tabswitcher.currentTab;
1632 
1633  modal.prompt( "Enter a name for the new tab:",
1634  "New Tab",
1635  THIS.addTab.bind(THIS)
1636  );
1637  },
1638 
1649  addTab: function(n,c)
1650  {
1651  c = c || [];
1652 
1653  var t = new tabs( { name: n,
1654  children: c,
1655  } );
1656 
1657  this._children.push( t );
1658  this._selected = this._children.length - 1;
1659  _paneManager.applyLayout( _paneManager.saveLayout() );
1660  },
1661 
1670  closeCurrentTab:function()
1671  {
1672  var ts = tabswitcher.currentTab;
1673  var t = ts._children[ts._selected];
1674  //console.log(ts._selected,t);
1675  t.destroy();
1676  _paneManager.applyLayout( _paneManager.saveLayout() );
1677  },
1678 
1687  closeTab: function()
1688  {
1689  var t = tabswitcher.currentTab;
1690  t.destroy();
1691  _paneManager.applyLayout( _paneManager.saveLayout() );
1692  },
1693 
1700  closeAllTabs: function()
1701  {
1702  var THIS = tabswitcher.currentTab;
1703 
1704  THIS._children = [];
1705  _paneManager.applyLayout( _paneManager.saveLayout() );
1706 
1707  /*
1708  while (THIS._children.length)
1709  THIS._children[0].destroy();
1710 
1711  _paneManager.applyLayout( _paneManager.saveLayout() );
1712  */
1713  },
1714 
1725  removeTab: function(tab)
1726  {
1727  var i = this._children.indexOf(tab);
1728  if (i>-1)
1729  {
1730  this._children.splice(i,1);
1731  this._selected = Math.min(this._selected,this._children.length-1);
1732  }
1733  else
1734  console.warn("Could not find tab!",tab);
1735  },
1736 
1747  selectTabHandler: function(p,e)
1748  {
1749  var i = this._children.indexOf(p);
1750 
1751  this._html.tabs2( "option", "active", i );
1752  },
1753 
1763  renameTab: function(e)
1764  {
1765  var THIS = (this instanceof tabswitcher) ? this : tabswitcher.currentTab;
1766  var c = THIS._children[THIS._selected];
1767  modal.prompt( "Enter a new name for this tab:",
1768  c._name,
1769  function(name) //accept function
1770  { c._name = name;
1771  THIS._html.find("#tab_"+c._id).html(name);
1772  _paneManager.applyLayout( _paneManager.saveLayout() );
1773  },
1774  function() //dismiss function
1775  {
1776  }
1777  );
1778  },
1779 
1798  saveLayout: function()
1799  {
1800  var ch = [],c;
1801  for (var i=0;i<this._children.length;i++)
1802  { c = this._children[i];
1803  ch.push( c.saveLayout() );
1804  }
1805 
1806  var l = { name: this._name,
1807  type: "tabswitcher",
1808  selected: this._selected,
1809  // showMenu: this._showMenu,
1810  // tabContextMenus:this._tabContextMenus,
1811  children: ch,
1812  };
1813 
1814  if (!this._showMenu) l.showMenu = false;
1815  if (!this._tabContextMenus) l.tabContextMenus = false;
1816  if (this._title) l.title = this._title;
1817 
1818  //if (_paneManager._primaryCell == this)
1819  // l.primary = true;
1820 
1821  if (this._trigger) l.trigger = this._trigger;
1822  if (!this._sortable) l.sortable = false;
1823 
1824  return l;
1825  },
1826 
1838  find: function(p,type)
1839  {
1840  //if ( ( (!type) || (this instanceof type) ) && ( (!p) || (p==this) || (p==this._name) || (p==this._id) ) )
1841  if ( ( (!type) || (utils.isInstanceOf(this,type)) ) && ( (!p) || (p==this) || (p==this._name) || (p==this._id) ) )
1842  return this;
1843 
1844  for(var i=0;i<this._children.length;i++)
1845  {
1846  var f = this._children[i].find(p,type);
1847 
1848  if (f) return f;
1849  }
1850 
1851  return null;
1852  },
1862  resize: function()
1863  {
1864  for (var i=0;i<this._children.length;i++)
1865  this._children[i].resize();
1866  },
1867 
1877  attach: function()
1878  {
1879  for (var i=0;i<this._children.length;i++)
1880  this._children[i].attach();
1881  },
1882 
1891  detach: function()
1892  {
1893  for (var i=0;i<this._children.length;i++)
1894  this._children[i].detach();
1895  },
1896 
1905  destroy: function()
1906  {
1907  for (var i=0;i<this._children.length;i++)
1908  this._children[i].destroy();
1909 
1910  this._html.detach();
1911  this._html = null;
1912  this._children = [];
1913  },
1914 
1926  replaceChild: function(oldChild, newChild)
1927  {
1928  var i = this._children.indexOf(oldChild);
1929 
1930  if (i>-1)
1931  {
1932  this._children[i] = newChild;
1933  newChild._parent = this;
1934  return true;
1935  }
1936 
1937  return false;
1938  },
1939  /*
1940  containsCellWithName: function(n)
1941  {
1942  },
1943  */
1944 
1955  containsTabsOtherThan: function(t)
1956  {
1957  return false;
1958  },
1959  }
1960 
1961 
1962 
1964  // Splitter //
1966 
2000  function splitter(layout)
2001  {
2002  layout = layout || {children:[]}; //to allow a default constructor
2003 
2004  this._id = layout.id || utils.uuid();
2005  this._tid = utils.uuid();
2006  this._name = layout.name || "(unnamed)";
2007  this._icon = layout.icon || null;
2008  this._title = layout.title || null;
2009  this._stacking = layout.stacking || "horizontal";
2010  this._showHandles = (layout.handles === false) ? false : true;
2011  this._sizes = layout.sizes || (function(){ var s = 1/(Math.min(layout.children.length,1)); return Array.apply(null, Array(5)).map(Number.prototype.valueOf,s); })();
2012  this._classes = layout.classes || [];
2013  this._fixedSize = layout.fixedSize || false; //whether or not the splitter is resizable
2014  this._children = this.createChildren(layout.children);
2015  this._html = null;
2016  this._activated = false;
2017  this._jqs = [];
2018  }
2019 
2020  splitter.prototype = {
2021 
2031  createHTML: function()
2032  {
2033  if (this._html) return this._html;
2034 
2035  var p = $("<div class='pane' id='"+this._tid+"'/>");
2036  var n = $("<div class='splitter'/>");
2037  var r,h;
2038 
2039  switch(this._stacking)
2040  {
2041  case "horizontal": r = n.append("<div class='ui-row'/>").find(".ui-row");
2042  for (var i=0;i<this._children.length;i++)
2043  {
2044  if (i!==0) r.append( "<div class='ui-cell splitterHandle splitterHandleH' style='cursor:col-resize;'/>" );
2045 
2046  h = $("<div class='ui-cell'/>").append(this._children[i].createHTML());
2047  if (this._sizes[i])
2048  h.css( { width:this._sizes[i] } );
2049  if(this._classes[i])
2050  h.addClass( this._classes[i] );
2051  r.append( h );
2052  this._jqs.push(h);
2053  }
2054  break;
2055 
2056  case "vertical": for (var i=0;i<this._children.length;i++)
2057  {
2058  if (i!==0) n.append( "<div class='ui-row' style='height:0px;'><div class='ui-cell splitterHandle splitterHandleV' style='cursor:row-resize;'/></div>" );
2059 
2060  r = $("<div class='ui-cell'/>").append(this._children[i].createHTML());
2061  if (this._sizes[i])
2062  r.css( { height:this._sizes[i] } );
2063  if (this._classes[i])
2064  r.addClass( this._classes[i] );
2065  n.append( $("<div class='ui-row'/>").append(r) );
2066  this._jqs.push(r);
2067  }
2068  break;
2069 
2070  default: console.log("Bad split stacking: '"+this._stacking+"'");
2071  break;
2072  }
2073 
2074  //if(!this._showHandles && r)
2075  // r.children(".splitterHandle").hide();
2076 
2077  p.append(n);
2078  this._html = p;
2079  //this._html = n;
2080 
2081  return this._html;
2082  },
2083 
2094  createChildren: function(children)
2095  {
2096  var ca = [];
2097  var c,p;
2098 
2099  children = children || []; //extra safety
2100 
2101  for (var i=0; i<children.length; i++)
2102  {
2103  p = null;
2104  c = children[i];
2105 
2106  switch(c.type)
2107  {
2108  case "tabswitcher": p = new tabswitcher(c); break;
2109  case "splitter": p = new splitter(c); break;
2110  case "tabs": p = new tabs(c); break;
2111  case "pane": p = _paneManager.fetchPane(c); break;
2112  default: p = _paneManager.fetchPane(c); break;
2113  }
2114 
2115  if (p)
2116  {
2117  p._parent = this;
2118  ca.push( p );
2119  }
2120  else
2121  console.warn( "Unknown splitter pane type: '"+c.type+"'" );
2122  }
2123 
2124  return ca;
2125  },
2126 
2138  replaceChild: function(oldChild, newChild)
2139  {
2140  var i = this._children.indexOf(oldChild);
2142  if (i>-1)
2143  {
2144  this._children[i] = newChild;
2145  return true;
2146  }
2147 
2148  return false;
2149  },
2150 
2159  activate: function()
2160  {
2161  for (var i=0;i<this._children.length;i++)
2162  this._children[i].activate();
2163 
2164  if (this._activated) return;
2165 
2166  if (!this._fixedSize)
2167  this._html.children(".splitter").children(".ui-row").children(".splitterHandle").bind("mousedown", {splitter:this}, this.handleFunction);
2168  else
2169  this._html.children(".splitter").children(".ui-row").children(".splitterHandle").addClass("splitterHandle-noresize");
2170 
2171  if (!this._showHandles)
2172  // this._html.children(".splitter").children(".ui-row").children(".splitterHandle").hide();//css("visibility","hidden");
2173  this._html.children(".splitter").children(".ui-row").children(".splitterHandle").css("display","none");
2174 
2175  this._activated = true;
2176  },
2177 
2184  deactivate: function()
2185  {
2186  //if (!this._activated) return;
2187 
2188  for (var i=0;i<this._children.length;i++)
2189  this._children[i].deactivate();
2190 
2191  //FIXME: deactivate tabs?
2192 
2193  this._activated = false;
2194  },
2195 
2206  handleFunction: function(e)
2207  {
2208  var SPLITTER = e.data.splitter;
2209  var THIS = this;
2210 
2211  $(THIS).addClass("splitterHandleHighlight");
2212 
2213  var index = (SPLITTER._stacking === "horizontal") ? $(this).index() : $(this).parent().index();
2214  index = Math.ceil((index-1)/2);
2215 
2216  var j1 = SPLITTER._jqs[index+0];
2217  var j2 = SPLITTER._jqs[index+1];
2218 
2219  var p1 = SPLITTER._sizes[index+0];
2220  var p2 = SPLITTER._sizes[index+1];
2221 
2222  //console.log(index);
2223 
2224  var d = 0;
2225  var s1=p1,s2=p2;
2226 
2227  var m1 = p1 ? ((p1.indexOf("%")>-1)?"P":"X") : "N";
2228  var m2 = p2 ? ((p2.indexOf("%")>-1)?"P":"X") : "N";
2229  var m = m1+m2;
2230 
2231  p1 = p1 ? parseFloat(p1) : 0;
2232  p2 = p2 ? parseFloat(p2) : 0;
2233 
2234  var total,positionParam,cssParam;
2235 
2236  switch(SPLITTER._stacking)
2237  { case "horizontal": total = SPLITTER._html.width();
2238  if (m1==="P") p1 = j1.width();
2239  if (m2==="P") p2 = j2.width();
2240  if (m==="NN") p1 = j1.width();
2241  positionParam = "pageX";
2242  cssParam = "width";
2243  $("body").css("cursor","col-resize");
2244  break;
2245 
2246  case "vertical": total = SPLITTER._html.height();
2247  if (m1==="P") p1 = j1.height();
2248  if (m2==="P") p2 = j2.height();
2249  if (m==="NN") p1 = j1.height();
2250  positionParam = "pageY";
2251  cssParam = "height";
2252  $("body").css("cursor","row-resize");
2253  break;
2254  }
2255 
2256  var s = e[positionParam];
2257 
2258  //console.log("mode: "+m+" "+p1+" "+p2);
2259 
2260  var mm = function(e)
2261  {
2262  d = s-e[positionParam];
2263 
2264  //console.log(d);
2265  //console.log(e);
2266  //console.log(p2+d);
2267 
2268  switch (m)
2269  {
2270  case "PP": if (p1-d>0 && p2+d>0)
2271  { s1 = ((p1-d)/total).toFixed(4)*100+"%";
2272  s2 = ((p2+d)/total).toFixed(4)*100+"%";
2273  j1.css( cssParam, s1 );
2274  j2.css( cssParam, s2 );
2275  } break;
2277  case "PX": if (p1-d>0 && p2+d>0)
2278  { s1 = ((p1-d)/total).toFixed(4)*100+"%";
2279  s2 = (p2+d)+"px";
2280  j1.css( cssParam, s1 );
2281  j2.css( cssParam, s2 );
2282  } break;
2283 
2284  case "NN":
2285  case "PN": if (p1-d>0)
2286  { s1 = ((p1-d)/total).toFixed(4)*100+"%";
2287  j1.css( cssParam, s1 );
2288  } break;
2289 
2290  case "XP": if (p1-d>0 && p2+d>0)
2291  { s1 = (p1-d)+"px";
2292  s2 = ((p2+d)/total).toFixed(4)*100+"%";
2293  j1.css( cssParam, s1 );
2294  j2.css( cssParam, s2 );
2295  } break;
2296 
2297  case "XX": if (p2+d>0 && p1-d>0)
2298  { s1 = (p1-d)+"px";
2299  s2 = (p2+d)+"px";
2300  j1.css( cssParam, s1 );
2301  j2.css( cssParam, s2 );
2302  } break;
2303 
2304  case "XN": if (p1-d > 0)
2305  { s1 = (p1-d)+"px";
2306  j1.css( cssParam, s1 );
2307  } break;
2308 
2309  case "NP": if (p2+d > 0)
2310  { s2 = ((p2+d)/total).toFixed(4)*100+"%";
2311  j2.css( cssParam, s2 );
2312  } break;
2313 
2314  case "NX": if (p2+d > 0)
2315  { s2 = (p2+d)+"px";
2316  j2.css( cssParam, s2 );
2317  } break;
2318  }
2319 
2320  SPLITTER.resize();
2321 
2322  e.preventDefault();
2323  e.stopPropagation();
2324  return false;
2325  };
2326 
2327  var mu = function(e)
2328  {
2329  //SPLITTER._html.unbind("mousemove");
2330  //SPLITTER._html.unbind("mouseup");
2331  $('body').unbind("mousemove",mm);
2332  $('body').unbind("mouseup mouseleave",mu);
2333  $('body').children(".splitterMask").remove();
2334 
2335  switch (m)
2336  {
2337  case "PP":
2338  case "PX":
2339  case "XP":
2340  case "XX": SPLITTER._sizes[index+0] = s1;
2341  SPLITTER._sizes[index+1] = s2; break;
2342 
2343  case "NN":
2344  case "PN":
2345  case "XN": SPLITTER._sizes[index+0] = s1; break;
2346 
2347  case "NP":
2348  case "NX": SPLITTER._sizes[index+1] = s2; break;
2349  }
2350 
2351  $(THIS).removeClass("splitterHandleHighlight");
2352 
2353  $("body").css("cursor","");
2354 
2355  _paneManager.saveLayout();
2356 
2357  e.preventDefault();
2358  e.stopPropagation();
2359  return false;
2360  };
2361 
2362  $('body').append("<div class='splitterMask'>");
2363  $('body').bind("mousemove",mm).bind("mouseup mouseleave",mu);
2364 
2365  e.preventDefault();
2366  e.stopPropagation();
2367  return false;
2368  },
2369 
2379  saveLayout: function()
2380  {
2381  var ch = [],c;
2382  for (var i=0;i<this._children.length;i++)
2383  { c = this._children[i];
2384  ch.push( c.saveLayout() );
2385  }
2386 
2387  //if (this._sizes.length !== this._children.length)
2388  // this._sizes.length = this._children.length;
2389 
2390  var l = { name: this._name,
2391  id: this._id,
2392  type: "splitter",
2393  stacking: this._stacking,
2394  sizes: this._sizes,
2395  children: ch,
2396  };
2397 
2398  if (this._classes.length) l.classes = this._classes;
2399  if (this._icon) l.icon = this._icon;
2400  if (this._title) l.title = this._title;
2401  if (this._showHandles === false) l.handles = false;
2402  if (this._fixedSize) l.fixedSize = this._fixedSize;
2403 
2404  return l;
2405  },
2406 
2418  find: function(p,type)
2419  {
2420  //console.log("splitter find: ",this._name,arguments);
2421 
2422  //if ( ( (!type) || (this instanceof type) ) && ( (!p) || (p==this) || (p==this._name) || (p==this._id) ) )
2423  if ( ( (!type) || (utils.isInstanceOf(this,type)) ) && ( (!p) || (p===this) || (p===this._name) || (p===this._id) ) )
2424  return this;
2425 
2426  for(var i=0;i<this._children.length;i++)
2427  {
2428  var f = this._children[i].find(p,type);
2429 
2430  if (f) return f;
2431  }
2432 
2433  return null;
2434  },
2435 
2444  resize: function()
2445  {
2446  for (var i=0;i<this._children.length;i++)
2447  this._children[i].resize();
2448  },
2449 
2459  attach: function()
2460  {
2461  for (var i=0;i<this._children.length;i++)
2462  this._children[i].attach();
2463  },
2464 
2473  detach: function()
2474  {
2475  for (var i=0;i<this._children.length;i++)
2476  this._children[i].detach();
2477  },
2478 
2487  destroy: function()
2488  {
2489  for (var i=0;i<this._children.length;i++)
2490  { this._children[i]._parent = null;
2491  this._children[i].destroy();
2492  }
2493  this._children = [];
2494 
2495  if (this._parent instanceof tabs || this._parent instanceof tabswitcher)
2496  this._parent.removeTab(this);
2497 
2498  if (this._html)
2499  this._html.detach();
2500  this._html = null;
2501  this._jqs = null;
2502  },
2503  /*
2504  containsCellWithName: function(n)
2505  {
2506  if (this._name == n) return true;
2507 
2508  for(var c=0; c<this._children.length; c++)
2509  if (this._children[c].containsCellWithName(n))
2510  return true;
2511 
2512  return false;
2513  },
2514  */
2515 
2526  containsTabsOtherThan: function(t)
2527  {
2528  var c;
2529  for (var i=0;i<this._children.length;i++)
2530  { c = this._children[i];
2531  if ((c instanceof tabs) && (c!==t))
2532  return true;
2533  }
2534 
2535  return false;
2536  },
2537 
2548  mergeOut: function(child)
2549  {
2550  var i = this._children.indexOf(child);
2551  var l = this._children.length;
2552 
2553  if (i>-1 && l>1)
2554  {
2555  var t = (i<l-1) ? this._children[i+1] : this._children[i-1] ; //the target child
2556 
2557  if (!(t instanceof tabs))
2558  { t = this.containsTabsOtherThan(child);
2559  if (!t) return false;
2560  }
2561 
2562  var c = this._children.splice(i,1);
2563 
2564  if (child._children)
2565  t._children = t._children.concat(child._children);
2566 
2567  if (!t._name)
2568  t._name = this._name;
2569 
2570  if (_paneManager._primaryCell === child)
2571  _paneManager._primaryCell = t;
2572 
2573  //remove this splitter if it's not actually splitting anymore!
2574  if (this._children.length === 1)
2575  {
2576  if (this._parent)
2577  {
2578  if ( this._parent.replaceChild(this,t) )
2579  {
2580  this._children = [];
2581  this._parent = null;
2582  this.destroy();
2583  return true;
2584  }
2585 
2586  return false;
2587  }
2588  else
2589  _paneManager._root = t;
2590  }
2591 
2592  return true;
2593  }
2594 
2595  /*
2596  if (l === 1)
2597  {
2598  this.destroy();
2599  _paneManager.applyLayout( _paneManager.saveLayout() );
2600 
2601  return true;
2602  }
2603  */
2605  return false;
2606  },
2607  }
2608 
2609 
2610 
2612  // Tabs //
2614 
2642  function tabs(layout)
2643  {
2644  layout = layout || {children:[]}; //to allow for default constructor
2645 
2646  this._id = layout.id || utils.uuid();
2647  this._tid = layout.uuid || utils.uuid();
2648  this._name = layout.name;
2649  this._icon = layout.icon || null;
2650  this._title = layout.title || null;
2651  this._children = this.createChildren(layout.children);
2652  this._html = null;
2653  this._menuButton = null;
2654  this._parent = null;
2655  this._activated = false;
2656  this._trigger = layout.trigger;
2657  this._selected = layout.selected || 0;
2658  this._selected = Math.min(this._selected,this._children.length-1);
2659  this._layout = layout;
2660  this._borderless = layout.borderless || false;
2661  this._sortable = layout.sortable === false ? false : true;
2662  this._showMenu = layout.showMenu === false ? false : true;
2663  this._tabContextMenus = layout.tabContextMenus === false ? false : true;
2664 
2665  //this._additionalMenus = layout.additionalMenus || {};
2666  //this._additionalMenusObjects = {};
2667  //this._additionalMenusSpacer = null;
2668 
2669 
2670  var closeIcon = "<span class='paneMenuIcon'><svg xmlns='http://www.w3.org/2000/svg' fill='var(--FontColorMain)' viewBox='0 0 14 14'><g><polygon points='11.58 5.5 9.08 2.99 6.75 5.32 4.42 2.99 1.92 5.5 4.25 7.82 1.92 10.15 4.42 12.66 6.75 10.33 9.08 12.66 11.58 10.15 9.25 7.82 11.58 5.5'/></g></svg></span>";
2671 
2672  if (layout.primary)
2673  _paneManager._primaryCell = this;
2674 
2675  if (!menuManager.getMenu("__tabsSelectMenu_"+this._id))
2676  {
2677  tabs.tabsSelectMenus.push("__tabsSelectMenu_"+this._id);
2678  var m = menuManager.createMenu("__tabsSelectMenu_"+this._id);
2679  //tabs will be dynamically added as created.
2680  }
2681 
2682  /*
2683  console.log(this._additionalMenus);
2684  if (Object.keys(this._additionalMenus).length)
2685  {
2686  //m.addItem("spacer");
2687  var mn = this._additionalMenus;
2688  for (var i in mn)
2689  {
2690  var pp = mn[i];
2691  var menuname = "__tabsAdditionalMenu_"+pp;
2692  var mm = menuManager.createMenu(menuname, pp, true);
2693  var ff = pp;
2694  //m.addSubMenu( ff, menuname, "<span class='paneMenuIcon'></span>" );
2695  this._additionalMenusObjects[i] = mm;
2696  }
2697  }
2698  */
2699 
2700  if (!menuManager.getMenu("__tabsMenu"))
2701  {
2702  let m = menuManager.createMenu("__tabsMenu");
2703  m.addItem( { //path
2704  label: "Split Horizontally",
2705  callback: this.splitHorizontal,
2706  //data
2707  test: function() { return !_paneManager._singlePane; },
2708  //icon: "<img src='"+require.toUrl("./images/icons/splitHorizontal.gif")+"' style='position:relative; top:1px;' width=14 height=14 />",
2709  // icon: "<span class='paneMenuIcon'><img src='"+splitHorizontalImg+"' style='position:relative; top:1px;' width='14' height='14' /></span>",
2710  icon: "<span class='paneMenuIcon'><svg fill='var(--FontColorMain)' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14'><g><rect style='opacity:0.75' x='1.04' y='1' width='5.96' height='11.98'/><rect style='opacity:0.25' x='7' y='1' width='5.96' height='11.98'/></g><path d='M0,0V14H14V0ZM13,7v6H1V1H13Z'/></svg></span>",
2711  } );
2712  m.addItem( { //path
2713  label: "Split Vertically",
2714  callback: this.splitVertical,
2715  //data
2716  test: function() { return !_paneManager._singlePane; },
2717  //icon: "<img src='"+require.toUrl("./images/icons/splitVertical.gif")+"' style='position:relative; top:1px;' width=14 height=14 />",
2718  // icon: "<span class='paneMenuIcon'><img src='"+splitVerticalImg+"' style='position:relative; top:1px;' width='14' height='14' /></span>",
2719  icon: "<span class='paneMenuIcon'><svg fill='var(--FontColorMain)' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14'><g><rect style='opacity:0.75' x='4.02' y='-1.98' width='5.96' height='11.98' transform='translate(11.01 -2.99) rotate(90)'/><rect style='opacity:0.25' x='4.02' y='3.98' width='5.96' height='11.98' transform='translate(16.97 2.97) rotate(90)'/></g><path d='M0,0V14H14V0ZM13,7v6H1V1H13Z'/></svg></span>",
2720  } );
2721  m.addItem( { //path
2722  label: "Unsplit Pane",
2723  callback: this.mergeSplitter,
2724  //data
2725  test: this.mergeable,
2726  //icon: "<img src='"+require.toUrl("./images/icons/mergeSplitter.gif")+"' style='position:relative; top:1px;' width=14 height=14 />",
2727  // icon: "<span class='paneMenuIcon'><img src='"+mergeSplitterImg+"' style='position:relative; top:1px;' width='14' height='14' /></span>",
2728  icon: "<span class='paneMenuIcon'><svg fill='var(--FontColorMain)' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14'></defs><g><rect style='opacity:0.75' x='1.04' y='1' width='5.96' height='5.96'/><rect style='opacity:0.5' x='1.04' y='6.96' width='11.92' height='6'/><rect style='opacity:0.25' x='7' y='1' width='5.96' height='5.96'/></g><path d='M0,0V14H14V0ZM13,7v6H1V1H13Z'/></svg></span>",
2729  } );
2730 
2731  m.addItem("spacer");
2732 
2733  m.addItem( { //path
2734  label: "Close Current Tab",
2735  callback: this.closeCurrentTab,
2736  //data
2737  test: function() { return tabs.currentTab._children.length>0; },
2738  //icon: "<span class='ui-icon ui-icon-circle-close' style='display:inline-block; position:relative; top:2px; left:-1px' />",
2739  // icon: "<span class='paneMenuIcon'>&#10006;</span>",
2740  icon: closeIcon,
2741  } );
2742  m.addItem( { //path
2743  label: "Close All Tabs",
2744  callback: this.closeAllTabs,
2745  //data
2746  test: function() { return tabs.currentTab._children.length>0; },
2747  //icon: "<span class='ui-icon ui-icon-circle-close' style='display:inline-block; position:relative; top:2px; left:-1px' />",
2748  // icon: "<span class='paneMenuIcon'>&#10006;</span>",
2749  icon: closeIcon,
2750  } );
2751 
2752 
2753  /*
2754  m.addItem( {
2755  label: "Create Pane from Path",
2756  callback: this.createPaneByPath.bind(this),
2757  //data
2758  //test
2759  icon: "<span class='ui-icon ui-icon-circle-plus' style='display:inline-block; position:relative; top:2px; left:-1px' />",
2760  } );
2761  */
2762 
2763  //m.addItem( "spacer" );
2764  m.addSubMenu( "Select Tab", tabs.currentSelectTabMenuID, "<span class='paneMenuIcon'></span>" );
2765  }
2766 
2767  if (!menuManager.getMenu("__tabOptionsMenu"))
2768  {
2769  let m = menuManager.createMenu("__tabOptionsMenu");
2770  /* m.addItem( {
2771  label: "Rename Tab",
2772  //callback: this.renameTab,
2773  callback: function() { console.log(arguments); alert("would rename here!"); },
2774  test: true,
2775  icon: "<span class='ui-icon ui-icon-pencil' style='display:inline-block; position:relative; top:2px; left:-1px' />",
2776  } );
2777  */
2778  m.addItem( { //path
2779  label: "Close Tab",
2780  callback: this.closeTab,
2781  //data
2782  test: true,
2783  //icon: "<span class='ui-icon ui-icon-circle-close' style='display:inline-block; position:relative; top:2px; left:-1px' />",
2784  // icon: "<span class='paneMenuIcon'>&#10006;</span>",
2785  icon: closeIcon,
2786  } );
2787  m.addItem( {
2788  label: "Help",
2789  callback: this.openHelp.bind(this),
2790  test: this.hasHelp.bind(this),
2791  //icon: "<span class='ui-icon ui-icon-lightbulb' style='display:inline-block; position:relative; top:2px; left:-1px' />",
2792  //icon: "<span class='paneMenuIcon'>&#128161;</span>",
2793  //icon: "<span class='paneMenuIcon'>&#x24d8;</span>",
2794  icon: "<span class='paneMenuIcon'>?</span>",
2795  } );
2796  }
2797  }
2798 
2799  tabs.currentTab = null;
2800  tabs.tabsSelectMenus = [];
2801  tabs.currentSelectTabMenuID = function() { return "__tabsSelectMenu_"+tabs.currentTab._id; };
2802  tabs.TAG = "aa";
2803 
2804  tabs.prototype = {
2805 
2815  createHTML: function()
2816  {
2817  if (this._html) return this._html;
2818 
2819  var n = $("<div class='tabs' id='"+this._tid+"'/>");
2820  var t = $("<ul/>");
2821 
2822  t.data("tabsPane",this);
2823 
2824  n.append(t);
2825 
2826  //pane button
2827  var b = $("<div class='tabsmenu'>").html("<span>&#9662;</span>");
2828  b.bind("click", this.openMenu.bind(this));
2829  t.append(b);
2830 
2831  if (!this._showMenu) //can't just not create it, because later methods use a child index counter offset
2832  b.hide();
2833 
2834  var m = menuManager.getMenu("__tabsSelectMenu_"+this._id);
2835 
2836  var c,j;
2837  for (var i=0;i<this._children.length;i++)
2838  {
2839  c = this._children[i];
2840  var cn = c._name.substring(0,30);
2841  cn = "<div class='tabLabel'>" + cn + "</div>";
2842 
2843  //console.log(c);
2844  //j = $("<li><"+tabs.TAG+" href='#"+c._tid+"' title='"+c._path+"'>"+(c._layout&&c._layout.icon?"<img src='"+c._layout.icon+"' height='12px' style='position:relative; top:1px; margin-right:-3px'/>":c._name||"[unnamed]")+"</a></li>");
2845  var tt = cn;
2846 
2847  if(c._icon)
2848  {
2849  //console.log(c._icon);
2850  //data:image/svg+xml;utf8,
2851  if(c._icon.startsWith("data:image/svg+xml;utf8,"))
2852  {
2853  tt = c._icon.replace("data:image/svg+xml;utf8,","") + cn;
2854  }
2855  else
2856  {
2857  //tt = ((c._icon?"<img src='"+c._icon+"' height='12px' style='position:relative; top:1px; margin-right:2px'/>"+cn:cn)||"[unnamed]");
2858  tt = ((c._icon?"<img src='"+c._icon+"' />"+cn:cn)||"[unnamed]");
2859  }
2860  }
2861 
2862 
2863  j = $("<li><"+tabs.TAG+" href='#"+c._tid+"'>"+tt+"</a></li>");
2864  j.attr("title",c._title);
2865  j.data("pane",c);
2866  t.append( j );
2867  n.append( c.createHTML() );
2868 
2869  m.addItem( {
2870  //path:
2871  label: c._name,
2872  callback: this.selectTabHandler.bind(this,c),
2873  //data:
2874  //test:
2875  icon: c._icon,
2876  //submenu:
2877  } );
2878  }
2879 
2880  this._html = n;
2881  this._menuButton = b;
2882 
2883  return this._html;
2884  },
2885 
2896  createChildren: function(children)
2897  {
2898  var ca = [];
2899  var c,p,n;
2900 
2901  children = children || []; //extra safety
2902 
2903  for (var i=0; i<children.length; i++)
2904  {
2905  p = null;
2906  c = children[i];
2907 
2908  switch(c.type)
2909  {
2910  case "splitter": p = new splitter(c); break;
2911  case "tabs": p = new tabs(c); break;
2912  case "pane":
2913  default: p = _paneManager.fetchPane(c); break;
2914 
2915  // default: console.warn( "Unknown tab pane type: '"+c.type+"'" );
2916  // break;
2917  }
2918 
2919  if (p)
2920  { p._parent = this;
2921  ca.push( p );
2922  }
2923  else
2924  console.error(p);
2925  }
2926 
2927  return ca;
2928  },
2929 
2930  /*
2931  appendChild: function(pane,select)
2932  {
2933  this._children.push(pane);
2934  if (select)
2935  this._selected = this._children.length-1;
2936  },
2937  */
2938 
2949  appendTab: function(pane) //used when dynamically adding tabs from the UI, or adding stray tabs when the layout changes
2950  {
2951  //console.log(pane);
2952 
2953  if (pane._parent)
2954  console.error("Appended pane should not have a parent... something's wrong.");
2955 
2956  pane._parent = this;
2957  this._children.push( pane );
2958 
2959  var tt = cn;
2960  if(c._icon)
2961  {
2962  //console.log(c._icon);
2963  if(c._icon.startsWith("data:image/svg+xml;utf8,"))
2964  {
2965  tt = c._icon.replace("data:image/svg+xml;utf8,","") + cn;
2966 
2967  }
2968  else
2969  {
2970  //tt = ((c._icon?"<img src='"+c._icon+"' height='12px' style='position:relative; top:1px; margin-right:2px'/>"+cn:cn)||"[unnamed]");
2971  tt = ((c._icon?"<img src='"+c._icon+"' />"+cn:cn)||"[unnamed]");
2972  }
2973  }
2974 
2975  var j = $( "<li><"+tabs.TAG+" href='#"+pane._tid+"' title='"+pane._path+"'>"+tt+"</a></li>" );
2976  j.appendTo( this._html.find(".ui-tabs2-nav"));
2977  j.attr("title",pane._title);
2978  j.data("pane",pane);
2979  this._html.append( pane.createHTML() );
2980  this._html.tabs2("refresh");
2981 
2982  //this._html.find(tabs.TAG+"[href='#"+pane._name+"']").parent().data("pane",pane); //for drag/drop
2983 
2984  var m = menuManager.getMenu("__tabsSelectMenu_"+this._id);
2985 
2986  m.addItem( {
2987  label: pane._name,
2988  callback: this.selectTabHandler.bind(this,pane),
2989  } );
2990 
2991  pane.activate();
2992  pane.resize();
2993 
2994  return pane;
2995  },
2996 
3007  removeTab: function(tab,moving)
3008  {
3009  var i = this._children.indexOf(tab);
3010  if (i>-1)
3011  {
3012  if (!moving) //move is called when panes are being dropped from other tabs... ie, don't destroy anything.
3013  { //this._html.tabs2("remove",i);
3014 
3015  this._html.find(".ui-tabs2-nav").children()[i].remove();
3016  this._html.children()[i+1].remove();
3017  this._html.tabs2("refresh");
3018  }
3019 
3020  this._children.splice(i,1);
3021 
3022  var s = this._html.tabs2( 'option', 'active' );
3023 
3024  this._selected = Math.max(s,0);
3025 
3026  var m = menuManager.getMenu("__tabsSelectMenu_"+this._id);
3027 
3028  m.removeItem( i );
3029  }
3030  else
3031  console.warn("TABS Could not find tab!",tab);
3032  },
3033 
3044  receiveTab: function(tab,index)
3045  {
3046  index = Math.min(this._children.length,index);
3047  index = Math.max(0,index);
3048 
3049  if (index >= 0 && index<=this._children.length)
3050  {
3051  //console.log(tab._parent);
3052  //tab.detach();
3053  if (tab._parent)
3054  tab._parent.removeTab(tab,true);
3055  tab._parent = this;
3056  this._children.splice(index,0,tab);
3057  this._selected = index;
3058  _paneManager.applyLayout( _paneManager.saveLayout() );
3059  //_paneManager.saveLayout();
3060  }
3061  else
3062  console.error("Tab placement was out of bounds: "+index);
3063  },
3064 
3075  moveTab: function(tab,index)
3076  {
3077  var i = this._children.indexOf(tab);
3078 
3079  if (i>-1)
3080  {
3081  //console.log("i: "+i);
3082  //console.log("x: "+index);
3083  //console.log( this._children );
3084 
3085  if (i!=index)
3086  {
3087  index -= (i<index-1);
3088  this._children.splice(i,1);
3089  this._children.splice(index,0,tab);
3090  this._selected = index;
3091  _paneManager.applyLayout( _paneManager.saveLayout() );
3092  }
3093  }
3094  else
3095  console.error("Tried to move pane which was not a child!");
3096  },
3097 
3108  renameTab: function(tab,name)
3109  {
3110  tab._name = name;
3111  var name = name.substring(0,30);
3112 
3113  //var h = ((tab._icon?"<img src='"+tab._icon+"' height='12px' style='position:relative; top:1px; margin-right:2px'/>"+name:name)||"[unnamed]");
3114  var h = cn;
3115  if(c._icon)
3116  {
3117  //console.log(c._icon);
3118  if(c._icon.startsWith("data:image/svg+xml;utf8,"))
3119  {
3120  h = c._icon.replace("data:image/svg+xml;utf8,","") + cn;
3121 
3122  }
3123  else
3124  {
3125  //h = ((c._icon?"<img src='"+c._icon+"' height='12px' style='position:relative; top:1px; margin-right:2px'/>"+cn:cn)||"[unnamed]");
3126  h = ((c._icon?"<img src='"+c._icon+"' />"+cn:cn)||"[unnamed]");
3127  }
3128  }
3129  var i = this._children.indexOf(tab);
3130  if (i>-1)
3131  {
3132  this._html.find("ul > li > "+tabs.TAG).eq(i).html(h);
3133  }
3134  else
3135  { console.warn("Could not rename tab:");
3136  console.log(tab);
3137  }
3138  },
3139 
3149  openMenu: function(e)
3150  {
3151  tabs.currentTab = this;
3152  _paneManager._primaryCell = this;
3153 
3154  if (preferences.fetch("contextMenus",true))
3155  {
3156  //var m = menuManager.getMenu("__tabsMenu");
3157 
3158  /*
3159  if (Object.keys(this._additionalMenus).length)
3160  {
3161  this._additionalMenusSpacer = m.addItem("spacer");
3162 
3163  for (var i in this._additionalMenus)
3164  m.addSubMenu( i, "__tabsAdditionalMenu_"+this._additionalMenus[i], "<span class='paneMenuIcon'></span>");
3165  }
3166  */
3167 
3168  menuManager.openMenu( "__tabsMenu", this._menuButton, "B", this, this.closeMenu.bind(this) );
3169  }
3170 
3171  return false;
3172  },
3173 
3183  closeMenu: function(e)
3184  {
3185  //console.log("close menu!",arguments);
3186 
3187  /*
3188  var m = menuManager.getMenu("__tabsMenu");
3189 
3190  if (this._additionalMenusSpacer)
3191  { m.removeItem(this._additionalMenusSpacer);
3192  this._additionalMenusSpacer = null;
3193  }
3194 
3195  for (var i in this._additionalMenus)
3196  { m.removeItem( "__tabsAdditionalMenu_"+this._additionalMenus[i] );
3197  console.log("removed: ",this._additionalMenus[i]);
3198  }
3199  */
3200  },
3201 
3210  splitHorizontal: function()
3211  {
3212  var THIS = tabs.currentTab;
3213 
3214  var l = { type: "splitter",
3215  stacking: "horizontal",
3216  name: THIS._name,
3217  sizes: ["50%",null],
3218  children: [ THIS.saveLayout(),
3219  { type: "tabs",
3220  children:[],
3221  },
3222  ],
3223  };
3224 
3225  var s = new splitter( l );
3226 
3227  if (THIS._parent.replaceChild( THIS, s ) )
3228  {
3229  _paneManager.applyLayout( _paneManager.saveLayout() );
3230  }
3231  else
3232  console.warn("Could not find child of parent to replace!");
3233  },
3234 
3243  splitVertical: function()
3244  {
3245  var THIS = tabs.currentTab;
3246 
3247  var l = { type: "splitter",
3248  stacking: "vertical",
3249  name: THIS._name,
3250  sizes: ["50%",null],
3251  children: [ THIS.saveLayout(),
3252  { type: "tabs",
3253  children:[],
3254  },
3255  ],
3256  };
3257 
3258  var s = new splitter( l );
3259 
3260  if (THIS._parent.replaceChild( THIS, s ) )
3261  {
3262  _paneManager.applyLayout( _paneManager.saveLayout() );
3263  }
3264  else
3265  console.error("Could not find child of parent to replace!");
3266  },
3267 
3276  mergeSplitter: function()
3277  {
3278  var THIS = tabs.currentTab;
3279 
3280  if (THIS._parent.mergeOut(THIS))
3281  {
3282  //FIXME: would be way more efficient to rebuild the parent instead of the whole layout
3283  _paneManager.applyLayout( _paneManager.saveLayout() );
3284  }
3285  else
3286  console.error("Could not mergeOut the node!");
3287  },
3288 
3298  mergeable: function()
3299  {
3300  var THIS = tabs.currentTab;
3301 
3302  var p = THIS._parent;
3303 
3304  if (!p) return false; //no parent happens in single pane and when it's been merged down to one cell
3305  if (!(p instanceof splitter)) return false;
3306 
3307  return p.containsTabsOtherThan(THIS);
3308  },
3309 
3318  openHelp: function()
3319  {
3320  var tab = tabs.currentTab;
3321 
3322  var t = typeof tab.help;
3323 
3324  if (t === "string")
3325  modal.alert( tab.help, false, "Help" );
3326  else if (t === "function")
3327  tab.help();
3328  },
3329 
3339  hasHelp: function()
3340  {
3341  var tab = tabs.currentTab;
3342 
3343  if (tab instanceof tabs)
3344  return false;
3345 
3346  return (tab.hasHelp && tab.hasHelp());
3347  },
3348 
3357  closeCurrentTab:function()
3358  {
3359  var ts = tabs.currentTab;
3360  var t = ts._children[ts._selected];
3361  //console.log(ts._selected,t);
3362  t.destroy();
3363  _paneManager.applyLayout( _paneManager.saveLayout() );
3364  },
3365 
3374  closeTab: function()
3375  {
3376  //var tb = tabs.currentTab._parent;
3377  var t = tabs.currentTab;
3378  //tb.removeTab(t);
3379  t.destroy();
3380  _paneManager.applyLayout( _paneManager.saveLayout() );
3381  },
3382 
3389  closeAllTabs: function()
3390  {
3391  var THIS = tabs.currentTab;
3392 
3393  THIS._children = [];
3394  _paneManager.applyLayout( _paneManager.saveLayout() );
3395 
3396  /*
3397  while (THIS._children.length)
3398  THIS._children[0].destroy();
3399 
3400  _paneManager.applyLayout( _paneManager.saveLayout() );
3401  */
3402  },
3403 
3414  selectTabHandler: function(p,e)
3415  {
3416  var i = this._children.indexOf(p);
3417 
3418  this._html.tabs2( "option", "active", i );
3419  },
3420 
3431  createPaneByPath: function()
3432  {
3433  modal.prompt( "Path for new pane:",
3434  "/path/to/pane",
3435  this.createPaneByPathHandler.bind(this),
3436  null,
3437  null,
3438  null,
3439  "Create pane from path"
3440  );
3441  },
3442 
3454  createPaneByPathHandler: function(path)
3455  {
3456  var THIS = tabs.currentTab;
3457 
3458  //someone may want to subscribe to a root folder, like for instance /applications, so this test is disabled.
3459  // if (!path.match(/\//))
3460  // {
3461  // modal.alert("Invalid path.");
3462  // return;
3463  // }
3464 
3465  var t = new pane( { id: path,
3466  path: path,
3467  type: "pane"
3468  } );
3469 
3470  // THIS.appendTab(t);
3471  THIS.receiveTab(t,THIS._children.length);
3472 
3473  // _paneManager.applyLayout( _paneManager.saveLayout() );
3474  // _paneManager.saveLayout();
3475  },
3476 
3485  activate: function()
3486  {
3487  for (var i=0;i<this._children.length;i++)
3488  this._children[i].activate();
3489 
3490  if (this._activated) return;
3491 
3492  //initialize the (jQuery) tabs
3493  var TABS = this;
3494  this._html.tabs2( { event: "mousedown",//"touchstart click",
3495  active: this._selected,
3496  //hide: false,
3497  //show:
3498  activate: function(event,ui)
3499  {
3500  TABS._selected = TABS._html.tabs2("option","active");
3501  _paneManager.saveLayout();
3502 
3503  //console.log( TABS._html.tabs2("option","active") );
3505  if ( TABS._children[TABS._selected] )
3506  { var p = _paneManager._currentPane = TABS._children[TABS._selected];
3507  p.resize();
3508  if (p.triggerCurrentPane)
3509  p.triggerCurrentPane();
3510  }
3511  },
3512  } );
3513 
3514  this._html.find( ".ui-tabs2-panel" ).css( {top:"",bottom:""} );
3515  //this._html.find( "li>a.ui-tabs2-anchor" ).removeAttr("href"); //try to prevent chrome from showing fragment link
3516  if (this._borderless)
3517  this._html.find( "[aria-hidden=false]" ).css( {top:"0px",bottom:"0px"} );
3518 
3519  paneManager.__TABSlastOver = null;
3520  paneManager.__TABSreceived = false;
3521  paneManager.__TABScurrent = null;
3522  var parentTab = this._parent;
3523  //while ( parentTab && !utils.isInstanceOf( parentTab, [tabs,tabswitcher,splitter] ) )
3524  while ( parentTab && !utils.isInstanceOf( parentTab, [tabs,tabswitcher] ) )
3525  parentTab = parentTab._parent;
3526  if (!parentTab) parentTab = this;
3527 
3528  //if (false)
3529  if (this._sortable)
3530  this._html.children("ul.ui-tabs2-nav").sortable(
3531  {
3532  appendTo: "body",
3533  zIndex: "1000",
3534  connectWith: "#"+parentTab._tid+" .tabs>ul.ui-tabs2-nav", //only connect with children of the parent
3535  items: "li",
3536  helper: "clone",
3537  opacity: 0.92,
3538  scroll: false,
3539  distance: 5,
3540  //tolerance: "pointer",
3541  placeholder: "tabs-placeholder",
3542  forcePlaceholderSize: true,
3543  start: function(event,ui)
3544  {
3545  paneManager.__TABScurrent = TABS;
3546  paneManager.__TABSparent = parentTab;
3547  paneManager.__TABSlastOver = $(this);
3548  $(this).addClass("tabs-target-over");
3549  },
3550  over: function(event,ui)
3551  {
3552  paneManager.__TABSlastOver.removeClass("tabs-target-over");
3553  paneManager.__TABSlastOver = $(this);
3554  if (!$(this).hasClass("tabs-target-notallowed"))
3555  $(this).addClass("tabs-target-over");
3556  },
3557  activate: function(event,ui)
3558  {
3559  // return;
3560 
3561  if ( paneManager.__TABSparent._html.find(".tabs #"+TABS._tid).length > 0 )
3562  $(this).addClass("tabs-target-notallowed");
3563  else
3564  $(this).addClass("tabs-target");
3565  //$(this).addClass("tabs-target");
3566  },
3567  deactivate: function(event,ui)
3568  {
3569  $(this).removeClass("tabs-target tabs-target-over tabs-target-notallowed");
3570  delete paneManager.__TABSlastOver;
3571  },
3572  receive: function(event,ui)
3573  {
3574  if ($(this).hasClass("tabs-target-notallowed"))
3575  { //console.log("NOT ALLOWED");
3576  _paneManager.applyLayout( _paneManager.saveLayout() );
3577  paneManager.__TABSreceived = true;
3578  return;
3579  }
3580 
3581  var source = ui.sender.data("tabsPane");
3582  var destination = $(this).data("tabsPane");
3583  var pane = ui.item.data("pane");
3584  var index = ui.item.index() - 1; //minus one for menu button
3585 
3586  destination.receiveTab(pane,index);
3587 
3588  paneManager.__TABSreceived = true;
3589  },
3590  stop: function(event,ui)
3591  {
3592  //if the move is not between tabs objects, nothing has been received... ie tabs have been reordered
3593  if (!paneManager.__TABSreceived)
3594  {
3595  var destination = $(this).data("tabsPane");
3596  var pane = ui.item.data("pane");
3597  var index = ui.item.index()-1; //minus one for menu button
3598 
3599  destination.moveTab(pane,index);
3600  }
3601 
3602  delete paneManager.__TABSreceived;
3603  delete paneManager.__TABScurrent;
3604  },
3605  } );
3606 
3607  this._html.children("ul.ui-tabs2-nav").bind("contextmenu", function(e)
3608  {
3609  _paneManager._primaryCell = tabs.currentTab = TABS;
3610  if (preferences.fetch("contextMenus",true))
3611  menuManager.openMenu( "__tabsMenu", e, "B" );
3612  return false;
3613  } );
3614 
3615  if (this._tabContextMenus)
3616  //this._html.children("ul.ui-tabs2-nav").children("li").bind("contextmenu", function(e)
3617  this._html.children("ul.ui-tabs2-nav").on("contextmenu", "li", function(e)
3618  {
3619  var t = $(e.currentTarget);
3620  var i = t.index() - 1; //minus one for menu button
3621  tabs.currentTab = TABS._children[i];
3622  if (preferences.fetch("contextMenus",true))
3623  if (!tabs.currentTab._noClose)
3624  menuManager.openMenu( "__tabOptionsMenu", e, "B" );
3625  return false;
3626  } );
3627 
3628  /*
3629  this._html.bind( "mouseenter", function(e)
3630  {
3631  //console.log("tabs over!");
3632 
3633  _paneManager._currentPane = TABS._children[TABS._selected];
3634 
3635  return false;
3636  } );
3637 
3638  this._html.bind( "mouseleave", function(e)
3639  {
3640  //console.log("tabs out!");
3641 
3642  return false;
3643  } );
3644  */
3645 
3646  this._html.bind( "triggerNavigation", function(e,dir)
3647  {
3648  //console.log("NAV REQUEST!");
3649  //console.log(arguments);
3650 
3651  var i = TABS._selected;
3652 
3653  switch (dir)
3654  {
3655  case "previous": i--;
3656  if (i >= 0)
3657  TABS._html.tabs2( "option", "active", i );
3658  else
3659  TABS._html.parent().trigger("triggerNavigation","previous");
3660 
3661  break;
3662 
3663  case "next": i++;
3664  if (i < TABS._children.length)
3665  TABS._html.tabs2( "option", "active", i );
3666  else
3667  TABS._html.parent().trigger("triggerNavigation","next");
3668  break;
3669 
3670  default: console.warn("bad navigation direction!");
3671  console.warn(arguments);
3672  break;
3673  }
3674 
3675  return false;
3676  } );
3677 
3678  this._html.bind("renameTab", function(evt,name)
3679  {
3680  //console.log("RENAME TAB",arguments);
3681 
3682  var child = evt.target; //as dom element, not jquery
3683 
3684  var tt = null;
3685  var tnum = null;
3686  for (var i=0; i<this._children.length; i++)
3687  {
3688  var parent = this._children[i]._html[0]; //as dom element!
3689  if ( $.contains( parent, child ) )
3690  {
3691  tt = this._children[i];
3692  tnum = i;
3693  break;
3694  }
3695  }
3696 
3697  //console.log("renaming tab #"+tnum);
3698 
3699  if (tt !== null)
3700  {
3701  this.renameTab(tt,name);
3702  _paneManager.saveLayout();
3703  }
3704 
3705  return false;
3706  }.bind(this) );
3707 
3708  this._activated = true;
3709  },
3710 
3719  triggerCurrentPane: function()
3720  {
3721  if (this._trigger)
3722  {
3723  for (var t in this._trigger)
3724  {
3725  switch(t)
3726  {
3727  case "select": switch(this._trigger[t])
3728  {
3729  case "first": this._html.tabs2( "option", "active", 0 ); break;
3730  case "next": console.log("next tab!"); break;
3731  case "previous": console.log("previous tab!"); break;
3732  break;
3733 
3734  //no default case... let it fall to vfsClient
3735  }
3736  break;
3737  }
3738  }
3739 
3740  //if (this.attached())
3741  this._html.trigger("triggerCurrentPane", this._trigger);
3742  }
3743 
3744  var t = this._children[this._selected];
3745 
3746  if (t && t.triggerCurrentPane)
3747  { //console.log("tabswitcher tab has trigger... doing it!");
3748  t.triggerCurrentPane();
3749  }
3750  },
3751 
3758  deactivate: function()
3759  {
3760  //if (!this._activated) return;
3761 
3762  for (var i=0;i<this._children.length;i++)
3763  this._children[i].deactivate();
3764 
3765  //FIXME: deactivate tabs?
3766 
3767  this._activated = false;
3768  },
3769 
3779  saveLayout: function()
3780  {
3781  var ch = [],c;
3782  for (var i=0;i<this._children.length;i++)
3783  { c = this._children[i];
3784  ch.push( c.saveLayout() );
3785  }
3786 
3787  var l = { name: this._name,
3788  id: this._id,
3789  //uuid: this._tid,
3790  type: "tabs",
3791  selected: this._selected,
3792  //borderless: this._borderless,
3793  children: ch,
3794  };
3795 
3796  if (this._icon) l.icon = this._icon;
3797  if (this._title) l.title = this._title;
3798  if (this._trigger) l.trigger = this._trigger;
3799  if (!this._sortable) l.sortable = false;
3800  if (!this._showMenu) l.showMenu = false;
3801  if (!this._tabContextMenus) l.tabContextMenus = false;
3802  //if (this._additionalMenus) l.additionalMenus = this._additionalMenus;
3803 
3804  if (_paneManager._primaryCell === this)
3805  l.primary = true;
3806 
3807  return l;
3808  },
3809 
3821  find: function(p,type)
3822  {
3823  //if ( ( (!type) || (this instanceof type) ) && ( (!p) || (p==this) || (p==this._name) || (p==this._id) ) )
3824  if ( ( (!type) || (utils.isInstanceOf(this,type)) ) && ( (!p) || (p==this) || (p==this._name) || (p==this._id) ) )
3825  return this;
3826 
3827  for(var i=0;i<this._children.length;i++)
3828  {
3829  var f = this._children[i].find(p,type);
3830 
3831  if (f) return f;
3832  }
3833 
3834  return null;
3835  },
3836 
3845  resize: function()
3846  {
3847  for (var i=0;i<this._children.length;i++)
3848  this._children[i].resize();
3849  },
3850 
3860  attach: function()
3861  {
3862  for (var i=0;i<this._children.length;i++)
3863  this._children[i].attach();
3864  },
3865 
3874  detach: function()
3875  {
3876  for (var i=0;i<this._children.length;i++)
3877  this._children[i].detach();
3878  },
3879 
3888  destroy: function()
3889  {
3890  for (var i=0;i<this._children.length;i++)
3891  this._children[i].destroy();
3892 
3893  if (this._parent instanceof tabs || this._parent instanceof tabswitcher)
3894  this._parent.removeTab(this);
3895 
3896  //console.log("should remove: __tabsSelectMenu_"+this._id);
3897  //menuManager.removeMenu( "__tabsSelectMenu_"+this._id ); //already in applyLayout
3898 
3899  this._html.detach();
3900  this._html = null;
3901  this._parent = null;
3902  this._children = [];
3903  },
3904  /*
3905  containsCellWithName: function(n)
3906  {
3907  if (this._name == n) return true;
3908 
3909  for(var c=0; c<this._children.length; c++)
3910  if (this._children[c].containsCellWithName(n))
3911  return true;
3912 
3913  return false;
3914  },
3915  */
3916 
3928  replaceChild: function(oldChild, newChild)
3929  {
3930  var i = this._children.indexOf(oldChild);
3931 
3932  if (i>-1)
3933  {
3934  this._children[i] = newChild;
3935  newChild._parent = this;
3936  return true;
3937  }
3938 
3939  return false;
3940  },
3941  }
3942 
3943 
3946  // Pane //
3948 
3968  function pane(layout,object)
3969  {
3970  if (!layout) return; //to allow a default constructor
3971 
3972  this._id = layout.id || utils.uuid();
3973  //this._id = utils.cleanPath(layout.id); //ensure that all paths are clean of additional separators.
3974  this._tid = utils.uuid();
3975  this._name = layout.name || utils.fileNameFromPath(layout.path);
3976  this._path = utils.cleanPath(layout.path);
3977  this._icon = layout.icon || null;
3978  this._title = layout.title || null;
3979  //this._object = object || _paneManager.objectFactory(layout); //use the objectFactory callback
3980  this._object = object || _paneManager.objectFactory((layout.overrideType=layout.type,layout),(this._path?null:this.applySubscription.bind(this,{},{}))); //use the objectFactory callback
3981  //this._object = object || _paneManager.objectFactory({type:"pane",overrideType:layout.type}); //always create a paneLoader, but remember what the original type was in case a layout change happened
3982  this._html = null;
3983  this._mask = null;
3984  this._parent = null;
3985  this._activated = false;
3986  this._trigger = layout.trigger; //the dom event that will be fired when this pane is made active
3987  this._keepType = layout.keepType || false;
3988  this._noClose = layout.noClose || false; //don't allow the tab context to open... this pane is non-closeable
3989  this._layout = layout;
3990  this._context = layout.context || {};
3991  this._context.sourcefile = this._context.sourcefile || utils.fileNameFromPath( this._path, true, true );
3992  this._context.sourcepath = this._context.sourcepath || this._path;
3993  this._class = layout.class || null;
3994 
3995  _paneManager.registerPane( this );
3996  }
3997 
3998  pane.prototype = {
3999 
4009  createHTML: function()
4010  {
4011  if (this._html) return this._html;
4012 
4013  var n = $("<div tabindex='-1' class='pane' id='"+this._tid+"'/>");
4014  n.attr("vfspath",this._path);
4015 
4016  if (this._class)
4017  n.addClass( this._class );
4018 
4019  if (this._object && this._object.createHTML)
4020  n.append(this._object.createHTML());
4021  //n.append(this._object.createHTML(this._layout));
4022 
4023  var mask = this._mask = $("<div class='paneMask'>").appendTo(n);
4024  mask.css( {
4025  position: "absolute",
4026  width: "100%",
4027  height: "100%",
4028  left: "0px",
4029  top: "0px",
4030  background: "var(--BG0)",
4031  "background-image": "url('"+paneLoaderImg+"')",
4032  "background-position": "center center",
4033  "background-repeat": "no-repeat",
4034  "background-size": "auto auto",
4035  opacity: ".6",
4036  "z-index": "10000",
4037  cursor: "not-allowed",
4038  } );
4039  mask.attr("title","Resource temporarily unavailable.");
4040  mask.hide();
4041 
4042  this._html = n;
4043 
4044  return this._html;
4045  },
4046 
4056  rename: function(name)
4057  {
4058  this._name = name;
4059  this._parent.renameTab(this,name);
4060  },
4061 
4072  applyDiff: function(data,user)
4073  {
4074  if (data === null) //a null diff (not an empty diff) means that the pane needs to be closed
4075  {
4076  //console.log("NULL DIFF",data);
4077  this.destroy();
4078  //requestAnimationFrame( function() {
4079  _paneManager.applyLayout( _paneManager.saveLayout() );
4080  //} );
4081  }
4082  /*
4083  //moved to objectRegistry
4084  else if (Object.keys(data).length == 1 && ("mounted" in data) ) //the special case of a pane from a remote VFS reporting its mounted state
4085  {
4086  //console.log("would adjust mask here: "+data.mounted);
4087  if (!data.mounted)
4088  this._mask.show();
4089  else
4090  this._mask.hide();
4091  }
4092  */
4093  else
4094  {
4095  if (this._object)
4096  {
4097  this._object.applyDiff(data,user);
4098  }
4099  }
4100  },
4101 
4112  applySubscription: function(data,metadata)
4113  {
4114  //console.error("PANE apply subscription: "+this._path);
4115  //console.log(arguments);
4116 
4117  // try
4118  {
4119  if (data === null) //a null diff (not an empty diff) means that the pane needs to be closed
4120  {
4121  //console.log("NULL SUBSCRIBE DIFF",data);
4122  this.destroy();
4123  _paneManager.applyLayout( _paneManager.saveLayout() );
4124  return;
4125  }
4126 
4127  if (this._object)
4128  {
4129  var type;
4130 
4131  if (this._layout.type && this._layout.type !== "pane")
4132  type = this._layout.type;
4133  else
4134  type = metadata.type;
4135 
4136  /* if (type == "pane")
4137  {
4138  if (data.icon) this._icon = data.icon; //FIXME: should we update the tab here?
4139  if (data.name) this._name = data.name; //FIXME: should we update the tab here?
4140 
4141  console.log("PANE type pane apply settings!");
4142  this._object.applySettings(data);
4143  }
4144  */
4145  if (type && this._object instanceof paneLoader)
4146  {
4147  //console.log("Creating new '"+type+"' for '"+this._path+"' ('"+this._object._type+"' -> '"+type+"')");
4148 
4149  //uncomment to test the loader...
4150  //var later = function() {
4151  var layout = this._layout;
4152  layout.parent = this;
4153  layout.type = type;
4154  layout.metadata = metadata;
4155  //layout.context = this._context;
4156  //console.log(layout);
4157 
4158  //if (!metadata.context)
4159  // metadata.context = this._context;
4160  metadata.context = utils.copyObject( this._context, metadata.context );
4161 
4162  var oldObject = this._object;
4163  var cb = this.applySubscription.bind(this,data,metadata);
4164  this._object = _paneManager.objectFactory(layout,cb);
4165  if ( this._object instanceof paneLoader )// ||
4166  // this._object._type == "diagnosticPane" )
4167  {
4168  //console.log("unknown type: '"+layout.type+"' ... loading it.");
4169  return;
4170  }
4171 
4172  if (this._html)
4173  {
4174  //this._html.empty();
4175  this._html.children(":not(.paneMask)").remove();
4176  }
4177  //else //if _html does not exist, that means this pane has been destroyed, which means some other logic is failing. These lines should not be needed, but useful for debugging.
4178  //{ console.warn("had to createHTML",this);
4179  // this.createHTML();
4180  //}
4181 
4182  if (this._object)
4183  {
4184  var oh=null;
4185 
4186  if ("createHTML" in this._object)
4187  {
4188  //oh = this._object.createHTML(layout);
4189  oh = this._object.createHTML();
4190  if (!this._html)
4191  console.log(this);
4192  this._html.append( oh );
4193 
4194  if ("resize" in this._object)
4195  this._object.resize();
4196 
4197  var THIS = this;
4198  //var TABS = this._parent; //don't want to bind this into the closure!
4199  oh.bind("createpane", function(evt,newpane) {
4200  var p = _paneManager.fetchPane( newpane );
4201  THIS._parent.selectTabHandler( THIS._parent.appendTab( p ) );
4202  //_paneManager.registerPane(p);
4203  //_paneManager.pathAddedCallback([p._id]);
4204  //objectRegistry.pathAddedCallback([p._id]); //subscribe this new pane.
4205  //console.log(data);
4206  return false;
4207  });
4208  }
4209  else
4210  { console.warn("can't createHTML for object...");
4211  console.warn(this._object);
4212  }
4213 
4214  if (this._activated && this._object.activate)
4215  this._object.activate();
4216 
4217  this._object.applySubscription(data,metadata);
4218 
4219  //sometimes an object will receive diffs while the panetype is loading... those are recorded by the loader and applied here.
4220  if (oldObject && oldObject instanceof paneLoader)
4221  {
4222  var diffList = oldObject._diffList;
4223  //console.log(diffList.length,diffList);
4224 
4225  while(diffList.length)
4226  {
4227  var diff = diffList.shift();
4228  //console.log(diff);
4229  this._object.applyDiff(diff,"loader");
4230  }
4231  }
4232 
4233  if (oh)
4234  oh.trigger("paneloaded",[this]);
4235  }
4236  //}
4237  //setTimeout( later.bind(this), 500+Math.random()*1000 );
4238  }
4239  else
4240  {
4241  //console.error("PANE: "+this._path +" applySub 2");
4242  //if (!this._object._subscribed)
4243  this._object.applySubscription(data,metadata);
4244  this.mounted(true);
4245  //else
4246  // console.warn("PANE: "+this._path +" already subscribed 2");
4247  //this._object.applySettings(data);
4248  }
4249  }
4250  }
4251  // catch(e)
4252  // {
4253  // console.error(e);
4254  // }
4255  },
4256 
4266  applySettings: function(settings)
4267  {
4268  if (this._object && this._object.applySettings)
4269  this._object.applySettings(settings);
4270  },
4271 
4281  applyRequestLock: function(data)
4282  {
4283  if (this._object && this._object.applyRequestLock)
4284  this._object.applyRequestLock(data);
4285  },
4286 
4296  applyReleaseLock: function(data)
4297  {
4298  if (this._object && this._object.applyReleaseLock)
4299  this._object.applyReleaseLock(data);
4300  },
4301 
4314  applyRequestSuccess: function(command,id,data,metadata)
4315  {
4316  if (id === this._path)
4317  {
4318  if (this._object && this._object.applyRequestSuccess)
4319  this._object.applyRequestSuccess(command,id,data,metadata);
4320  else
4321  console.log("no applyRequestSuccess on the object",this._object);
4322 
4323  if (false)
4324  switch(command)
4325  {
4326  case "rm": console.warn("close pane now...");
4327  this.destroy();
4328  _paneManager.applyLayout( _paneManager.saveLayout() );
4329  return;
4330 
4331  default: console.log("success: "+this._path+" "+command);
4332  break;
4333  }
4334  }
4335  else
4336  console.trace("applyRequestSuccess for different id: "+id,arguments);
4337  },
4338 
4350  applyRequestError: function(command,id,reason)
4351  {
4352  switch (command)
4353  {
4354  case "subscribe":
4355  var fn = utils.fileNameFromPath(id,true);
4356  modal.alert( "Unable to subscribe to '"+fn+"'.\n\nThe pane or tab was removed." );
4357 
4358  this.applyDiff(null); //will destroy this pane and update the layout
4359  //objectRegistry.applyDiff( this._path, null, null );
4360  //this.destroy();
4361  //_paneManager.saveLayout();
4362 
4363  console.error(command,id,reason);
4364 
4365  break;
4366 
4367  default:
4368  if (this._object && this._object.applyRequestError)
4369  this._object.applyRequestError(command,id,reason);
4370  else
4371  modal.alert( reason, null, command+" error" );
4372  break;
4373  }
4374  },
4375 
4383  attached: function()
4384  {
4385  if (this._html) return (this._html[0].parentElement!==null);
4386  },
4387 
4396  attach: function()
4397  {
4398  if (this._object && this._object.attach)
4399  this._object.attach();
4400  },
4401 
4408  detach: function()
4409  {
4410  //console.trace("detach",this._path);
4411 
4412  this._parent = null;
4413 
4414  if (this._object && this._object.detach)
4415  this._object.detach();
4416 
4417  if (this._html) this._html.detach();
4418  },
4419 
4428  activate: function()
4429  {
4430  var THIS = this;
4431 
4432  if (_paneManager._currentPane === this)
4433  { requestAnimationFrame( function() {
4434  THIS._html.focus(); //try to prevent layout thrashing
4435  } );
4436  }
4437 
4438  if (this._activated) return;
4439 
4440  var PANE = this;
4441 /*
4442  this._html.bind( "mouseenter", function(e) { //for keyboard commands
4443  var THIS = this;
4444  requestAnimationFrame( function() {
4445  $(THIS).focus();
4446  _paneManager._currentPane = PANE;
4447  } )
4448  //console.log("pane over!");
4449 
4450  return false;
4451  } );
4452 
4453  this._html.bind( "mouseleave", function(e) {
4454  $(this).blur();
4455  //_paneManager._currentPane = null;
4456  //console.log("pane out!");
4457 
4458  return false;
4459  } );
4460 */
4461  //activate pane object...
4462  if (this._object && this._object.activate)
4463  this._object.activate();
4464 
4465  this._activated = true;
4466  },
4467 
4476  triggerCurrentPane: function()
4477  {
4478  //console.log("trigger current!",this);
4479 
4480  if (this._trigger)
4481  {
4482  // console.log("would triggerCurrent with value: ");
4483  // console.log(this._trigger);
4484 
4485  // if (this._object && this._object.triggerCurrent)
4486  // this._object.triggerCurrent(this._trigger);
4487 
4488  if (this.attached())
4489  this._html.trigger("triggerCurrentPane", this._trigger);
4490  }
4491 
4492  if (this._object && this._object.triggerCurrentPane)
4493  this._object.triggerCurrentPane();
4494 
4495  },
4496 
4503  deactivate: function()
4504  {
4505  //if (!this._activated) return;
4506 
4507  //activate pane object...
4508  if (this._object && this._object.deactivate)
4509  this._object.deactivate();
4510 
4511  this._activated = false;
4512  },
4513 
4521  saveLayout: function()
4522  {
4523  var l = {
4524  id: this._id,
4525  name: this._name,
4526  path: this._path,
4527  type: this.paneType,
4528  };
4529 
4530  if (this._keepType)
4531  l.keepType = this._keepType;
4532 
4533  if (this._layout.icon)
4534  l.icon = this._layout.icon;
4535 
4536  if (this._trigger)
4537  l.trigger = this._trigger;
4538 
4539  if (this._noClose)
4540  l.noClose = this._noClose;
4541 
4542  if (this._class)
4543  l["class"] = this._class;
4544 
4545  if (this._title)
4546  l["title"] = this._title;
4547 
4548  if (this._object)
4549  { var s = this._object._saveFields;
4550  for (var f in s)
4551  l[f] = this._object[s[f]];
4552  }
4553 
4554  //save any additional settings provided by the layout.
4555  //presumably these are settings provided during creation, but
4556  //we haven't had a chance to access _saveFields because it's a
4557  //paneLoader, so not a real object yet
4558  if (this._object instanceof paneLoader)
4559  {
4560  var no = ["id","type","parent","metadata"];
4561 
4562  for (var y in this._layout)
4563  if (!no.includes(y) && !(y in l))
4564  { //console.log("should get: "+y);
4565  l[y] = this._layout[y];
4566  }
4567  }
4568 
4569  return l;
4570  },
4571 
4583  find: function(p,type)
4584  {
4585  //if ( ( (!type) || (this instanceof type) ) && ( (!p) || (p==this) || (p==this._name) || (p==this._id) ) )
4586  if ( ( (!type) || (utils.isInstanceOf(this,type)) ) && ( (!p) || (p===this) || (p===this._name) || (p===this._id) || (p===this._path) ) )
4587  return this;
4588 
4589  return null;
4590  },
4591 
4600  resize: function()
4601  {
4602  if (this._object && this._object.resize)
4603  this._object.resize();
4604  },
4605 
4613  mounted: function(mountState)
4614  {
4615  if (mountState)
4616  {
4617  this._mask.hide();
4618  }
4619  else
4620  {
4621  this._mask.show();
4622  }
4623  },
4624 
4634  area: function()
4635  {
4636  var a = 0;
4637  if (this._html)
4638  a = this._html.width() * this._html.height();
4639 
4640  return a;
4641  },
4642 
4651  destroy: function()
4652  {
4653  //console.warn("PM Pane destroy: "+this._path);
4655  if (this._parent instanceof tabs || this._parent instanceof tabswitcher)
4656  this._parent.removeTab(this);
4657  else if (this._parent instanceof splitter)
4658  this._parent.mergeOut(this);
4659  //else
4660  // console.log("PARENT WAS: "+this._parent+" // "+this._path);
4661 
4662  _paneManager.unregisterPane(this);
4663 
4664  if (this._html)
4665  this._html.detach();
4666  this._html = null;
4667  this._mask = null;
4668  this._parent = null;
4669  this._name = null;
4670 
4671  if (this._object)
4672  {
4673  if (this._object.destroy)
4674  {
4675  _paneManager.objectDestroy(this._object); //use the objectDestroy callback
4676  this._object.destroy();
4677  }
4678  else
4679  console.log("missing destroy!",this._object);
4680 
4681  this._object = null;
4682  }
4683  },
4684 
4694  hasHelp: function()
4695  {
4696  if (this._object && this._object._help)
4697  return true;
4698 
4699  return false;
4700  },
4701 
4715  get help() {
4716  if (this._object && this._object._help)
4717  return this._object._help;
4718 
4719  return "No help available for this item.";
4720  },
4721 
4731  get paneType() {
4732  if (this._object)
4733  return this._keepType ? this._object.paneType : "pane";
4734 
4735  return "pane";
4736  },
4737 
4740  /*
4741  containsCellWithName: function(n)
4742  {
4743  return (this._name == n);
4744  },
4745  */
4746  }
4747 
4748  _paneManager = new paneManager();
4749 
4750  _paneManager.classes = {
4752  splitter: splitter,
4753  tabs: tabs,
4755  }
4756 
4757  return _paneManager;
4758  }
4759 );
applySubscription(data, metadata)
setter selected
a setter DOCME
setter moving
a setter DOCME
getter html
Get the html color representation of this object.
getter id
returns the number of milliseconds since midnight January 1, 1970 UTC
setter user
a setter DOCME
Form renderer.
form(layout)
setter background
Set the background color of the renderer.
getter position
A getter, returns this._position.
This library creates convenient methods for adding keyboard shortcuts to an application.
registerCommand(c, f, l, r, p)
Register a command with a keyboard listener.
Manages context menus or any other menu.
createMenu(m, p, subscribe)
Create a paneMenu(). You will want to use paneMenu.addItem() on the resulting object.
openMenu(m, b, p, d, c)
Open a menu.
getter y
a getter DOCME
setter name
a setter DOCME
setter type
a setter DOCME
find(id, type)
Find a registered object, optionally by type.
applyRequestError(command, id, reason)
printRegistry()
Print the contents of the registry to the console.
applyReleaseLock(id, data)
registerObject(id, o, nosubscribe)
Register an object in the registry, and call the pathAddedCallback.
applyRequestLock(id, data)
unregisterObject(id, o, silent, nounsubscribe, now)
Unregister an object from the registry.
Create a pane which will be mounted into a paneManager layout.
mounted(mountState)
Notify this pane that the mounted state has changed.
getter help
A getter that will return a pane object's help message, or a default message indicating that no help ...
attached()
Detects is this pane is attached to the DOM.
resize()
DOCME.
rename(name)
createHTML()
getter paneType
A getter which will calculate the pane type based on the _object, or return the default "pane".
pane(layout, object)
activate()
DOCME.
Create a splitter.
splitter(layout)
mergeOut(child)
handleFunction(e)
A tabbed layout element which allows splits.
moveTab(tab, index)
mergeable()
splitVertical()
DOCME.
createPaneByPath()
Create a new pane to any path.
tabs(layout)
Create a tabs object based on layout parameters.
hasHelp()
destroy()
DOCME.
splitHorizontal()
DOCME.
openHelp()
DOCME.
receiveTab(tab, index)
appendTab(pane)
mergeSplitter()
DOCME.
A tabbed layout element, intended to be used at the top of the applications UI.
replaceChild(oldChild, newChild)
attach()
Attach the tabs's children.
deactivate()
Deactivate this element and call deactivate on all its children.
tabswitcher(layout)
Create a tabswitcher object based on layout parameters.
closeCurrentTab()
DOCME.
destroy()
DOCME.
detach()
Detach the tab's children.
createChildren(children)
selectTabHandler(p, e)
triggerCurrentPane()
DOCME.
containsTabsOtherThan(t)
closeAllTabs()
Close all open tabs.
closeTab()
DOCME.
The paneManager manages panes in a user layout.
toggleSinglePane(e, nosave)
openApplicationLayout(event, context, template, metadata)
registerPane(p)
Register a pane in the paneManager registry, and also add it to the objectRegistry.
paneManager()
DOCME.
applyLayout(_layout)
Apply a json layout, building and destroying children as needed.
pathDeleteCallback()
A callback to use for deleting entries in the VFS based on a path.
pathSubmitCallback()
DOCME.
buildLayout(_layout)
pathAddedCallback()
DOCME.
setter config
a setter DOCME
openModalForm(path, form, title, size, context, metadata)
toggleSinglePaneBorderless(e, nosave)
initialize()
DOCME.
pathRemovedCallback()
DOCME.
saveCallback()
DOCME.
objectDestroy(pane)
garbageCollect()
garbge collect (destroy) any unmounted cells
fetchPane(c, o)
Find a pane by path/id, and return it, or create a new pane.
findPane(c)
Find a pane by path/id, and return it, or return null.
objectFactory(layout)
pathCreateCallback()
A callback to use for creating new entries in the VFS based on a path.
getter valid
a getter DOCME
getter mask
a getter DOCME
getter path
a getter DOCME
applyRequestSuccess()
If an adminstrator has deleted his own preferences, this function will be called on success.
fetch(v, _default)
setter total
a setter DOCME
applySettings(settings)
resize()
Does nothing.
applyDiff(diff, user)
createHTML()
Create the HTML contents of the pane.
Utility functions for javascript clients.
uuid()
Generate a universally unique identifier.
toggleFullScreen()
Toggle the fullscreen state of the page.
cleanPath(path)
Clean and normalize a resource path.
fileNameFromPath(p, orDirIfNeeded)
Retrieve the last part of a file path.
copyObject(source, dest)
Create a deep copy of a JSON object, extending the destination object recursively.
isInstanceOf(o, t)
Check if an object is of a chosen type.
resolveContextValues(o, ctx)
Recursively resolve any context values in an object.
createPaneByPathHandler()
metadata(paths)