gecko/browser/base/content/tabcandy/app/groups.js
Aza Raskin 092367501d + Added reverse-syncing of tab ordering: from tab bar order to group order.
- Before this commit, changing the order of tabs in a group would change
   the order of tabs in the tab bar on zoom-in.
 - After this commit, that continues to be the case. In addition, if you
   change the order of tabs in the tab bar, those changes will be reflected
   in the order of tabs in the group.
2010-05-03 18:05:43 -07:00

1133 lines
28 KiB
JavaScript

// Title: groups.js (revision-a)
(function(){
var numCmp = function(a,b){ return a-b; }
function min(list){ return list.slice().sort(numCmp)[0]; }
function max(list){ return list.slice().sort(numCmp).reverse()[0]; }
function isEventOverElement(event, el){
var hit = {nodeName: null};
var isOver = false;
var hiddenEls = [];
while(hit.nodeName != "BODY" && hit.nodeName != "HTML"){
hit = document.elementFromPoint(event.clientX, event.clientY);
if( hit == el ){
isOver = true;
break;
}
$(hit).hide();
hiddenEls.push(hit);
}
var hidden;
[$(hidden).show() for([,hidden] in Iterator(hiddenEls))];
return isOver;
}
// ##########
// Class: Group
// A single group in the tab candy window. Descended from <Item>.
// Note that it implements the <Subscribable> interface.
window.Group = function(listOfEls, options) {
if(typeof(options) == 'undefined')
options = {};
this._children = []; // an array of Items
this.defaultSize = new Point(TabItems.tabWidth * 1.5, TabItems.tabHeight * 1.5);
this.locked = options.locked || false;
this.isAGroup = true;
this.id = options.id || Groups.getNextID();
this.userSize = options.userSize || null;
this._isStacked = false;
this._stackAngles = [0];
this.expanded = null;
var self = this;
var rectToBe;
if(options.bounds)
rectToBe = new Rect(options.bounds);
if(!rectToBe) {
var boundingBox = this._getBoundingBox(listOfEls);
var padding = 30;
rectToBe = new Rect(
boundingBox.left-padding,
boundingBox.top-padding,
boundingBox.width+padding*2,
boundingBox.height+padding*2
);
}
var $container = options.container;
if(!$container) {
$container = $('<div class="group" />')
.css({position: 'absolute'})
.css(rectToBe);
}
$container
.css({zIndex: -100})
.appendTo("body")
.dequeue();
// ___ New Tab Button
this.$ntb = $("<div class='newTabButton'/>").appendTo($container)
function zoomIn() {
var box = self.getContentBounds();
if(!self._isStacked) {
var rects = Items.arrange(null, box, {pretend: true, count: self._children.length + 1});
if(rects.length)
box = rects[rects.length - 1];
}
anim = $("<div class='newTabAnimatee'/>").css({
width: self.$ntb.width(),
height: self.$ntb.height(),
top: self.$ntb.offset().top,
left: self.$ntb.offset().left,
zIndex: 999
})
.appendTo("body")
.animate({
top: box.top,
left: box.left,
width: box.width,
height: box.height,
}, 200)//, "tabcandyBounce")
.animate({
top: 0,
left: 0,
width: window.innerWidth,
height: window.innerHeight
}, 250, function(){
anim.remove();
Groups.setActiveGroup(self);
var newTab = Tabs.open("about:blank").focus();
UI.tabBar.show(false);
UI.navBar.urlBar.focus();
anim.remove();
// We need a timeout here so that there is a chance for the
// new tab to get made! Otherwise it won't appear in the list
// of the group's tab.
// TODO: This is probably a terrible hack that sets up a race
// condition. We need a better solution.
setTimeout(function(){
UI.tabBar.showOnlyTheseTabs(Groups.getActiveGroup()._children);
}, 400);
});
}
this.$ntb.click(function(){ zoomIn(); });
// ___ Resizer
this.$resizer = $("<div class='resizer'/>")
.css({
position: "absolute",
width: 16, height: 16,
bottom: 0, right: 0,
})
.appendTo($container)
.hide();
// ___ Titlebar
var html = "<div class='titlebar'><input class='name' value='"
+ (options.title || "")
+ "'/><div class='close'></div></div>";
this.$titlebar = $(html)
.appendTo($container);
this.$titlebar.css({
position: "absolute",
});
var $close = $('.close', this.$titlebar).click(function() {
self.closeAll();
});
// ___ Title
var titleUnfocus = function() {
if(!self.getTitle()) {
self.$title
.addClass("defaultName")
.val(self.defaultName);
} else {
self.$title.css({"background":"none"})
.animate({"paddingLeft":1}, 340, "tabcandyBounce");
}
};
var handleKeyPress = function(e){
if( e.which == 13 ) { // return
self.$title.blur()
.addClass("transparentBorder")
.one("mouseout", function(){
self.$title.removeClass("transparentBorder");
});
}
}
this.$title = $('.name', this.$titlebar)
.css({backgroundRepeat: 'no-repeat'})
.blur(titleUnfocus)
.focus(function() {
if(self.locked) {
self.$title.blur();
return;
}
self.$title.select();
if(!self.getTitle()) {
self.$title
.removeClass("defaultName")
.val('');
}
})
.keydown(handleKeyPress);
titleUnfocus();
if(this.locked)
this.$title.addClass('name-locked');
// ___ Content
this.$content = $('<div class="group-content"/>')
.css({
left: 0,
top: this.$titlebar.height(),
position: 'absolute'
})
.appendTo($container);
// ___ locking
if(this.locked) {
$container.css({cursor: 'default'});
/* $close.hide(); */
}
// ___ Superclass initialization
this._init($container.get(0));
if(this.$debug)
this.$debug.css({zIndex: -1000});
// ___ Children
$.each(listOfEls, function(index, el) {
self.add(el, null, options);
});
// ___ Finish Up
this._addHandlers($container);
if(!this.locked)
this.setResizable(true);
Groups.register(this);
this.setBounds(rectToBe);
// ___ Push other objects away
if(!options.dontPush)
this.pushAway();
};
// ----------
window.Group.prototype = $.extend(new Item(), new Subscribable(), {
// ----------
defaultName: "name this group...",
// ----------
getStorageData: function() {
var data = {
bounds: this.getBounds(),
userSize: this.userSize,
locked: this.locked,
title: this.getTitle(),
id: this.id
};
return data;
},
// ----------
getTitle: function() {
var value = (this.$title ? this.$title.val() : '');
return (value == this.defaultName ? '' : value);
},
// ----------
setValue: function(value) {
this.$title.val(value);
},
// ----------
_getBoundingBox: function(els) {
var el;
var boundingBox = {
top: min( [$(el).position().top for([,el] in Iterator(els))] ),
left: min( [$(el).position().left for([,el] in Iterator(els))] ),
bottom: max( [$(el).position().top for([,el] in Iterator(els))] ) + $(els[0]).height(),
right: max( [$(el).position().left for([,el] in Iterator(els))] ) + $(els[0]).width(),
};
boundingBox.height = boundingBox.bottom - boundingBox.top;
boundingBox.width = boundingBox.right - boundingBox.left;
return boundingBox;
},
// ----------
getContentBounds: function() {
var box = this.getBounds();
var titleHeight = this.$titlebar.height();
box.top += titleHeight;
box.height -= titleHeight;
box.inset(6, 6);
box.height -= 15; // For new tab button
return box;
},
// ----------
reloadBounds: function() {
var bb = Utils.getBounds(this.container);
if(!this.bounds)
this.bounds = new Rect(0, 0, 0, 0);
this.setBounds(bb, true);
},
// ----------
setBounds: function(rect, immediately) {
var titleHeight = this.$titlebar.height();
// ___ Determine what has changed
var css = {};
var titlebarCSS = {};
var contentCSS = {};
if(rect.left != this.bounds.left)
css.left = rect.left;
if(rect.top != this.bounds.top)
css.top = rect.top;
if(rect.width != this.bounds.width) {
css.width = rect.width;
titlebarCSS.width = rect.width;
contentCSS.width = rect.width;
}
if(rect.height != this.bounds.height) {
css.height = rect.height;
contentCSS.height = rect.height - titleHeight;
}
if($.isEmptyObject(css))
return;
var offset = new Point(rect.left - this.bounds.left, rect.top - this.bounds.top);
this.bounds = new Rect(rect);
// ___ Deal with children
if(this._children.length) {
if(css.width || css.height) {
this.arrange({animate: !immediately}); //(immediately ? 'sometimes' : true)});
} else if(css.left || css.top) {
$.each(this._children, function(index, child) {
var box = child.getBounds();
child.setPosition(box.left + offset.x, box.top + offset.y, immediately);
});
}
}
// ___ Update our representation
if(immediately) {
$(this.container).css(css);
this.$titlebar.css(titlebarCSS);
this.$content.css(contentCSS);
} else {
TabMirror.pausePainting();
$(this.container).animate(css, {
complete: function() {TabMirror.resumePainting();},
easing: "tabcandyBounce"
}).dequeue();
this.$titlebar.animate(titlebarCSS).dequeue();
this.$content.animate(contentCSS).dequeue();
}
this._updateDebugBounds();
},
// ----------
setZ: function(value) {
$(this.container).css({zIndex: value});
if(this.$debug)
this.$debug.css({zIndex: value + 1});
var count = this._children.length;
if(count) {
var zIndex = value + count + 1;
$.each(this._children, function(index, child) {
child.setZ(zIndex);
zIndex--;
});
}
},
// ----------
close: function() {
this.removeAll();
this._sendOnClose();
Groups.unregister(this);
$(this.container).fadeOut(function() {
$(this).remove();
});
},
// ----------
closeAll: function() {
if(this._children.length) {
var toClose = $.merge([], this._children);
$.each(toClose, function(index, child) {
child.close();
});
} else if(!this.locked)
this.close();
},
// ----------
// Function: add
// Adds an item to the group.
// Parameters:
//
// a - The item to add. Can be an <Item>, a DOM element or a jQuery object.
// The latter two must refer to the container of an <Item>.
// dropPos - An object with left and top properties referring to the location dropped at. Optional.
// options - An object with optional settings for this call. Currently the only one is dontArrange.
add: function(a, dropPos, options) {
var item;
var $el;
if(a.isAnItem) {
item = a;
$el = $(a.container);
} else {
$el = $(a);
item = Items.item($el);
}
Utils.assert('shouldn\'t already be in a group', !item.parent || item.parent == this);
if(!dropPos)
dropPos = {top:window.innerWidth, left:window.innerHeight};
if(typeof(options) == 'undefined')
options = {};
var self = this;
// TODO: You should be allowed to drop in the white space at the bottom and have it go to the end
// (right now it can match the thumbnail above it and go there)
function findInsertionPoint(dropPos){
if(self._isStacked)
return 0;
var best = {dist: Infinity, item: null};
var index = 0;
var box;
$.each(self._children, function(index, child) {
box = child.getBounds();
if(box.bottom < dropPos.top || box.top > dropPos.top)
return;
var dist = Math.sqrt( Math.pow((box.top+box.height/2)-dropPos.top,2) + Math.pow((box.left+box.width/2)-dropPos.left,2) );
if( dist <= best.dist ){
best.item = child;
best.dist = dist;
best.index = index;
}
});
if( self._children.length > 0 ){
if(best.item) {
box = best.item.getBounds();
var insertLeft = dropPos.left <= box.left + box.width/2;
if( !insertLeft )
return best.index+1;
else
return best.index;
} else
return self._children.length;
}
return 0;
}
var wasAlreadyInThisGroup = false;
var oldIndex = $.inArray(item, this._children);
if(oldIndex != -1) {
this._children.splice(oldIndex, 1);
wasAlreadyInThisGroup = true;
}
// Insert the tab into the right position.
var index = findInsertionPoint(dropPos);
this._children.splice( index, 0, item );
item.setZ(this.getZ() + 1);
$el.addClass("tabInGroup");
if(!wasAlreadyInThisGroup) {
$el.droppable("disable");
item.groupData = {};
item.addOnClose(this, function() {
self.remove($el);
});
item.parent = this;
if(typeof(item.setResizable) == 'function')
item.setResizable(false);
}
if(!options.dontArrange)
this.arrange();
},
// ----------
// Function: remove
// Removes an item from the group.
// Parameters:
//
// a - The item to remove. Can be an <Item>, a DOM element or a jQuery object.
// The latter two must refer to the container of an <Item>.
// options - An object with optional settings for this call. Currently the only one is dontArrange.
remove: function(a, options) {
var $el;
var item;
if(a.isAnItem) {
item = a;
$el = $(item.container);
} else {
$el = $(a);
item = Items.item($el);
}
if(typeof(options) == 'undefined')
options = {};
var index = $.inArray(item, this._children);
if(index != -1)
this._children.splice(index, 1);
item.parent = null;
$el.removeClass("tabInGroup");
item.setSize(item.defaultSize.x, item.defaultSize.y);
$el.droppable("enable");
item.removeOnClose(this);
if(typeof(item.setResizable) == 'function')
item.setResizable(true);
if(this._children.length == 0 && !this.locked && !this.getTitle()){
this.close();
} else if(!options.dontArrange) {
this.arrange();
}
},
// ----------
removeAll: function() {
var self = this;
var toRemove = $.merge([], this._children);
$.each(toRemove, function(index, child) {
self.remove(child, {dontArrange: true});
});
},
// ----------
arrange: function(options) {
if(this.expanded)
return;
var count = this._children.length;
if(!count)
return;
var bb = this.getContentBounds();
if((bb.width * bb.height) / count > 7000) {
this._children.forEach(function(child){
child.removeClass("stacked")
});
Items.arrange(this._children, bb, options);
this._isStacked = false;
} else
this._stackArrange(bb, options);
},
// ----------
_stackArrange: function(bb, options) {
var animate;
if(!options || typeof(options.animate) == 'undefined')
animate = true;
else
animate = options.animate;
if(typeof(options) == 'undefined')
options = {};
var count = this._children.length;
if(!count)
return;
var zIndex = this.getZ() + count + 1;
var scale = 0.8;
var w;
var h;
var itemAspect = TabItems.tabHeight / TabItems.tabWidth;
var bbAspect = bb.height / bb.width;
if(bbAspect > itemAspect) { // Tall, thin group
w = bb.width * scale;
h = w * itemAspect;
} else { // Short, wide group
h = bb.height * scale;
w = h * (1 / itemAspect);
}
var x = (bb.width - w) / 2;
var y = Math.min(x, (bb.height - h) / 2);
var box = new Rect(bb.left + x, bb.top + y, w, h);
var self = this;
$.each(this._children, function(index, child) {
if(!child.locked) {
child.setZ(zIndex);
zIndex--;
child.setBounds(box, !animate);
child.setRotation(self._randRotate(35, index));
child.addClass("stacked");
}
});
self._isStacked = true;
},
// ----------
_randRotate: function(spread, index){
if( index >= this._stackAngles.length ){
var randAngle = parseInt( ((Math.random()+.6)/1.3)*spread-(spread/2) );
this._stackAngles.push(randAngle);
return randAngle;
}
return this._stackAngles[index];
},
// ----------
childHit: function(child) {
var self = this;
// ___ normal click
if(!this._isStacked || this.expanded) {
setTimeout(function() {
self.collapse();
}, 200);
return false;
}
// ___ we're stacked, so expand
Groups.setActiveGroup(self);
var startBounds = child.getBounds();
var $tray = $("<div />").css({
top: startBounds.top,
left: startBounds.left,
width: startBounds.width,
height: startBounds.height,
position: "absolute",
zIndex: 99998
}).appendTo("body");
var w = 180;
var h = w * (TabItems.tabHeight / TabItems.tabWidth) * 1.1;
var padding = 20;
var col = Math.ceil(Math.sqrt(this._children.length));
var row = Math.ceil(this._children.length/col);
var overlayWidth = Math.min(window.innerWidth - (padding * 2), w*col + padding*(col+1));
var overlayHeight = Math.min(window.innerHeight - (padding * 2), h*row + padding*(row+1));
var pos = {left: startBounds.left, top: startBounds.top};
pos.left -= overlayWidth/3;
pos.top -= overlayHeight/3;
if( pos.top < 0 ) pos.top = 20;
if( pos.left < 0 ) pos.left = 20;
if( pos.top+overlayHeight > window.innerHeight ) pos.top = window.innerHeight-overlayHeight-20;
if( pos.left+overlayWidth > window.innerWidth ) pos.left = window.innerWidth-overlayWidth-20;
$tray.animate({
width: overlayWidth,
height: overlayHeight,
top: pos.top,
left: pos.left
}, 350, "tabcandyBounce").addClass("overlay");
this._children.forEach(function(child){
child.addClass("stack-trayed");
});
var box = new Rect(pos.left, pos.top, overlayWidth, overlayHeight);
box.inset(8, 8);
Items.arrange(this._children, box, {padding: 8, z: 99999});
var $shield = $('<div />')
.css({
left: 0,
top: 0,
width: window.innerWidth,
height: window.innerHeight,
position: 'absolute',
zIndex: 99997
})
.appendTo('body')
.mouseover(function() {
self.collapse();
})
.click(function() { // just in case
self.collapse();
});
this.expanded = {
$tray: $tray,
$shield: $shield
};
return true;
},
// ----------
collapse: function() {
if(this.expanded) {
var z = this.getZ();
var box = this.getBounds();
this.expanded.$tray
.css({
zIndex: z + 1
})
.animate({
width: box.width,
height: box.height,
top: box.top,
left: box.left,
opacity: 0
}, 350, "tabcandyBounce", function() {
$(this).remove();
});
this.expanded.$shield.remove();
this.expanded = null;
this._children.forEach(function(child){
child.removeClass("stack-trayed");
});
this.arrange({z: z + 2});
}
},
// ----------
_addHandlers: function(container) {
var self = this;
if(!this.locked) {
$(container).draggable({
scroll: false,
cancel: '.close, .name',
start: function(e, ui){
drag.info = new DragInfo(this, e);
},
drag: function(e, ui){
drag.info.drag(e, ui);
},
stop: function() {
drag.info.stop();
drag.info = null;
}
});
}
$(container).droppable({
tolerance: "intersect",
over: function(){
drag.info.$el.addClass("willGroup");
},
out: function(){
var group = drag.info.item.parent;
if(group) {
group.remove(drag.info.$el);
}
drag.info.$el.removeClass("willGroup");
},
drop: function(event){
drag.info.$el.removeClass("willGroup");
self.add( drag.info.$el, {left:event.pageX, top:event.pageY} );
},
accept: ".tab", //".tab, .group",
});
},
// ----------
setResizable: function(value){
var self = this;
if(value) {
this.$resizer.fadeIn();
$(this.container).resizable({
handles: "se",
aspectRatio: false,
minWidth: 60,
minHeight: 90,
resize: function(){
self.reloadBounds();
},
stop: function(){
self.reloadBounds();
self.setUserSize();
self.pushAway();
}
});
} else {
this.$resizer.fadeOut();
$(this.container).resizable('disable');
}
},
reorderBasedOnTabOrder: function(){
// Reorderes the tabs in a group based on the arrangment of the tabs
// shown in the tab bar. It doesn't it by sorting the children
// of the group by the positions of their respective tabs in the
// tab bar.
var groupTabs = [];
for( var i=0; i<UI.tabBar.el.children.length; i++ ){
var tab = UI.tabBar.el.children[i];
if( tab.collapsed == false )
groupTabs.push(tab);
}
this._children.sort(function(a,b){
return groupTabs.indexOf(a.tab.raw) - groupTabs.indexOf(b.tab.raw)
})
}
});
// ##########
var DragInfo = function(element, event) {
this.el = element;
this.$el = $(this.el);
this.item = Items.item(this.el);
this.parent = this.item.parent;
this.startPosition = new Point(event.clientX, event.clientY);
this.startTime = Utils.getMilliseconds();
this.$el.data('isDragging', true);
this.item.setZ(99999);
};
DragInfo.prototype = {
// ----------
drag: function(event, ui) {
if(this.item.isAGroup) {
var bb = this.item.getBounds();
bb.left = ui.position.left;
bb.top = ui.position.top;
this.item.setBounds(bb, true);
} else
this.item.reloadBounds();
if(this.parent && this.parent.expanded) {
var now = Utils.getMilliseconds();
var distance = this.startPosition.distance(new Point(event.clientX, event.clientY));
if(now - this.startTime > 500 && distance > 100) {
this.item.locked = true;
this.parent.collapse();
this.item.locked = false;
}
}
},
// ----------
stop: function() {
this.$el.data('isDragging', false);
if(this.parent && !this.parent.locked && this.parent != this.item.parent
&& this.parent._children.length == 1 && !this.parent.getTitle()) {
this.parent.remove(this.parent._children[0]);
}
if(this.item && !this.$el.hasClass('willGroup') && !this.item.parent) {
this.item.setZ(drag.zIndex);
drag.zIndex++;
this.item.reloadBounds();
this.item.pushAway();
}
}
};
var drag = {
info: null,
zIndex: 100
};
// ##########
// Class: Groups
// Singelton for managing all <Group>s.
window.Groups = {
// ----------
dragOptions: {
scroll: false,
cancel: '.close',
start: function(e, ui) {
drag.info = new DragInfo(this, e);
},
drag: function(e, ui) {
drag.info.drag(e, ui);
},
stop: function() {
drag.info.stop();
drag.info = null;
}
},
// ----------
dropOptions: {
accept: ".tab",
tolerance: "intersect",
greedy: true,
drop: function(e){
$target = $(e.target);
drag.info.$el.removeClass("willGroup")
var phantom = $target.data("phantomGroup")
var group = drag.info.item.parent;
if( group == null ){
phantom.removeClass("phantom");
phantom.removeClass("group-content");
var group = new Group([$target, drag.info.$el], {container:phantom});
} else
group.add( drag.info.$el );
},
over: function(e){
var $target = $(e.target);
function elToRect($el){
return new Rect( $el.position().left, $el.position().top, $el.width(), $el.height() );
}
var height = elToRect($target).height * 1.5 + 20;
var width = elToRect($target).width * 1.5 + 20;
var unionRect = elToRect($target).union( elToRect(drag.info.$el) );
var newLeft = unionRect.left + unionRect.width/2 - width/2;
var newTop = unionRect.top + unionRect.height/2 - height/2;
$(".phantom").remove();
var phantom = $("<div class='group phantom group-content'/>").css({
width: width,
height: height,
position:"absolute",
top: newTop,
left: newLeft,
zIndex: -99
}).appendTo("body").hide().fadeIn();
$target.data("phantomGroup", phantom);
},
out: function(e){
$(e.target).data("phantomGroup").fadeOut(function(){
$(this).remove();
});
}
},
// ----------
init: function() {
this.groups = [];
this.nextID = 1;
},
// ----------
getNextID: function() {
var result = this.nextID;
this.nextID++;
return result;
},
// ----------
getStorageData: function() {
var data = {nextID: this.nextID, groups: []};
$.each(this.groups, function(index, group) {
data.groups.push(group.getStorageData());
});
return data;
},
// ----------
reconstitute: function(data) {
if(data && data.nextID)
this.nextID = data.nextID;
if(data && data.groups) {
$.each(data.groups, function(index, group) {
new Group([], $.extend({}, group, {locked: false, dontPush: true}));
});
}
var group = this.getNewTabGroup();
if(!group) {
var box = this.getBoundsForNewTabGroup();
new Group([], {bounds: box, title: 'New Tabs', dontPush: true});
}
},
// ----------
getNewTabGroup: function() {
var groupTitle = 'New Tabs';
var array = jQuery.grep(this.groups, function(group) {
return group.getTitle() == groupTitle;
});
if(array.length)
return array[0];
return null;
},
// ----------
getBoundsForNewTabGroup: function() {
var pad = 20;
var sw = window.innerWidth;
var sh = window.innerHeight;
//var w = sw - (pad * 2);
var w = TabItems.tabWidth*2 + pad*2;
var h = TabItems.tabHeight*1.2 + pad*2;
return new Rect(pad, sh - (h + pad), w, h);
},
// ----------
repositionNewTabGroup: function() {
var box = this.getBoundsForNewTabGroup();
var group = this.getNewTabGroup();
group.setBounds(box, true);
},
// ----------
register: function(group) {
Utils.assert('only register once per group', $.inArray(group, this.groups) == -1);
this.groups.push(group);
},
// ----------
unregister: function(group) {
var index = $.inArray(group, this.groups);
if(index != -1)
this.groups.splice(index, 1);
},
// ----------
// Function: group
// Given some sort of identifier, returns the appropriate group.
// Currently only supports group ids.
group: function(a) {
var result = null;
$.each(this.groups, function(index, candidate) {
if(candidate.id == a) {
result = candidate;
return false;
}
});
return result;
},
// ----------
arrange: function() {
var count = this.groups.length;
var columns = Math.ceil(Math.sqrt(count));
var rows = ((columns * columns) - count >= columns ? columns - 1 : columns);
var padding = 12;
var startX = padding;
var startY = Page.startY;
var totalWidth = window.innerWidth - startX;
var totalHeight = window.innerHeight - startY;
var box = new Rect(startX, startY,
(totalWidth / columns) - padding,
(totalHeight / rows) - padding);
$.each(this.groups, function(index, group) {
group.setBounds(box, true);
box.left += box.width + padding;
if(index % columns == columns - 1) {
box.left = startX;
box.top += box.height + padding;
}
});
},
// ----------
removeAll: function() {
var toRemove = $.merge([], this.groups);
$.each(toRemove, function(index, group) {
group.removeAll();
});
},
// ----------
newTab: function(tabItem) {
//var group = this.getActiveGroup() ? this.getActiveGroup() : this.getNewTabGroup();
var group = this.getActiveGroup();
if( group == null )
group = this.getNewTabGroup();
var $el = $(tabItem.container);
if(group)
group.add($el);
},
// ----------
getActiveGroup: function() {
return this._activeGroup;
},
// ----------
setActiveGroup: function(group) {
this._activeGroup = group;
if(group)
UI.tabBar.showOnlyTheseTabs( group._children );
}
};
// ----------
Groups.init();
// ##########
$(".tab").data('isDragging', false)
.draggable(window.Groups.dragOptions)
.droppable(window.Groups.dropOptions);
})();