pgadmin4/web/pgadmin/static/js/wcDocker/docker.js

1863 lines
60 KiB
JavaScript
Executable File

/*!
* Web Cabin Docker - Docking Layout Interface.
*
* Dependancies:
* JQuery 1.11.1
* JQuery-contextMenu 1.6.6
* font-awesome 4.2.0
*
* Author: Jeff Houde (Lochemage@gmail.com)
* Web: http://docker.webcabin.org/
*
* Licensed under
* MIT License http://www.opensource.org/licenses/mit-license
* GPL v3 http://opensource.org/licenses/GPL-3.0
*
*/
// Provide backward compatibility for IE8 and other such older browsers.
if (!Function.prototype.bind) {
Function.prototype.bind = function (oThis) {
if (typeof this !== "function") {
// closest thing possible to the ECMAScript 5
// internal IsCallable function
throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
}
var aArgs = Array.prototype.slice.call(arguments, 1),
fToBind = this,
fNOP = function () {},
fBound = function () {
return fToBind.apply(this instanceof fNOP && oThis
? this
: oThis,
aArgs.concat(Array.prototype.slice.call(arguments)));
};
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
};
}
if (!Array.prototype.indexOf)
{
Array.prototype.indexOf = function(elt /*, from*/)
{
var len = this.length >>> 0;
var from = Number(arguments[1]) || 0;
from = (from < 0)
? Math.ceil(from)
: Math.floor(from);
if (from < 0)
from += len;
for (; from < len; from++)
{
if (from in this &&
this[from] === elt)
return from;
}
return -1;
};
}
/*
The main window instance. This manages all of the docking panels and user input.
There should only be one instance of this, although it is not enforced.
options allows overriding default options for docker. The current fields are:
allowContextMenu: boolean (default true) - Create the right click menu for adding/removing panels.
hideOnResize: boolean (default false) - If true, panels will hide their content as they are being resized.
*/
function wcDocker(container, options) {
this.$container = $(container).addClass('wcDocker');
this.$transition = $('<div class="wcDockerTransition"></div>');
this.$container.append(this.$transition);
this._events = {};
this._root = null;
this._frameList = [];
this._floatingList = [];
this._modalList = [];
this._focusFrame = null;
this._splitterList = [];
this._tabList = [];
this._dockPanelTypeList = [];
this._draggingSplitter = null;
this._draggingFrame = null;
this._draggingFrameSizer = null;
this._draggingFrameTab = null;
this._draggingCustomTabFrame = null;
this._ghost = null;
this._menuTimer = 0;
this._resizeData = {
time: -1,
timeout: false,
delta: 150,
};
this._defaultOptions = {
allowContextMenu: true
};
this._options = {};
for( var prop in this._defaultOptions ) {
this._options[prop] = this._defaultOptions[prop];
}
for( var prop in options ) {
this._options[prop] = options[prop];
}
this.__init();
};
// Docking positions.
wcDocker.DOCK_MODAL = 'modal';
wcDocker.DOCK_FLOAT = 'float';
wcDocker.DOCK_TOP = 'top';
wcDocker.DOCK_LEFT = 'left';
wcDocker.DOCK_RIGHT = 'right';
wcDocker.DOCK_BOTTOM = 'bottom';
wcDocker.DOCK_STACKED = 'stacked';
// Internal events.
wcDocker.EVENT_INIT = 'panelInit';
wcDocker.EVENT_UPDATED = 'panelUpdated';
wcDocker.EVENT_VISIBILITY_CHANGED = 'panelVisibilityChanged';
wcDocker.EVENT_BEGIN_DOCK = 'panelBeginDock';
wcDocker.EVENT_END_DOCK = 'panelEndDock';
wcDocker.EVENT_GAIN_FOCUS = 'panelGainFocus';
wcDocker.EVENT_LOST_FOCUS = 'panelLostFocus';
wcDocker.EVENT_CLOSED = 'panelClosed';
wcDocker.EVENT_BUTTON = 'panelButton';
wcDocker.EVENT_ATTACHED = 'panelAttached';
wcDocker.EVENT_DETACHED = 'panelDetached';
wcDocker.EVENT_MOVE_STARTED = 'panelMoveStarted';
wcDocker.EVENT_MOVE_ENDED = 'panelMoveEnded';
wcDocker.EVENT_MOVED = 'panelMoved';
wcDocker.EVENT_RESIZE_STARTED = 'panelResizeStarted';
wcDocker.EVENT_RESIZE_ENDED = 'panelResizeEnded';
wcDocker.EVENT_RESIZED = 'panelResized';
wcDocker.EVENT_SCROLLED = 'panelScrolled';
wcDocker.EVENT_SAVE_LAYOUT = 'layoutSave';
wcDocker.EVENT_RESTORE_LAYOUT = 'layoutRestore';
wcDocker.EVENT_CUSTOM_TAB_CHANGED = 'customTabChanged';
wcDocker.EVENT_CUSTOM_TAB_CLOSED = 'customTabClosed';
// Used for the splitter bar orientation.
wcDocker.ORIENTATION_VERTICAL = false;
wcDocker.ORIENTATION_HORIZONTAL = true;
wcDocker.prototype = {
///////////////////////////////////////////////////////////////////////////////////////////////////////
// Public Functions
///////////////////////////////////////////////////////////////////////////////////////////////////////
// Registers a new docking panel type to be used later.
// Params:
// name The name for this new type.
// options An optional object that defines various options
// to initialize the panel with.
// createFunc The function that populates the contents of
// a newly created dock panel of this type.
// Params:
// panel The dock panel to populate.
// isPrivate If true, this type will not appear to the user
// as a window type to create.
// Returns:
// true The new type has been added successfully.
// false Failure, the type name already exists.
registerPanelType: function(name, optionsOrCreateFunc, isPrivate) {
var options = optionsOrCreateFunc;
if (typeof options === 'function') {
options = {
onCreate: optionsOrCreateFunc,
};
}
if (typeof isPrivate != 'undefined') {
options.isPrivate = isPrivate;
}
if ($.isEmptyObject(options)) {
options = null;
}
for (var i = 0; i < this._dockPanelTypeList.length; ++i) {
if (this._dockPanelTypeList[i].name === name) {
return false;
}
}
this._dockPanelTypeList.push({
name: name,
options: options,
});
var $menu = $('menu').find('menu');
$menu.append($('<menuitem label="' + name + '">'));
return true;
},
// Retrieves a list of all currently registered panel types.
// Params:
// includePrivate If true, panels registered as private will
// also be included with this list.
//
// Returns:
// String[] A list of panel type names.
panelTypes: function(includePrivate) {
var result = [];
for (var i = 0; i < this._dockPanelTypeList.length; ++i) {
if (includePrivate || !this._dockPanelTypeList[i].options.isPrivate) {
result.push(this._dockPanelTypeList[i].name);
}
}
return result;
},
// Retrieves the options data associated with a given panel type when it was registered.
// Params:
// typeName The type of the panel.
//
// Returns:
// Object An object that represents the registered info of the panel type.
// false The panel type requested was not found.
panelTypeInfo: function(typeName) {
for (var i = 0; i < this._dockPanelTypeList.length; ++i) {
if (this._dockPanelTypeList[i].name == typeName) {
return this._dockPanelTypeList[i].options;
}
}
return false;
},
// Add a new dock panel to the window of a given type.
// Params:
// typeName The type of panel to create.
// location The docking location of the new panel,
// as defined by wcDocker.DOCK_ values.
// targetPanel An optional target panel, providing one will cause
// the docking location to be relative to this panel.
// Must be supplied when using wcDocker.DOCK_STACKED.
// rect An object with an x, y position, and a w, h size
// used to influence the starting position and size
// of a floating or modal panel.
//
// Returns:
// wcPanel The panel that was created.
// false The panel type does not exist.
addPanel: function(typeName, location, targetPanel, rect) {
for (var i = 0; i < this._dockPanelTypeList.length; ++i) {
if (this._dockPanelTypeList[i].name === typeName) {
var panelType = this._dockPanelTypeList[i];
var panel = new wcPanel(typeName, panelType.options);
panel._parent = this;
panel.__container(this.$transition);
var options = (panelType.options && panelType.options.options) || {};
panel._panelObject = new panelType.options.onCreate(panel, options);
if (location === wcDocker.DOCK_STACKED) {
this.__addPanelGrouped(panel, location, targetPanel);
} else {
this.__addPanelAlone(panel, location, targetPanel, rect);
}
this.__update();
return panel;
}
}
return false;
},
// Removes a dock panel from the window.
// Params:
// panel The panel to remove.
// Returns:
// true The panel was removed.
// false There was a problem.
removePanel: function(panel) {
if (!panel) {
return false;
}
// Do not remove if this is the last moveable panel.
if (this.__isLastPanel(panel)) {
return false;
}
var parentFrame = panel._parent;
if (parentFrame instanceof wcFrame) {
panel.__trigger(wcDocker.EVENT_CLOSED);
// If no more panels remain in this frame, remove the frame.
if (!parentFrame.removePanel(panel)) {
var index = this._floatingList.indexOf(parentFrame);
if (index !== -1) {
this._floatingList.splice(index, 1);
}
index = this._frameList.indexOf(parentFrame);
if (index !== -1) {
this._frameList.splice(index, 1);
}
index = this._modalList.indexOf(parentFrame);
if (index !== -1) {
this._modalList.splice(index, 1);
}
if (this._modalList.length) {
this.__focus(this._modalList[this._modalList.length-1]);
} else if (this._floatingList.length) {
this.__focus(this._floatingList[this._floatingList.length-1]);
}
var parentSplitter = parentFrame._parent;
if (parentSplitter instanceof wcSplitter) {
parentSplitter.__removeChild(parentFrame);
var other;
if (parentSplitter.pane(0)) {
other = parentSplitter.pane(0);
parentSplitter._pane[0] = null;
} else {
other = parentSplitter.pane(1);
parentSplitter._pane[1] = null;
}
// Keep the panel in a hidden transition container so as to not
// destroy any event handlers that may be on it.
other.__container(this.$transition);
other._parent = null;
index = this._splitterList.indexOf(parentSplitter);
if (index !== -1) {
this._splitterList.splice(index, 1);
}
var parent = parentSplitter._parent;
parentContainer = parentSplitter.__container();
parentSplitter.__destroy();
if (parent instanceof wcSplitter) {
parent.__removeChild(parentSplitter);
if (!parent.pane(0)) {
parent.pane(0, other);
} else {
parent.pane(1, other);
}
} else if (parent === this) {
this._root = other;
other._parent = this;
other.__container(parentContainer);
}
this.__update();
} else if (parentFrame === this._root) {
this._root = null;
}
if (this._focusFrame === parentFrame) {
this._focusFrame = null;
}
parentFrame.__destroy();
}
panel.__destroy();
return true;
}
return false;
},
// Moves a docking panel from its current location to another.
// Params:
// panel The panel to move.
// location The docking location of the new panel,
// as defined by wcDocker.DOCK_ values.
// targetPanel An optional target panel, providing one will cause
// the docking location to be relative to this panel.
// Must be supplied when using wcDocker.DOCK_STACKED.
// rect An object with an x, y position, and a w, h size
// used to influence the starting position and size
// of a floating or modal panel.
// Returns:
// wcPanel The panel that was created.
// false The panel type does not exist.
movePanel: function(panel, location, targetPanel, rect) {
if (this.__isLastPanel(panel)) {
return panel;
}
var $elem = panel.$container;
if (panel._parent instanceof wcFrame) {
$elem = panel._parent.$frame;
}
var offset = $elem.offset();
var width = $elem.width();
var height = $elem.height();
var parentFrame = panel._parent;
var floating = false;
if (parentFrame instanceof wcFrame) {
floating = parentFrame._isFloating;
}
if (parentFrame instanceof wcFrame) {
// Remove the panel from the frame.
for (var i = 0; i < parentFrame._panelList.length; ++i) {
if (parentFrame._panelList[i] === panel) {
if (parentFrame._curTab >= i) {
parentFrame._curTab--;
}
// Keep the panel in a hidden transition container so as to not
// destroy any event handlers that may be on it.
panel.__container(this.$transition);
panel._parent = null;
parentFrame._panelList.splice(i, 1);
break;
}
}
if (parentFrame._curTab === -1 && parentFrame._panelList.length) {
parentFrame._curTab = 0;
}
parentFrame.__updateTabs();
// If no more panels remain in this frame, remove the frame.
if (parentFrame._panelList.length === 0) {
var index = this._floatingList.indexOf(parentFrame);
if (index !== -1) {
this._floatingList.splice(index, 1);
}
index = this._frameList.indexOf(parentFrame);
if (index !== -1) {
this._frameList.splice(index, 1);
}
var parentSplitter = parentFrame._parent;
if (parentSplitter instanceof wcSplitter) {
parentSplitter.__removeChild(parentFrame);
var other;
if (parentSplitter.pane(0)) {
other = parentSplitter.pane(0);
parentSplitter._pane[0] = null;
} else {
other = parentSplitter.pane(1);
parentSplitter._pane[1] = null;
}
// Keep the item in a hidden transition container so as to not
// destroy any event handlers that may be on it.
other.__container(this.$transition);
other._parent = null;
index = this._splitterList.indexOf(parentSplitter);
if (index !== -1) {
this._splitterList.splice(index, 1);
}
var parent = parentSplitter._parent;
parentContainer = parentSplitter.__container();
parentSplitter.__destroy();
if (parent instanceof wcSplitter) {
parent.__removeChild(parentSplitter);
if (!parent.pane(0)) {
parent.pane(0, other);
} else {
parent.pane(1, other);
}
} else if (parent === this) {
this._root = other;
other._parent = this;
other.__container(parentContainer);
}
this.__update();
}
if (this._focusFrame === parentFrame) {
this._focusFrame = null;
}
parentFrame.__destroy();
}
}
panel.initSize(width, height);
if (location === wcDocker.DOCK_STACKED) {
this.__addPanelGrouped(panel, location, targetPanel);
} else {
this.__addPanelAlone(panel, location, targetPanel, rect);
}
var frame = panel._parent;
if (frame instanceof wcFrame) {
if (frame._panelList.length === 1) {
frame.pos(offset.left + width/2 + 20, offset.top + height/2 + 20, true);
}
}
this.__update();
if (frame instanceof wcFrame) {
if (floating !== frame._isFloating) {
if (frame._isFloating) {
panel.__trigger(wcDocker.EVENT_DETACHED);
} else {
panel.__trigger(wcDocker.EVENT_ATTACHED);
}
}
}
panel.__trigger(wcDocker.EVENT_MOVED);
return panel;
},
// Finds all instances of a given panel type.
// Params:
// typeName The type of panel.
// Returns:
// [wcPanel] A list of all panels of the given type.
findPanels: function(typeName) {
var result = [];
for (var i = 0; i < this._frameList.length; ++i) {
var frame = this._frameList[i];
for (var a = 0; a < frame._panelList.length; ++a) {
var panel = frame._panelList[a];
if (!typeName || panel._type === typeName) {
result.push(panel);
}
}
}
return result;
},
// Registers an event.
// Params:
// eventType The event type, as defined by wcDocker.EVENT_...
// handler A handler function to be called for the event.
// Params:
// panel The panel invoking the event.
// Returns:
// true The event was added.
// false The event failed to add.
on: function(eventType, handler) {
if (!eventType) {
return false;
}
if (!this._events[eventType]) {
this._events[eventType] = [];
}
if (this._events[eventType].indexOf(handler) !== -1) {
return false;
}
this._events[eventType].push(handler);
return true;
},
// Unregisters an event.
// Params:
// eventType The event type to remove, if omitted, all events are removed.
// handler The handler function to remove, if omitted, all events of
// the above type are removed.
off: function(eventType, handler) {
if (typeof eventType === 'undefined') {
this._events = {};
return;
} else {
if (this._events[eventType]) {
if (typeof handler === 'undefined') {
this._events[eventType] = [];
} else {
for (var i = 0; i < this._events[eventType].length; ++i) {
if (this._events[eventType][i] === handler) {
this._events[eventType].splice(i, 1);
break;
}
}
}
}
}
},
// Trigger an event on all panels.
// Params:
// eventName The name of the event.
// data A custom data parameter to pass to all handlers.
trigger: function(eventName, data) {
if (!eventName) {
return false;
}
for (var i = 0; i < this._frameList.length; ++i) {
var frame = this._frameList[i];
for (var a = 0; a < frame._panelList.length; ++a) {
var panel = frame._panelList[a];
panel.__trigger(eventName, data);
}
}
this.__trigger(eventName, data);
},
// Assigns a basic context menu to a selector element. The context
// Menu is a simple list of options, no nesting or special options.
//
// If you wish to use a more complex context menu, you can use
// $.contextMenu directly, see
// http://medialize.github.io/jQuery-contextMenu/docs.html
// for more information.
// Params:
// selector A JQuery selector string that designates the
// elements who use this menu.
// itemListOrBuildFunc An array with each context menu item in it, each item
// is an object {name:string, callback:function(key, opts, panel)}.
// This can also be a function that dynamically builds and
// returns the item list, parameters given are the $trigger object
// of the menu and the menu event object.
// includeDefault If true, all default panel menu options will also be shown.
basicMenu: function(selector, itemListOrBuildFunc, includeDefault) {
var self = this;
$.contextMenu({
selector: selector,
build: function($trigger, event) {
var myFrame;
for (var i = 0; i < self._frameList.length; ++i) {
var $frame = $trigger.hasClass('wcFrame') && $trigger || $trigger.parents('.wcFrame');
if (self._frameList[i].$frame[0] === $frame[0]) {
myFrame = self._frameList[i];
break;
}
}
var mouse = {
x: event.clientX,
y: event.clientY,
};
var isTitle = false;
if (mouse.y - myFrame.$frame.offset().top <= 20) {
isTitle = true;
}
var windowTypes = {};
for (var i = 0; i < self._dockPanelTypeList.length; ++i) {
var type = self._dockPanelTypeList[i];
if (!type.options.isPrivate) {
if (type.options.limit > 0) {
if (self.findPanels(type.name).length >= type.options.limit) {
continue;
}
}
var icon = null;
var faicon = null;
if (type.options) {
if (type.options.faicon) {
faicon = type.options.faicon;
}
if (type.options.icon) {
icon = type.options.icon;
}
}
windowTypes[type.name] = {
name: type.name,
icon: icon,
faicon: faicon,
className: 'wcMenuCreatePanel',
};
}
}
var separatorIndex = 0;
var finalItems = {};
var itemList = itemListOrBuildFunc;
if (typeof itemListOrBuildFunc === 'function') {
itemList = itemListOrBuildFunc($trigger, event);
}
for (var i = 0; i < itemList.length; ++i) {
if ($.isEmptyObject(itemList[i])) {
finalItems['sep' + separatorIndex++] = "---------";
continue;
}
var callback = itemList[i].callback;
if (callback) {
(function(listItem, callback) {
listItem.callback = function(key, opts) {
var panel = null;
var $frame = opts.$trigger.parents('.wcFrame').first();
if ($frame.length) {
for (var a = 0; a < self._frameList.length; ++a) {
if ($frame[0] === self._frameList[a].$frame[0]) {
panel = self._frameList[a].panel();
}
}
}
callback(key, opts, panel);
};
})(itemList[i], callback);
}
finalItems[itemList[i].name] = itemList[i];
}
var items = finalItems;
if (includeDefault) {
if (!$.isEmptyObject(finalItems)) {
items['sep' + separatorIndex++] = "---------";
}
if (isTitle) {
items['Close Panel'] = {
name: 'Close Tab',
faicon: 'close',
disabled: !myFrame.panel().closeable() || self.__isLastPanel(myFrame.panel()),
};
if (!myFrame._isFloating) {
items['Detach Panel'] = {
name: 'Detach Tab',
faicon: 'level-down',
disabled: !myFrame.panel().moveable() || self.__isLastPanel(myFrame.panel()),
};
}
items['sep' + separatorIndex++] = "---------";
items.fold1 = {
name: 'Add Tab',
faicon: 'columns',
items: windowTypes,
disabled: !(myFrame.panel()._titleVisible && (!myFrame._isFloating || self._modalList.indexOf(myFrame) === -1)),
className: 'wcMenuCreatePanel',
};
items['sep' + separatorIndex++] = "---------";
items['Flash Panel'] = {
name: 'Flash Panel',
faicon: 'lightbulb-o',
};
} else {
items['Close Panel'] = {
name: 'Close Panel',
faicon: 'close',
disabled: !myFrame.panel().closeable() || self.__isLastPanel(myFrame.panel()),
};
if (!myFrame._isFloating) {
items['Detach Panel'] = {
name: 'Detach Panel',
faicon: 'level-down',
disabled: !myFrame.panel().moveable() || self.__isLastPanel(myFrame.panel()),
};
}
items['sep' + separatorIndex++] = "---------";
items.fold1 = {
name: 'Insert Panel',
faicon: 'columns',
items: windowTypes,
disabled: !(!myFrame._isFloating && myFrame.panel().moveable()),
className: 'wcMenuCreatePanel',
};
items['sep' + separatorIndex++] = "---------";
items['Flash Panel'] = {
name: 'Flash Panel',
faicon: 'lightbulb-o',
};
}
if (!myFrame._isFloating && myFrame.panel().moveable()) {
var rect = myFrame.__rect();
self._ghost = new wcGhost(rect, mouse, self);
myFrame.__checkAnchorDrop(mouse, false, self._ghost, true);
self._ghost.$ghost.hide();
}
}
return {
callback: function(key, options) {
if (key === 'Close Panel') {
setTimeout(function() {
myFrame.panel().close();
}, 10);
} else if (key === 'Detach Panel') {
self.movePanel(myFrame.panel(), wcDocker.DOCK_FLOAT, false);
} else if (key === 'Flash Panel') {
self.__focus(myFrame, true);
} else {
if (myFrame && self._ghost) {
var anchor = self._ghost.anchor();
var newPanel = self.addPanel(key, anchor.loc, myFrame.panel(), self._ghost.rect());
newPanel.focus();
}
}
},
events: {
show: function(opt) {
(function(items){
// Whenever them menu is shown, we update and add the faicons.
// Grab all those menu items, and propogate a list with them.
var menuItems = {};
var options = opt.$menu.find('.context-menu-item');
for (var i = 0; i < options.length; ++i) {
var $option = $(options[i]);
var $span = $option.find('span');
if ($span.length) {
menuItems[$span[0].innerHTML] = $option;
}
}
// function calls itself so that we get nice icons inside of menus as well.
(function recursiveIconAdd(items) {
for(var it in items) {
var item = items[it];
var $menu = menuItems[item.name];
if ($menu) {
var $icon = $('<div class="wcMenuIcon">');
$menu.prepend($icon);
if (item.icon) {
$icon.addClass(item.icon);
}
if (item.faicon) {
$icon.addClass('fa fa-menu fa-' + item.faicon + ' fa-lg fa-fw');
}
// Custom submenu arrow.
if ($menu.hasClass('context-menu-submenu')) {
var $expander = $('<div class="wcMenuSubMenu fa fa-caret-right fa-lg">');
$menu.append($expander);
}
}
// Iterate through sub-menus.
if (item.items) {
recursiveIconAdd(item.items);
}
}
})(items);
})(items);
},
hide: function(opt) {
if (self._ghost) {
self._ghost.__destroy();
self._ghost = false;
}
},
},
animation: {duration: 250, show: 'fadeIn', hide: 'fadeOut'},
reposition: false,
autoHide: true,
zIndex: 200,
items: items,
};
},
});
},
// Bypasses the next context menu event.
// Use this during a mouse up event in which you do not want the
// context menu to appear.
bypassMenu: function() {
if (this._menuTimer) {
clearTimeout(this._menuTimer);
}
for (var i in $.contextMenu.menus) {
var menuSelector = $.contextMenu.menus[i].selector;
$(menuSelector).contextMenu(false);
}
var self = this;
this._menuTimer = setTimeout(function() {
for (var i in $.contextMenu.menus) {
var menuSelector = $.contextMenu.menus[i].selector;
$(menuSelector).contextMenu(true);
}
self._menuTimer = null;
}, 0);
},
// Saves the current panel configuration into a meta
// string that can be used later to restore it.
save: function() {
var data = {};
data.floating = [];
for (var i = 0; i < this._floatingList.length; ++i) {
data.floating.push(this._floatingList[i].__save());
}
data.root = this._root.__save();
return JSON.stringify(data, function(key, value) {
if (value == Infinity) {
return "Infinity";
}
return value;
});
},
// Restores a previously saved configuration.
restore: function(dataString) {
var data = JSON.parse(dataString, function(key, value) {
if (value === 'Infinity') {
return Infinity;
}
return value;
});
this.clear();
this._root = this.__create(data.root, this, this.$container);
this._root.__restore(data.root, this);
for (var i = 0; i < data.floating.length; ++i) {
var panel = this.__create(data.floating[i], this, this.$container);
panel.__restore(data.floating[i], this);
}
this.__update();
},
// Clears out all panels.
clear: function() {
this._root = null;
for (var i = 0; i < this._splitterList.length; ++i) {
this._splitterList[i].__destroy();
}
for (var i = 0; i < this._frameList.length; ++i) {
this._frameList[i].__destroy();
}
while (this._frameList.length) this._frameList.pop();
while (this._floatingList.length) this._floatingList.pop();
while (this._splitterList.length) this._splitterList.pop();
},
///////////////////////////////////////////////////////////////////////////////////////////////////////
// Private Functions
///////////////////////////////////////////////////////////////////////////////////////////////////////
__init: function() {
this._root = null;
var self = this;
$(window).resize(self.__resize.bind(self));
// Setup our context menus.
if ( this._options.allowContextMenu ) {
this.basicMenu('.wcFrame', [], true);
}
var contextTimer;
$('body').on('contextmenu', 'a, img', function() {
if (contextTimer) {
clearTimeout(contextTimer);
}
$(".wcFrame").contextMenu(false);
contextTimer = setTimeout(function() {
$(".wcFrame").contextMenu(true);
contextTimer = null;
}, 100);
return true;
});
$('body').on('contextmenu', '.wcSplitterBar', function() {
return false;
});
// Hovering over a panel creation context menu.
$('body').on('mouseenter', '.wcMenuCreatePanel', function() {
if (self._ghost) {
self._ghost.$ghost.stop().fadeIn(200);
}
});
$('body').on('mouseleave', '.wcMenuCreatePanel', function() {
if (self._ghost) {
self._ghost.$ghost.stop().fadeOut(200);
}
});
$('body').on('mousedown', '.wcModalBlocker', function(event) {
// for (var i = 0; i < self._modalList.length; ++i) {
// self._modalList[i].__focus(true);
// }
if (self._modalList.length) {
self._modalList[self._modalList.length-1].__focus(true);
}
});
// On some browsers, clicking and dragging a tab will drag it's graphic around.
// Here I am disabling this as it interferes with my own drag-drop.
$('body').on('mousedown', '.wcPanelTab', function(event) {
event.preventDefault();
event.returnValue = false;
});
$('body').on('selectstart', '.wcFrameTitle, .wcPanelTab, .wcFrameButton', function(event) {
event.preventDefault();
});
// Close button on frames should destroy those panels.
$('body').on('mousedown', '.wcFrame > .wcFrameButton', function() {
self.$container.addClass('wcDisableSelection');
});
// Clicking on a panel frame button.
$('body').on('click', '.wcFrame > .wcFrameButton', function() {
self.$container.removeClass('wcDisableSelection');
for (var i = 0; i < self._frameList.length; ++i) {
var frame = self._frameList[i];
if (frame.$close[0] === this) {
var panel = frame.panel();
self.removePanel(panel);
self.__update();
return;
}
if (frame.$tabLeft[0] === this) {
frame._tabScrollPos-=frame.$title.width()/2;
if (frame._tabScrollPos < 0) {
frame._tabScrollPos = 0;
}
frame.__updateTabs();
return;
}
if (frame.$tabRight[0] === this) {
frame._tabScrollPos+=frame.$title.width()/2;
frame.__updateTabs();
return;
}
for (var a = 0; a < frame._buttonList.length; ++a) {
if (frame._buttonList[a][0] === this) {
var $button = frame._buttonList[a];
var result = {
name: $button.data('name'),
isToggled: false,
}
if ($button.hasClass('wcFrameButtonToggler')) {
$button.toggleClass('wcFrameButtonToggled');
if ($button.hasClass('wcFrameButtonToggled')) {
result.isToggled = true;
}
}
var panel = frame.panel();
panel.buttonState(result.name, result.isToggled);
panel.__trigger(wcDocker.EVENT_BUTTON, result);
return;
}
}
}
});
// Clicking on a custom tab button.
$('body').on('click', '.wcCustomTab > .wcFrameButton', function() {
self.$container.removeClass('wcDisableSelection');
for (var i = 0; i < self._tabList.length; ++i) {
var customTab = self._tabList[i];
if (customTab.$close[0] === this) {
var tabIndex = customTab.tab();
customTab.removeTab(tabIndex);
return;
}
if (customTab.$tabLeft[0] === this) {
customTab._tabScrollPos-=customTab.$title.width()/2;
if (customTab._tabScrollPos < 0) {
customTab._tabScrollPos = 0;
}
customTab.__updateTabs();
return;
}
if (customTab.$tabRight[0] === this) {
customTab._tabScrollPos+=customTab.$title.width()/2;
customTab.__updateTabs();
return;
}
}
});
// Middle mouse button on a panel tab to close it.
$('body').on('mouseup', '.wcPanelTab', function(event) {
if (event.which !== 2) {
return;
}
var index = parseInt($(this).attr('id'));
for (var i = 0; i < self._frameList.length; ++i) {
var frame = self._frameList[i];
if (frame.$title[0] === $(this).parents('.wcFrameTitle')[0]) {
var panel = frame._panelList[index];
if (self._removingPanel === panel) {
self.removePanel(panel);
self.__update();
}
return;
}
}
});
// Mouse down on a splitter bar will allow you to resize them.
$('body').on('mousedown', '.wcSplitterBar', function(event) {
if (event.which !== 1) {
return true;
}
self.$container.addClass('wcDisableSelection');
for (var i = 0; i < self._splitterList.length; ++i) {
if (self._splitterList[i].$bar[0] === this) {
self._draggingSplitter = self._splitterList[i];
self._draggingSplitter.$pane[0].addClass('wcResizing');
self._draggingSplitter.$pane[1].addClass('wcResizing');
break;
}
}
return true;
});
// Mouse down on a frame title will allow you to move them.
$('body').on('mousedown', '.wcFrameTitle', function(event) {
if (event.which === 3) {
return true;
}
if ($(event.target).hasClass('wcFrameButton')) {
return false;
}
self.$container.addClass('wcDisableSelection');
for (var i = 0; i < self._frameList.length; ++i) {
if (self._frameList[i].$title[0] == this) {
self._draggingFrame = self._frameList[i];
var mouse = {
x: event.clientX,
y: event.clientY,
};
self._draggingFrame.__anchorMove(mouse);
var $panelTab = $(event.target).hasClass('wcPanelTab')? $(event.target): $(event.target).parent('.wcPanelTab');
if ($panelTab && $panelTab.length) {
var index = parseInt($panelTab.attr('id'));
self._draggingFrame.panel(index, true);
// if (event.which === 2) {
// self._draggingFrame = null;
// return;
// }
self._draggingFrameTab = $panelTab[0];
}
// If the window is able to be docked, give it a dark shadow tint and
// begin the movement process
if ((!self._draggingFrame.$title.hasClass('wcNotMoveable') && !$panelTab.hasClass('wcNotMoveable')) &&
(!self._draggingFrame._isFloating || event.which !== 1 || self._draggingFrameTab)) {
var rect = self._draggingFrame.__rect();
self._ghost = new wcGhost(rect, mouse, self);
self._draggingFrame.__checkAnchorDrop(mouse, true, self._ghost, true);
self.trigger(wcDocker.EVENT_BEGIN_DOCK);
}
break;
}
}
for (var i = 0; i < self._tabList.length; ++i) {
if (self._tabList[i].$title[0] == this) {
self._draggingCustomTabFrame = self._tabList[i];
var $panelTab = $(event.target).hasClass('wcPanelTab')? $(event.target): $(event.target).parent('.wcPanelTab');
if ($panelTab && $panelTab.length) {
var index = parseInt($panelTab.attr('id'));
self._draggingCustomTabFrame.tab(index, true);
self._draggingFrameTab = $panelTab[0];
}
break;
}
}
if (self._draggingFrame) {
self.__focus(self._draggingFrame);
}
return true;
});
// Mouse down on a panel will put it into focus.
$('body').on('mousedown', '.wcLayout', function(event) {
if (event.which === 3) {
return true;
}
for (var i = 0; i < self._frameList.length; ++i) {
if (self._frameList[i].panel().layout().scene()[0] == this) {
setTimeout(function() {
self.__focus(self._frameList[i]);
}, 10);
break;
}
}
return true;
});
// Floating frames have resizable edges.
$('body').on('mousedown', '.wcFrameEdge', function(event) {
if (event.which === 3) {
return true;
}
self.$container.addClass('wcDisableSelection');
for (var i = 0; i < self._frameList.length; ++i) {
if (self._frameList[i]._isFloating) {
if (self._frameList[i].$top[0] == this) {
self._draggingFrame = self._frameList[i];
self._draggingFrameSizer = ['top'];
break;
} else if (self._frameList[i].$bottom[0] == this) {
self._draggingFrame = self._frameList[i];
self._draggingFrameSizer = ['bottom'];
break;
} else if (self._frameList[i].$left[0] == this) {
self._draggingFrame = self._frameList[i];
self._draggingFrameSizer = ['left'];
break;
} else if (self._frameList[i].$right[0] == this) {
self._draggingFrame = self._frameList[i];
self._draggingFrameSizer = ['right'];
break;
} else if (self._frameList[i].$corner1[0] == this) {
self._draggingFrame = self._frameList[i];
self._draggingFrameSizer = ['top', 'left'];
break;
} else if (self._frameList[i].$corner2[0] == this) {
self._draggingFrame = self._frameList[i];
self._draggingFrameSizer = ['top', 'right'];
break;
} else if (self._frameList[i].$corner3[0] == this) {
self._draggingFrame = self._frameList[i];
self._draggingFrameSizer = ['bottom', 'right'];
break;
} else if (self._frameList[i].$corner4[0] == this) {
self._draggingFrame = self._frameList[i];
self._draggingFrameSizer = ['bottom', 'left'];
break;
}
}
}
if (self._draggingFrame) {
self.__focus(self._draggingFrame);
}
return true;
});
// Mouse move will allow you to move an object that is being dragged.
$('body').on('mousemove', function(event) {
if (event.which === 3) {
return true;
}
if (self._draggingSplitter) {
var mouse = {
x: event.clientX,
y: event.clientY,
};
self._draggingSplitter.__moveBar(mouse);
self._draggingSplitter.__update();
} else if (self._draggingFrameSizer) {
var mouse = {
x: event.clientX,
y: event.clientY,
};
var offset = self.$container.offset();
mouse.x += offset.left;
mouse.y += offset.top;
self._draggingFrame.__resize(self._draggingFrameSizer, mouse);
self._draggingFrame.__update();
} else if (self._draggingFrame) {
var mouse = {
x: event.clientX,
y: event.clientY,
};
if (self._ghost) {
self._ghost.__move(mouse);
var forceFloat = !(self._draggingFrame._isFloating || event.which === 1);
var found = false;
// Check anchoring with self.
if (!self._draggingFrame.__checkAnchorDrop(mouse, true, self._ghost, self._draggingFrame._panelList.length > 1 && self._draggingFrameTab)) {
self._draggingFrame.__shadow(true);
if (!forceFloat) {
for (var i = 0; i < self._frameList.length; ++i) {
if (self._frameList[i] !== self._draggingFrame) {
if (self._frameList[i].__checkAnchorDrop(mouse, false, self._ghost, true)) {
// self._draggingFrame.__shadow(true);
return;
}
}
}
}
self._ghost.anchor(mouse, null);
} else {
self._draggingFrame.__shadow(false);
var $hoverTab = $(event.target).hasClass('wcPanelTab')? $(event.target): $(event.target).parent('.wcPanelTab');
if (self._draggingFrameTab && $hoverTab && $hoverTab.length && self._draggingFrameTab !== event.target) {
self._draggingFrameTab = self._draggingFrame.__tabMove(parseInt($(self._draggingFrameTab).attr('id')), parseInt($hoverTab.attr('id')));
}
}
} else if (!self._draggingFrameTab) {
self._draggingFrame.__move(mouse);
self._draggingFrame.__update();
}
} else if (self._draggingCustomTabFrame) {
var $hoverTab = $(event.target).hasClass('wcPanelTab')? $(event.target): $(event.target).parent('.wcPanelTab');
if (self._draggingFrameTab && $hoverTab && $hoverTab.length && self._draggingFrameTab !== event.target) {
self._draggingFrameTab = self._draggingCustomTabFrame.moveTab(parseInt($(self._draggingFrameTab).attr('id')), parseInt($hoverTab.attr('id')));
}
}
return true;
});
// Mouse released
$('body').on('mouseup', function(event) {
if (event.which === 3) {
return true;
}
self.$container.removeClass('wcDisableSelection');
if (self._draggingFrame) {
for (var i = 0; i < self._frameList.length; ++i) {
self._frameList[i].__shadow(false);
}
}
if (self._ghost && self._draggingFrame) {
var anchor = self._ghost.anchor();
if (!anchor) {
var index = self._draggingFrame._curTab;
if (!self._draggingFrameTab) {
self._draggingFrame.panel(0);
}
var mouse = {
x: event.clientX,
y: event.clientY,
};
if (self._draggingFrameTab || !self.__isLastFrame(self._draggingFrame)) {
var panel = self.movePanel(self._draggingFrame.panel(), wcDocker.DOCK_FLOAT);
// Dragging the entire frame.
if (!self._draggingFrameTab) {
while (self._draggingFrame.panel())
self.movePanel(self._draggingFrame.panel(), wcDocker.DOCK_STACKED, panel);
}
var frame = panel._parent;
if (frame instanceof wcFrame) {
frame.pos(mouse.x, mouse.y + self._ghost.__rect().h/2 - 10, true);
frame.panel(index);
frame._size.x = self._ghost.__rect().w;
frame._size.y = self._ghost.__rect().h;
}
frame.__update();
}
} else if (!anchor.self) {
var index = self._draggingFrame._curTab;
if (!self._draggingFrameTab) {
self._draggingFrame.panel(0);
}
var panel;
if (anchor.item) {
panel = anchor.item._parent;
}
// If we are dragging a tab to split its own container, find another
// tab item within the same frame and split from there.
if (panel === self._draggingFrame.panel()) {
for (var i = 0; i < self._draggingFrame._panelList.length; ++i) {
if (panel !== self._draggingFrame._panelList[i]) {
panel = self._draggingFrame._panelList[i];
index--;
break;
}
}
}
panel = self.movePanel(self._draggingFrame.panel(), anchor.loc, panel, self._ghost.rect());
panel._parent.panel(panel._parent._panelList.length-1, true);
// Dragging the entire frame.
if (!self._draggingFrameTab) {
while (self._draggingFrame.panel()) {
self.movePanel(self._draggingFrame.panel(), wcDocker.DOCK_STACKED, panel, self._ghost.rect());
}
} else {
var frame = panel._parent;
if (frame instanceof wcFrame) {
index = index + frame._panelList.length;
}
}
var frame = panel._parent;
if (frame instanceof wcFrame) {
frame.panel(index);
}
}
self._ghost.destroy();
self._ghost = null;
self.trigger(wcDocker.EVENT_END_DOCK);
}
if ( self._draggingSplitter ) {
self._draggingSplitter.$pane[0].removeClass('wcResizing');
self._draggingSplitter.$pane[1].removeClass('wcResizing');
}
self._draggingSplitter = null;
self._draggingFrame = null;
self._draggingFrameSizer = null;
self._draggingFrameTab = null;
self._draggingCustomTabFrame = null;
self._removingPanel = null;
return true;
});
// Middle mouse button on a panel tab to close it.
$('body').on('mousedown', '.wcPanelTab', function(event) {
if (event.which !== 2) {
return;
}
var index = parseInt($(this).attr('id'));
for (var i = 0; i < self._frameList.length; ++i) {
var frame = self._frameList[i];
if (frame.$title[0] === $(this).parents('.wcFrameTitle')[0]) {
var panel = frame._panelList[index];
self._removingPanel = panel;
return;
}
}
});
},
// Updates the sizing of all panels inside this window.
__update: function() {
if (this._root) {
this._root.__update();
}
for (var i = 0; i < this._floatingList.length; ++i) {
this._floatingList[i].__update();
}
},
// On window resized event.
__resize: function(event) {
this._resizeData.time = new Date();
if (!this._resizeData.timeout) {
this._resizeData.timeout = true;
setTimeout(this.__resizeEnd.bind(this), this._resizeData.delta);
this.__trigger(wcDocker.EVENT_RESIZE_STARTED);
}
this.__trigger(wcDocker.EVENT_RESIZED);
this.__update();
},
// On window resize event ended.
__resizeEnd: function() {
if (new Date() - this._resizeData.time < this._resizeData.delta) {
setTimeout(this.__resizeEnd.bind(this), this._resizeData.delta);
} else {
this._resizeData.timeout = false;
this.__trigger(wcDocker.EVENT_RESIZE_ENDED);
}
},
// Brings a floating window to the top.
// Params:
// frame The frame to focus.
// flash Whether to flash the frame.
__focus: function(frame, flash) {
var reorder = this._focusFrame != frame;
if (this._focusFrame) {
if (this._focusFrame._isFloating) {
this._focusFrame.$frame.removeClass('wcFloatingFocus');
}
this._focusFrame.__trigger(wcDocker.EVENT_LOST_FOCUS);
this._focusFrame = null;
}
this._focusFrame = frame;
if (this._focusFrame) {
if (this._focusFrame._isFloating) {
this._focusFrame.$frame.addClass('wcFloatingFocus');
if (reorder) {
$('body').append(this._focusFrame.$frame);
}
}
this._focusFrame.__focus(flash);
this._focusFrame.__trigger(wcDocker.EVENT_GAIN_FOCUS);
}
},
// Triggers an event exclusively on the docker and none of its panels.
// Params:
// eventName The name of the event.
// data A custom data parameter to pass to all handlers.
__trigger: function(eventName, data) {
if (!eventName) {
return;
}
if (this._events[eventName]) {
for (var i = 0; i < this._events[eventName].length; ++i) {
this._events[eventName][i].call(this, data);
}
}
},
// Checks a given panel to see if it is the final remaining
// moveable panel in the docker.
// Params:
// panel The panel.
// Returns:
// true The panel is the last.
// false The panel is not the last.
__isLastPanel: function(panel) {
for (var i = 0; i < this._frameList.length; ++i) {
var testFrame = this._frameList[i];
if (testFrame._isFloating) {
continue;
}
for (var a = 0; a < testFrame._panelList.length; ++a) {
var testPanel = testFrame._panelList[a];
if (testPanel !== panel && testPanel.moveable()) {
return false;
}
}
}
return true;
},
// Checks a given frame to see if it is the final remaining
// moveable frame in the docker.
// Params:
// frame The frame.
// Returns:
// true The panel is the last.
// false The panel is not the last.
__isLastFrame: function(frame) {
for (var i = 0; i < this._frameList.length; ++i) {
var testFrame = this._frameList[i];
if (testFrame._isFloating || testFrame === frame) {
continue;
}
for (var a = 0; a < testFrame._panelList.length; ++a) {
var testPanel = testFrame._panelList[a];
if (testPanel.moveable()) {
return false;
}
}
}
return true;
},
// For restore, creates the appropriate object type.
__create: function(data, parent, $container) {
switch (data.type) {
case 'wcSplitter':
var splitter = new wcSplitter($container, parent, data.horizontal);
splitter.scrollable(0, false, false);
splitter.scrollable(1, false, false);
return splitter;
case 'wcFrame':
var frame = new wcFrame($container, parent, data.floating);
this._frameList.push(frame);
if (data.floating) {
this._floatingList.push(frame);
}
return frame;
case 'wcPanel':
for (var i = 0; i < this._dockPanelTypeList.length; ++i) {
if (this._dockPanelTypeList[i].name === data.panelType) {
var panel = new wcPanel(data.panelType, this._dockPanelTypeList[i].options);
panel._parent = parent;
panel.__container(this.$transition);
var options = (this._dockPanelTypeList[i].options && this._dockPanelTypeList[i].options.options) || {};
panel._panelObject = new this._dockPanelTypeList[i].options.onCreate(panel, options);
panel.__container($container);
break;
}
}
return panel;
}
return null;
},
// Creates a new frame for the panel and then attaches it
// to the window.
// Params:
// panel The panel to insert.
// location The desired location for the panel.
// parentPanel An optional panel to 'split', if not supplied the
// new panel will split the center window.
__addPanelAlone: function(panel, location, parentPanel, rect) {
// Floating windows need no placement.
if (location === wcDocker.DOCK_FLOAT || location === wcDocker.DOCK_MODAL) {
var frame = new wcFrame(this.$container, this, true);
this._frameList.push(frame);
this._floatingList.push(frame);
this.__focus(frame);
frame.addPanel(panel);
frame.pos(panel._pos.x, panel._pos.y, false);
if (location === wcDocker.DOCK_MODAL) {
frame.$modalBlocker = $('<div class="wcModalBlocker"></div>');
frame.$frame.prepend(frame.$modalBlocker);
panel.moveable(false);
frame.$frame.addClass('wcModal');
this._modalList.push(frame);
}
if (rect) {
if (rect.hasOwnProperty('x') && rect.hasOwnProperty('y')) {
frame.pos(rect.x + rect.w/2, rect.y + rect.h/2, true);
}
frame._size = {
x: rect.w,
y: rect.h,
};
}
return;
}
if (parentPanel) {
var parentFrame = parentPanel._parent;
if (parentFrame instanceof wcFrame) {
var parentSplitter = parentFrame._parent;
if (parentSplitter instanceof wcSplitter) {
var splitter;
var left = parentSplitter.pane(0);
var right = parentSplitter.pane(1);
var size = {
x: -1,
y: -1,
};
if (left === parentFrame) {
splitter = new wcSplitter(this.$transition, parentSplitter, location !== wcDocker.DOCK_BOTTOM && location !== wcDocker.DOCK_TOP);
parentSplitter.pane(0, splitter);
size.x = parentSplitter.$pane[0].width();
size.y = parentSplitter.$pane[0].height();
} else {
splitter = new wcSplitter(this.$transition, parentSplitter, location !== wcDocker.DOCK_BOTTOM && location !== wcDocker.DOCK_TOP);
parentSplitter.pane(1, splitter);
size.x = parentSplitter.$pane[1].width();
size.y = parentSplitter.$pane[1].height();
}
if (splitter) {
splitter.scrollable(0, false, false);
splitter.scrollable(1, false, false);
frame = new wcFrame(this.$transition, splitter, false);
this._frameList.push(frame);
if (location === wcDocker.DOCK_LEFT || location === wcDocker.DOCK_TOP) {
splitter.pane(0, frame);
splitter.pane(1, parentFrame);
} else {
splitter.pane(0, parentFrame);
splitter.pane(1, frame);
}
if (!rect) {
rect = {
w: panel._size.x,
h: panel._size.y,
};
}
if (rect) {
if (rect.w < 0) {
rect.w = size.x/2;
}
if (rect.h < 0) {
rect.h = size.y/2;
}
if (location === wcDocker.DOCK_LEFT) {
splitter.pos(rect.w / size.x);
} else if (location === wcDocker.DOCK_RIGHT) {
splitter.pos(1.0 - (rect.w / size.x));
} else if (location === wcDocker.DOCK_TOP) {
splitter.pos(rect.h / size.y);
} else {
splitter.pos(1.0 - (rect.h / size.y));
}
} else {
splitter.pos(0.5);
}
frame.addPanel(panel);
}
return;
}
}
}
var frame = new wcFrame(this.$transition, this, false);
this._frameList.push(frame);
if (!this._root) {
this._root = frame;
frame.__container(this.$container);
} else {
var splitter = new wcSplitter(this.$container, this, location !== wcDocker.DOCK_BOTTOM && location !== wcDocker.DOCK_TOP);
if (splitter) {
frame._parent = splitter;
splitter.scrollable(0, false, false);
splitter.scrollable(1, false, false);
var size = {
x: this.$container.width(),
y: this.$container.height(),
};
if (location === wcDocker.DOCK_LEFT || location === wcDocker.DOCK_TOP) {
splitter.pane(0, frame);
splitter.pane(1, this._root);
} else {
splitter.pane(0, this._root);
splitter.pane(1, frame);
}
if (!rect) {
splitter.__findBestPos();
} else {
if (rect.w < 0) {
rect.w = size.x/2;
}
if (rect.h < 0) {
rect.h = size.y/2;
}
if (location === wcDocker.DOCK_LEFT) {
splitter.pos(rect.w / size.x);
} else if (location === wcDocker.DOCK_RIGHT) {
splitter.pos(1.0 - (rect.w / size.x));
} else if (location === wcDocker.DOCK_TOP) {
splitter.pos(rect.h / size.y);
} else {
splitter.pos(1.0 - (rect.h / size.y));
}
}
this._root = splitter;
}
}
frame.addPanel(panel);
},
// Attempts to insert a given dock panel into an already existing frame.
// If insertion is not possible for any reason, the panel will be
// placed in its own frame instead.
// Params:
// panel The panel to insert.
// location The desired location for the panel.
// parentPanel An optional panel to 'split', if not supplied the
// new panel will split the center window.
__addPanelGrouped: function(panel, location, parentPanel) {
if (parentPanel) {
var frame = parentPanel._parent;
if (frame instanceof wcFrame) {
frame.addPanel(panel);
return;
}
}
// Floating windows need no placement.
if (location === wcDocker.DOCK_FLOAT) {
var frame;
if (this._floatingList.length) {
frame = this._floatingList[this._floatingList.length-1];
}
if (!frame) {
this.__addPanelAlone(panel, location);
return;
}
frame.addPanel(panel);
return;
}
var needsHorizontal = location !== wcDocker.DOCK_BOTTOM;
function ___iterateParents(item) {
if (item instanceof wcSplitter) {
var left = item.pane(0);
var right = item.pane(1);
// Check if the orientation of the splitter is one that we want.
if (item.orientation() === needsHorizontal) {
// Make sure the dock panel is on the proper side.
if (left instanceof wcFrame && (location === wcDocker.DOCK_LEFT || location === wcDocker.DOCK_TOP)) {
left.addPanel(panel);
return true;
} else if (right instanceof wcFrame && (location === wcDocker.DOCK_RIGHT || location === wcDocker.DOCK_BOTTOM)) {
right.addPanel(panel);
return true;
}
// This splitter was not valid, continue iterating through parents.
}
// If it isn't, iterate to which ever pane is not a dock panel.
if (!(left instanceof wcFrame)) {
return ___iterateParents.call(this, left);
} else {
return ___iterateParents.call(this, right);
}
}
return false;
};
if (!___iterateParents.call(this, this._root)) {
// If we did not manage to find a place for this panel, last resort is to put it in its own frame.
this.__addPanelAlone(panel, location);
}
},
};