mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
092367501d
- 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.
1133 lines
28 KiB
JavaScript
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);
|
|
|
|
})(); |