2010-06-16 17:19:11 -07:00
|
|
|
/* ***** BEGIN LICENSE BLOCK *****
|
|
|
|
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
|
|
|
*
|
|
|
|
* The contents of this file are subject to the Mozilla Public License Version
|
|
|
|
* 1.1 (the "License"); you may not use this file except in compliance with
|
|
|
|
* the License. You may obtain a copy of the License at
|
|
|
|
* http://www.mozilla.org/MPL/
|
|
|
|
*
|
|
|
|
* Software distributed under the License is distributed on an "AS IS" basis,
|
|
|
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
|
|
* for the specific language governing rights and limitations under the
|
|
|
|
* License.
|
|
|
|
*
|
2010-08-06 15:46:55 -07:00
|
|
|
* The Original Code is groupItems.js.
|
2010-06-16 17:19:11 -07:00
|
|
|
*
|
|
|
|
* The Initial Developer of the Original Code is
|
2010-08-11 15:28:45 -07:00
|
|
|
* the Mozilla Foundation.
|
2010-06-16 17:19:11 -07:00
|
|
|
* Portions created by the Initial Developer are Copyright (C) 2010
|
|
|
|
* the Initial Developer. All Rights Reserved.
|
|
|
|
*
|
|
|
|
* Contributor(s):
|
2010-08-11 15:28:45 -07:00
|
|
|
* Ian Gilman <ian@iangilman.com>
|
2010-06-16 17:19:11 -07:00
|
|
|
* Aza Raskin <aza@mozilla.com>
|
|
|
|
* Michael Yoshitaka Erlewine <mitcho@mitcho.com>
|
|
|
|
* Ehsan Akhgari <ehsan@mozilla.com>
|
2010-07-14 19:40:46 -07:00
|
|
|
* Raymond Lee <raymond@appcoast.com>
|
2011-01-21 22:25:12 -08:00
|
|
|
* Tim Taubert <tim.taubert@gmx.de>
|
2010-09-26 23:25:00 -07:00
|
|
|
* Sean Dunn <seanedunn@yahoo.com>
|
2010-06-16 17:19:11 -07:00
|
|
|
*
|
|
|
|
* Alternatively, the contents of this file may be used under the terms of
|
|
|
|
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
|
|
|
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
|
|
|
* in which case the provisions of the GPL or the LGPL are applicable instead
|
|
|
|
* of those above. If you wish to allow use of your version of this file only
|
|
|
|
* under the terms of either the GPL or the LGPL, and not to allow others to
|
|
|
|
* use your version of this file under the terms of the MPL, indicate your
|
|
|
|
* decision by deleting the provisions above and replace them with the notice
|
|
|
|
* and other provisions required by the GPL or the LGPL. If you do not delete
|
|
|
|
* the provisions above, a recipient may use your version of this file under
|
|
|
|
* the terms of any one of the MPL, the GPL or the LGPL.
|
|
|
|
*
|
|
|
|
* ***** END LICENSE BLOCK ***** */
|
|
|
|
|
|
|
|
// **********
|
2010-08-06 15:46:55 -07:00
|
|
|
// Title: groupItems.js
|
2010-06-16 17:19:11 -07:00
|
|
|
|
2010-03-25 17:22:45 -07:00
|
|
|
// ##########
|
2010-08-06 15:46:55 -07:00
|
|
|
// Class: GroupItem
|
|
|
|
// A single groupItem in the TabView window. Descended from <Item>.
|
2010-05-26 11:29:31 -07:00
|
|
|
// Note that it implements the <Subscribable> interface.
|
2010-07-18 08:58:10 -07:00
|
|
|
//
|
2010-06-11 15:08:14 -07:00
|
|
|
// ----------
|
2010-08-06 15:46:55 -07:00
|
|
|
// Constructor: GroupItem
|
2010-07-18 08:58:10 -07:00
|
|
|
//
|
|
|
|
// Parameters:
|
2010-08-06 15:46:55 -07:00
|
|
|
// listOfEls - an array of DOM elements for tabs to be added to this groupItem
|
|
|
|
// options - various options for this groupItem (see below). In addition, gets passed
|
2010-07-18 08:58:10 -07:00
|
|
|
// to <add> along with the elements provided.
|
|
|
|
//
|
|
|
|
// Possible options:
|
2010-08-06 15:46:55 -07:00
|
|
|
// id - specifies the groupItem's id; otherwise automatically generated
|
2010-06-11 15:08:14 -07:00
|
|
|
// locked - see <Item.locked>; default is {}
|
|
|
|
// userSize - see <Item.userSize>; default is null
|
|
|
|
// bounds - a <Rect>; otherwise based on the locations of the provided elements
|
2010-08-06 15:46:55 -07:00
|
|
|
// container - a DOM element to use as the container for this groupItem; otherwise will create
|
|
|
|
// title - the title for the groupItem; otherwise blank
|
2010-09-04 12:15:31 -07:00
|
|
|
// dontPush - true if this groupItem shouldn't push away on creation; default is false
|
2010-10-09 12:46:18 -07:00
|
|
|
// dontPush - true if this groupItem shouldn't push away or snap on creation; default is false
|
|
|
|
// immediately - true if we want all placement immediately, not with animation
|
2010-09-08 10:02:08 -07:00
|
|
|
function GroupItem(listOfEls, options) {
|
2011-01-17 09:31:39 -08:00
|
|
|
if (!options)
|
2010-05-26 11:29:31 -07:00
|
|
|
options = {};
|
|
|
|
|
|
|
|
this._inited = false;
|
2010-10-13 10:07:55 -07:00
|
|
|
this._uninited = false;
|
2010-05-26 11:29:31 -07:00
|
|
|
this._children = []; // an array of Items
|
|
|
|
this.defaultSize = new Point(TabItems.tabWidth * 1.5, TabItems.tabHeight * 1.5);
|
2010-08-06 15:46:55 -07:00
|
|
|
this.isAGroupItem = true;
|
|
|
|
this.id = options.id || GroupItems.getNextID();
|
2010-05-26 11:29:31 -07:00
|
|
|
this._isStacked = false;
|
|
|
|
this.expanded = null;
|
|
|
|
this.locked = (options.locked ? Utils.copy(options.locked) : {});
|
|
|
|
this.topChild = null;
|
2010-09-10 07:40:27 -07:00
|
|
|
this.hidden = false;
|
2010-10-28 21:55:18 -07:00
|
|
|
this.fadeAwayUndoButtonDelay = 15000;
|
|
|
|
this.fadeAwayUndoButtonDuration = 300;
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-06-19 17:45:23 -07:00
|
|
|
this.keepProportional = false;
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2011-01-09 19:23:56 -08:00
|
|
|
// Double click tracker
|
|
|
|
this._lastClick = 0;
|
|
|
|
this._lastClickPositions = null;
|
|
|
|
|
2010-06-14 15:43:02 -07:00
|
|
|
// Variable: _activeTab
|
2010-08-06 15:46:55 -07:00
|
|
|
// The <TabItem> for the groupItem's active tab.
|
2010-05-25 19:18:25 -07:00
|
|
|
this._activeTab = null;
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-07-22 12:46:51 -07:00
|
|
|
if (Utils.isPoint(options.userSize))
|
2010-05-26 11:29:31 -07:00
|
|
|
this.userSize = new Point(options.userSize);
|
|
|
|
|
|
|
|
var self = this;
|
|
|
|
|
|
|
|
var rectToBe;
|
2010-07-17 22:03:31 -07:00
|
|
|
if (options.bounds) {
|
2010-08-10 11:13:10 -07:00
|
|
|
Utils.assert(Utils.isRect(options.bounds), "options.bounds must be a Rect");
|
2010-05-26 11:29:31 -07:00
|
|
|
rectToBe = new Rect(options.bounds);
|
2010-07-17 22:03:31 -07:00
|
|
|
}
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-07-11 17:54:42 -07:00
|
|
|
if (!rectToBe) {
|
2010-08-06 15:46:55 -07:00
|
|
|
rectToBe = GroupItems.getBoundingBox(listOfEls);
|
2010-07-30 02:54:30 -07:00
|
|
|
rectToBe.inset(-30, -30);
|
2010-05-26 11:29:31 -07:00
|
|
|
}
|
|
|
|
|
2010-07-18 08:58:10 -07:00
|
|
|
var $container = options.container;
|
2010-10-09 12:46:18 -07:00
|
|
|
let immediately = options.immediately || $container ? true : false;
|
2010-07-11 17:54:42 -07:00
|
|
|
if (!$container) {
|
2010-06-04 15:08:24 -07:00
|
|
|
$container = iQ('<div>')
|
2010-08-06 15:46:55 -07:00
|
|
|
.addClass('groupItem')
|
2010-05-26 11:29:31 -07:00
|
|
|
.css({position: 'absolute'})
|
|
|
|
.css(rectToBe);
|
|
|
|
}
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-06-28 16:11:22 -07:00
|
|
|
this.bounds = $container.bounds();
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-06-19 11:56:07 -07:00
|
|
|
this.isDragging = false;
|
2010-05-26 11:29:31 -07:00
|
|
|
$container
|
|
|
|
.css({zIndex: -100})
|
2010-06-04 15:08:24 -07:00
|
|
|
.appendTo("body");
|
2010-07-18 08:58:10 -07:00
|
|
|
|
|
|
|
// ___ New Tab Button
|
2010-06-04 15:08:24 -07:00
|
|
|
this.$ntb = iQ("<div>")
|
2010-08-04 22:43:05 -07:00
|
|
|
.addClass('newTabButton')
|
2010-07-30 02:54:30 -07:00
|
|
|
.click(function() {
|
2010-05-26 11:29:31 -07:00
|
|
|
self.newTab();
|
2010-08-12 16:32:18 -07:00
|
|
|
})
|
2010-09-09 07:06:00 -07:00
|
|
|
.attr('title', tabviewString('groupItem.newTabButton'))
|
2010-08-12 16:32:18 -07:00
|
|
|
.appendTo($container);
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-05-26 11:29:31 -07:00
|
|
|
// ___ Resizer
|
2010-06-04 15:08:24 -07:00
|
|
|
this.$resizer = iQ("<div>")
|
|
|
|
.addClass('resizer')
|
2010-05-26 11:29:31 -07:00
|
|
|
.appendTo($container)
|
|
|
|
.hide();
|
|
|
|
|
|
|
|
// ___ Titlebar
|
|
|
|
var html =
|
2010-06-04 15:08:24 -07:00
|
|
|
"<div class='title-container'>" +
|
2011-01-11 20:36:05 -08:00
|
|
|
"<input class='name' placeholder='" + this.defaultName + "'/>" +
|
2010-07-18 08:58:10 -07:00
|
|
|
"<div class='title-shield' />" +
|
2010-06-22 16:47:02 -07:00
|
|
|
"</div>";
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-06-04 15:08:24 -07:00
|
|
|
this.$titlebar = iQ('<div>')
|
|
|
|
.addClass('titlebar')
|
2010-07-18 08:58:10 -07:00
|
|
|
.html(html)
|
2010-05-26 11:29:31 -07:00
|
|
|
.appendTo($container);
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-12-16 21:43:31 -08:00
|
|
|
this.$closeButton = iQ('<div>')
|
2010-06-22 16:47:02 -07:00
|
|
|
.addClass('close')
|
|
|
|
.click(function() {
|
|
|
|
self.closeAll();
|
|
|
|
})
|
|
|
|
.appendTo($container);
|
2010-07-18 08:58:10 -07:00
|
|
|
|
|
|
|
// ___ Title
|
2010-06-04 15:08:24 -07:00
|
|
|
this.$titleContainer = iQ('.title-container', this.$titlebar);
|
|
|
|
this.$title = iQ('.name', this.$titlebar);
|
|
|
|
this.$titleShield = iQ('.title-shield', this.$titlebar);
|
2011-01-11 20:36:05 -08:00
|
|
|
this.setTitle(options.title);
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-09-10 00:22:39 -07:00
|
|
|
var handleKeyDown = function(e) {
|
2010-08-01 23:34:52 -07:00
|
|
|
if (e.which == 13 || e.which == 27) { // return & escape
|
2010-07-28 22:02:26 -07:00
|
|
|
(self.$title)[0].blur();
|
2010-06-04 15:08:24 -07:00
|
|
|
self.$title
|
2010-05-26 11:29:31 -07:00
|
|
|
.addClass("transparentBorder")
|
2010-07-30 02:54:30 -07:00
|
|
|
.one("mouseout", function() {
|
2010-05-26 11:29:31 -07:00
|
|
|
self.$title.removeClass("transparentBorder");
|
|
|
|
});
|
2010-09-10 00:22:39 -07:00
|
|
|
e.stopPropagation();
|
|
|
|
e.preventDefault();
|
|
|
|
}
|
|
|
|
};
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-09-10 00:22:39 -07:00
|
|
|
var handleKeyUp = function(e) {
|
|
|
|
// NOTE: When user commits or cancels IME composition, the last key
|
|
|
|
// event fires only a keyup event. Then, we shouldn't take any
|
|
|
|
// reactions but we should update our status.
|
|
|
|
self.adjustTitleSize();
|
2010-05-26 11:29:31 -07:00
|
|
|
self.save();
|
2010-06-25 16:00:51 -07:00
|
|
|
};
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-05-26 11:29:31 -07:00
|
|
|
this.$title
|
2011-01-11 20:36:05 -08:00
|
|
|
.blur(function() {
|
|
|
|
self.$titleShield.show();
|
|
|
|
})
|
2010-05-26 11:29:31 -07:00
|
|
|
.focus(function() {
|
2010-07-11 17:54:42 -07:00
|
|
|
if (self.locked.title) {
|
2010-07-28 22:02:26 -07:00
|
|
|
(self.$title)[0].blur();
|
2010-05-26 11:29:31 -07:00
|
|
|
return;
|
2010-07-18 08:58:10 -07:00
|
|
|
}
|
2010-07-28 22:02:26 -07:00
|
|
|
(self.$title)[0].select();
|
2010-05-26 11:29:31 -07:00
|
|
|
})
|
2010-09-10 00:22:39 -07:00
|
|
|
.keydown(handleKeyDown)
|
|
|
|
.keyup(handleKeyUp);
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-07-11 17:54:42 -07:00
|
|
|
if (this.locked.title)
|
2010-05-26 11:29:31 -07:00
|
|
|
this.$title.addClass('name-locked');
|
|
|
|
else {
|
|
|
|
this.$titleShield
|
|
|
|
.mousedown(function(e) {
|
2011-01-25 13:13:21 -08:00
|
|
|
self.lastMouseDownTarget = (Utils.isLeftClick(e) ? e.target : null);
|
2010-05-26 11:29:31 -07:00
|
|
|
})
|
2010-07-18 08:58:10 -07:00
|
|
|
.mouseup(function(e) {
|
2010-05-26 11:29:31 -07:00
|
|
|
var same = (e.target == self.lastMouseDownTarget);
|
|
|
|
self.lastMouseDownTarget = null;
|
2010-07-11 17:54:42 -07:00
|
|
|
if (!same)
|
2010-05-26 11:29:31 -07:00
|
|
|
return;
|
2010-07-18 08:58:10 -07:00
|
|
|
|
|
|
|
if (!self.isDragging) {
|
2010-05-26 11:29:31 -07:00
|
|
|
self.$titleShield.hide();
|
2010-07-28 22:02:26 -07:00
|
|
|
(self.$title)[0].focus();
|
2010-05-26 11:29:31 -07:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-06-19 18:46:39 -07:00
|
|
|
// ___ Stack Expander
|
2010-08-11 20:08:33 -07:00
|
|
|
this.$expander = iQ("<div/>")
|
2010-06-19 18:46:39 -07:00
|
|
|
.addClass("stackExpander")
|
|
|
|
.appendTo($container)
|
2010-07-18 08:58:10 -07:00
|
|
|
.hide();
|
|
|
|
|
2010-09-10 02:50:14 -07:00
|
|
|
// ___ app tabs: create app tab tray and populate it
|
|
|
|
this.$appTabTray = iQ("<div/>")
|
|
|
|
.addClass("appTabTray")
|
|
|
|
.appendTo($container);
|
|
|
|
|
|
|
|
AllTabs.tabs.forEach(function(xulTab) {
|
|
|
|
if (xulTab.pinned && xulTab.ownerDocument.defaultView == gWindow)
|
|
|
|
self.addAppTab(xulTab);
|
|
|
|
});
|
|
|
|
|
2010-05-26 11:29:31 -07:00
|
|
|
// ___ locking
|
2010-07-11 17:54:42 -07:00
|
|
|
if (this.locked.bounds)
|
2010-07-18 08:58:10 -07:00
|
|
|
$container.css({cursor: 'default'});
|
|
|
|
|
2010-07-11 17:54:42 -07:00
|
|
|
if (this.locked.close)
|
2010-12-16 21:43:31 -08:00
|
|
|
this.$closeButton.hide();
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-09-10 07:40:27 -07:00
|
|
|
// ___ Undo Close
|
|
|
|
this.$undoContainer = null;
|
2010-10-28 21:55:18 -07:00
|
|
|
this._undoButtonTimeoutId = null;
|
2010-09-10 07:40:27 -07:00
|
|
|
|
2010-05-26 11:29:31 -07:00
|
|
|
// ___ Superclass initialization
|
2010-07-28 22:02:26 -07:00
|
|
|
this._init($container[0]);
|
2010-05-26 11:29:31 -07:00
|
|
|
|
2010-07-18 08:58:10 -07:00
|
|
|
if (this.$debug)
|
2010-05-26 11:29:31 -07:00
|
|
|
this.$debug.css({zIndex: -1000});
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-05-26 11:29:31 -07:00
|
|
|
// ___ Children
|
2010-07-18 08:58:10 -07:00
|
|
|
Array.prototype.forEach.call(listOfEls, function(el) {
|
2011-01-12 13:48:42 -08:00
|
|
|
self.add(el, options);
|
2010-05-26 11:29:31 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
// ___ Finish Up
|
|
|
|
this._addHandlers($container);
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-07-11 17:54:42 -07:00
|
|
|
if (!this.locked.bounds)
|
2010-10-09 12:46:18 -07:00
|
|
|
this.setResizable(true, immediately);
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-08-06 15:46:55 -07:00
|
|
|
GroupItems.register(this);
|
2010-07-06 11:42:23 -07:00
|
|
|
|
2010-07-11 16:48:05 -07:00
|
|
|
// ___ Position
|
2010-07-22 19:24:31 -07:00
|
|
|
this.setBounds(rectToBe, immediately);
|
2010-10-09 12:46:18 -07:00
|
|
|
if (options.dontPush) {
|
|
|
|
this.setZ(drag.zIndex);
|
|
|
|
drag.zIndex++;
|
|
|
|
} else
|
|
|
|
// Calling snap will also trigger pushAway
|
|
|
|
this.snap(immediately);
|
2010-07-17 22:03:31 -07:00
|
|
|
if ($container)
|
2010-07-22 19:24:31 -07:00
|
|
|
this.setBounds(rectToBe, immediately);
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-05-26 11:29:31 -07:00
|
|
|
this._inited = true;
|
|
|
|
this.save();
|
2010-12-16 21:43:31 -08:00
|
|
|
|
|
|
|
GroupItems.updateGroupCloseButtons();
|
2010-05-26 11:29:31 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
// ----------
|
2010-09-08 10:02:08 -07:00
|
|
|
GroupItem.prototype = Utils.extend(new Item(), new Subscribable(), {
|
2010-05-26 11:29:31 -07:00
|
|
|
// ----------
|
2010-06-11 15:08:14 -07:00
|
|
|
// Variable: defaultName
|
|
|
|
// The prompt text for the title field.
|
2010-09-09 07:06:00 -07:00
|
|
|
defaultName: tabviewString('groupItem.defaultName'),
|
2010-05-25 19:18:25 -07:00
|
|
|
|
|
|
|
// -----------
|
2010-06-11 15:08:14 -07:00
|
|
|
// Function: setActiveTab
|
2010-09-10 02:50:14 -07:00
|
|
|
// Sets the active <TabItem> for this groupItem; can be null, but only
|
2010-09-11 19:51:19 -07:00
|
|
|
// if there are no children.
|
2010-09-08 10:02:08 -07:00
|
|
|
setActiveTab: function GroupItem_setActiveTab(tab) {
|
2010-09-10 02:50:14 -07:00
|
|
|
Utils.assertThrow((!tab && this._children.length == 0) || tab.isATabItem,
|
|
|
|
"tab must be null (if no children) or a TabItem");
|
|
|
|
|
2010-05-25 19:18:25 -07:00
|
|
|
this._activeTab = tab;
|
|
|
|
},
|
|
|
|
|
|
|
|
// -----------
|
2010-06-11 15:08:14 -07:00
|
|
|
// Function: getActiveTab
|
2010-09-10 02:50:14 -07:00
|
|
|
// Gets the active <TabItem> for this groupItem; can be null, but only
|
|
|
|
// if there are no children.
|
2010-09-08 10:02:08 -07:00
|
|
|
getActiveTab: function GroupItem_getActiveTab() {
|
2010-05-25 19:18:25 -07:00
|
|
|
return this._activeTab;
|
|
|
|
},
|
2010-07-18 08:58:10 -07:00
|
|
|
|
|
|
|
// ----------
|
2010-06-11 15:08:14 -07:00
|
|
|
// Function: getStorageData
|
2010-08-06 15:46:55 -07:00
|
|
|
// Returns all of the info worth storing about this groupItem.
|
2010-09-08 10:02:08 -07:00
|
|
|
getStorageData: function GroupItem_getStorageData() {
|
2010-05-26 11:29:31 -07:00
|
|
|
var data = {
|
2010-07-18 08:58:10 -07:00
|
|
|
bounds: this.getBounds(),
|
2010-05-26 11:29:31 -07:00
|
|
|
userSize: null,
|
2010-07-18 08:58:10 -07:00
|
|
|
locked: Utils.copy(this.locked),
|
2010-05-26 11:29:31 -07:00
|
|
|
title: this.getTitle(),
|
|
|
|
id: this.id
|
|
|
|
};
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-07-22 12:46:51 -07:00
|
|
|
if (Utils.isPoint(this.userSize))
|
2010-05-26 11:29:31 -07:00
|
|
|
data.userSize = new Point(this.userSize);
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-05-26 11:29:31 -07:00
|
|
|
return data;
|
|
|
|
},
|
|
|
|
|
2010-07-03 14:55:19 -07:00
|
|
|
// ----------
|
|
|
|
// Function: isEmpty
|
2010-08-06 15:46:55 -07:00
|
|
|
// Returns true if the tab groupItem is empty and unnamed.
|
2010-09-08 10:02:08 -07:00
|
|
|
isEmpty: function GroupItem_isEmpty() {
|
2010-07-13 16:38:51 -07:00
|
|
|
return !this._children.length && !this.getTitle();
|
2010-07-11 16:48:05 -07:00
|
|
|
},
|
2010-07-03 14:55:19 -07:00
|
|
|
|
2010-05-26 11:29:31 -07:00
|
|
|
// ----------
|
2010-06-11 15:08:14 -07:00
|
|
|
// Function: save
|
2010-08-06 15:46:55 -07:00
|
|
|
// Saves this groupItem to persistent storage.
|
2010-09-08 10:02:08 -07:00
|
|
|
save: function GroupItem_save() {
|
2010-10-13 10:07:55 -07:00
|
|
|
if (!this._inited || this._uninited) // too soon/late to save
|
2010-05-26 11:29:31 -07:00
|
|
|
return;
|
|
|
|
|
|
|
|
var data = this.getStorageData();
|
2010-08-06 15:46:55 -07:00
|
|
|
if (GroupItems.groupItemStorageSanity(data))
|
|
|
|
Storage.saveGroupItem(gWindow, data);
|
2010-05-26 11:29:31 -07:00
|
|
|
},
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-10-13 10:07:55 -07:00
|
|
|
// ----------
|
|
|
|
// Function: deleteData
|
|
|
|
// Deletes the groupItem in the persistent storage.
|
|
|
|
deleteData: function GroupItem_deleteData() {
|
|
|
|
this._uninited = true;
|
|
|
|
Storage.deleteGroupItem(gWindow, this.id);
|
|
|
|
},
|
|
|
|
|
2010-05-26 11:29:31 -07:00
|
|
|
// ----------
|
2010-06-17 14:52:25 -07:00
|
|
|
// Function: getTitle
|
2010-08-06 15:46:55 -07:00
|
|
|
// Returns the title of this groupItem as a string.
|
2010-09-08 10:02:08 -07:00
|
|
|
getTitle: function GroupItem_getTitle() {
|
2011-01-11 20:36:05 -08:00
|
|
|
return this.$title ? this.$title.val() : '';
|
2010-05-26 11:29:31 -07:00
|
|
|
},
|
|
|
|
|
2010-07-18 08:58:10 -07:00
|
|
|
// ----------
|
2010-06-17 14:52:25 -07:00
|
|
|
// Function: setTitle
|
2010-08-06 15:46:55 -07:00
|
|
|
// Sets the title of this groupItem with the given string
|
2010-09-08 10:02:08 -07:00
|
|
|
setTitle: function GroupItem_setTitle(value) {
|
2010-07-18 08:58:10 -07:00
|
|
|
this.$title.val(value);
|
2010-05-26 11:29:31 -07:00
|
|
|
this.save();
|
|
|
|
},
|
|
|
|
|
2010-07-18 08:58:10 -07:00
|
|
|
// ----------
|
2010-06-17 14:52:25 -07:00
|
|
|
// Function: adjustTitleSize
|
2010-08-06 15:46:55 -07:00
|
|
|
// Used to adjust the width of the title box depending on groupItem width and title size.
|
2010-09-08 10:02:08 -07:00
|
|
|
adjustTitleSize: function GroupItem_adjustTitleSize() {
|
2010-08-10 11:13:10 -07:00
|
|
|
Utils.assert(this.bounds, 'bounds needs to have been set');
|
2010-08-11 20:08:33 -07:00
|
|
|
let closeButton = iQ('.close', this.container);
|
2010-11-07 22:38:25 -08:00
|
|
|
var dimension = UI.rtl ? 'left' : 'right';
|
|
|
|
var w = Math.min(this.bounds.width - parseInt(closeButton.width()) - parseInt(closeButton.css(dimension)),
|
2010-08-11 20:08:33 -07:00
|
|
|
Math.max(150, this.getTitle().length * 6));
|
|
|
|
// The * 6 multiplier calculation is assuming that characters in the title
|
|
|
|
// are approximately 6 pixels wide. Bug 586545
|
2010-05-26 11:29:31 -07:00
|
|
|
var css = {width: w};
|
|
|
|
this.$title.css(css);
|
|
|
|
this.$titleShield.css(css);
|
|
|
|
},
|
2010-07-18 08:58:10 -07:00
|
|
|
|
|
|
|
// ----------
|
2010-06-17 14:52:25 -07:00
|
|
|
// Function: getContentBounds
|
2010-08-06 15:46:55 -07:00
|
|
|
// Returns a <Rect> for the groupItem's content area (which doesn't include the title, etc).
|
2010-09-08 10:02:08 -07:00
|
|
|
getContentBounds: function GroupItem_getContentBounds() {
|
2010-05-26 11:29:31 -07:00
|
|
|
var box = this.getBounds();
|
|
|
|
var titleHeight = this.$titlebar.height();
|
|
|
|
box.top += titleHeight;
|
|
|
|
box.height -= titleHeight;
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-11-15 12:07:02 -08:00
|
|
|
var appTabTrayWidth = this.$appTabTray.width();
|
|
|
|
box.width -= appTabTrayWidth;
|
|
|
|
if (UI.rtl) {
|
|
|
|
box.left += appTabTrayWidth;
|
|
|
|
}
|
2010-09-10 02:50:14 -07:00
|
|
|
|
2010-08-11 20:08:33 -07:00
|
|
|
// Make the computed bounds' "padding" and new tab button margin actually be
|
|
|
|
// themeable --OR-- compute this from actual bounds. Bug 586546
|
|
|
|
box.inset(6, 6);
|
2010-08-08 15:32:08 -07:00
|
|
|
box.height -= 33; // For new tab button
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-05-26 11:29:31 -07:00
|
|
|
return box;
|
2010-03-17 01:07:00 -07:00
|
|
|
},
|
2010-07-18 08:58:10 -07:00
|
|
|
|
|
|
|
// ----------
|
2010-06-17 14:52:25 -07:00
|
|
|
// Function: setBounds
|
2010-06-17 15:57:45 -07:00
|
|
|
// Sets the bounds with the given <Rect>, animating unless "immediately" is false.
|
2010-07-18 08:58:10 -07:00
|
|
|
//
|
|
|
|
// Parameters:
|
2010-07-17 14:10:29 -07:00
|
|
|
// rect - a <Rect> giving the new bounds
|
|
|
|
// immediately - true if it should not animate; default false
|
|
|
|
// options - an object with additional parameters, see below
|
2010-07-18 08:58:10 -07:00
|
|
|
//
|
|
|
|
// Possible options:
|
2010-07-17 14:10:29 -07:00
|
|
|
// force - true to always update the DOM even if the bounds haven't changed; default false
|
2010-09-26 23:25:00 -07:00
|
|
|
setBounds: function GroupItem_setBounds(inRect, immediately, options) {
|
|
|
|
if (!Utils.isRect(inRect)) {
|
|
|
|
Utils.trace('GroupItem.setBounds: rect is not a real rectangle!', inRect);
|
2010-05-26 11:29:31 -07:00
|
|
|
return;
|
|
|
|
}
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-09-26 23:25:00 -07:00
|
|
|
// Validate and conform passed in size
|
|
|
|
let validSize = GroupItems.calcValidSize(
|
|
|
|
new Point(inRect.width, inRect.height));
|
|
|
|
let rect = new Rect(inRect.left, inRect.top, validSize.x, validSize.y);
|
|
|
|
|
2010-07-17 14:10:29 -07:00
|
|
|
if (!options)
|
|
|
|
options = {};
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-05-26 11:29:31 -07:00
|
|
|
var titleHeight = this.$titlebar.height();
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-05-26 11:29:31 -07:00
|
|
|
// ___ Determine what has changed
|
|
|
|
var css = {};
|
|
|
|
var titlebarCSS = {};
|
|
|
|
var contentCSS = {};
|
|
|
|
|
2010-07-17 14:10:29 -07:00
|
|
|
if (rect.left != this.bounds.left || options.force)
|
2010-05-26 11:29:31 -07:00
|
|
|
css.left = rect.left;
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-07-17 14:10:29 -07:00
|
|
|
if (rect.top != this.bounds.top || options.force)
|
2010-05-26 11:29:31 -07:00
|
|
|
css.top = rect.top;
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-07-17 14:10:29 -07:00
|
|
|
if (rect.width != this.bounds.width || options.force) {
|
2010-05-26 11:29:31 -07:00
|
|
|
css.width = rect.width;
|
|
|
|
titlebarCSS.width = rect.width;
|
|
|
|
contentCSS.width = rect.width;
|
|
|
|
}
|
2010-04-22 17:19:42 -07:00
|
|
|
|
2010-07-17 14:10:29 -07:00
|
|
|
if (rect.height != this.bounds.height || options.force) {
|
|
|
|
css.height = rect.height;
|
|
|
|
contentCSS.height = rect.height - titleHeight;
|
2010-05-26 11:29:31 -07:00
|
|
|
}
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-07-23 14:33:02 -07:00
|
|
|
if (Utils.isEmptyObject(css))
|
2010-05-26 11:29:31 -07:00
|
|
|
return;
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-05-26 11:29:31 -07:00
|
|
|
var offset = new Point(rect.left - this.bounds.left, rect.top - this.bounds.top);
|
|
|
|
this.bounds = new Rect(rect);
|
2010-04-22 17:19:42 -07:00
|
|
|
|
2010-05-26 11:29:31 -07:00
|
|
|
// ___ Deal with children
|
2010-07-11 17:54:42 -07:00
|
|
|
if (css.width || css.height) {
|
2010-05-26 11:29:31 -07:00
|
|
|
this.arrange({animate: !immediately}); //(immediately ? 'sometimes' : true)});
|
2010-07-11 17:54:42 -07:00
|
|
|
} else if (css.left || css.top) {
|
2010-07-13 16:38:51 -07:00
|
|
|
this._children.forEach(function(child) {
|
2010-11-05 06:32:00 -07:00
|
|
|
if (!child.getHidden()) {
|
|
|
|
var box = child.getBounds();
|
|
|
|
child.setPosition(box.left + offset.x, box.top + offset.y, immediately);
|
|
|
|
}
|
2010-05-26 11:29:31 -07:00
|
|
|
});
|
|
|
|
}
|
2010-07-17 14:10:29 -07:00
|
|
|
|
2010-05-26 11:29:31 -07:00
|
|
|
// ___ Update our representation
|
2010-07-11 17:54:42 -07:00
|
|
|
if (immediately) {
|
2010-06-04 15:08:24 -07:00
|
|
|
iQ(this.container).css(css);
|
2010-05-26 11:29:31 -07:00
|
|
|
this.$titlebar.css(titlebarCSS);
|
|
|
|
} else {
|
2010-07-27 20:02:51 -07:00
|
|
|
TabItems.pausePainting();
|
2010-06-07 16:16:55 -07:00
|
|
|
iQ(this.container).animate(css, {
|
2010-07-18 08:58:10 -07:00
|
|
|
duration: 350,
|
2010-07-29 12:37:25 -07:00
|
|
|
easing: "tabviewBounce",
|
2010-06-07 16:16:55 -07:00
|
|
|
complete: function() {
|
2010-07-27 20:02:51 -07:00
|
|
|
TabItems.resumePainting();
|
2010-06-07 16:16:55 -07:00
|
|
|
}
|
|
|
|
});
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-06-07 16:16:55 -07:00
|
|
|
this.$titlebar.animate(titlebarCSS, {
|
|
|
|
duration: 350
|
2010-07-17 14:10:29 -07:00
|
|
|
});
|
2010-04-22 17:19:42 -07:00
|
|
|
}
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-11-05 06:32:00 -07:00
|
|
|
if (css.width) {
|
|
|
|
this.adjustTitleSize();
|
|
|
|
}
|
2010-05-26 11:29:31 -07:00
|
|
|
|
2011-01-21 07:21:30 -08:00
|
|
|
UI.clearShouldResizeItems();
|
|
|
|
|
2010-05-26 11:29:31 -07:00
|
|
|
this._updateDebugBounds();
|
2010-08-08 15:32:08 -07:00
|
|
|
this.setTrenches(rect);
|
2010-06-15 11:53:53 -07:00
|
|
|
|
2010-05-26 11:29:31 -07:00
|
|
|
this.save();
|
2010-04-01 17:20:59 -07:00
|
|
|
},
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-05-26 11:29:31 -07:00
|
|
|
// ----------
|
2010-06-17 15:57:45 -07:00
|
|
|
// Function: setZ
|
2010-08-06 15:46:55 -07:00
|
|
|
// Set the Z order for the groupItem's container, as well as its children.
|
2010-09-08 10:02:08 -07:00
|
|
|
setZ: function GroupItem_setZ(value) {
|
2010-07-07 14:12:04 -07:00
|
|
|
this.zIndex = value;
|
|
|
|
|
2010-06-04 15:08:24 -07:00
|
|
|
iQ(this.container).css({zIndex: value});
|
2010-04-22 17:19:42 -07:00
|
|
|
|
2010-07-18 08:58:10 -07:00
|
|
|
if (this.$debug)
|
2010-05-26 11:29:31 -07:00
|
|
|
this.$debug.css({zIndex: value + 1});
|
|
|
|
|
|
|
|
var count = this._children.length;
|
2010-07-11 17:54:42 -07:00
|
|
|
if (count) {
|
2010-05-26 11:29:31 -07:00
|
|
|
var topZIndex = value + count + 1;
|
|
|
|
var zIndex = topZIndex;
|
|
|
|
var self = this;
|
2010-07-13 16:38:51 -07:00
|
|
|
this._children.forEach(function(child) {
|
2010-07-11 17:54:42 -07:00
|
|
|
if (child == self.topChild)
|
2010-05-26 11:29:31 -07:00
|
|
|
child.setZ(topZIndex + 1);
|
|
|
|
else {
|
|
|
|
child.setZ(zIndex);
|
|
|
|
zIndex--;
|
2010-05-18 17:08:45 -07:00
|
|
|
}
|
2010-04-30 17:24:03 -07:00
|
|
|
});
|
2010-05-26 11:29:31 -07:00
|
|
|
}
|
|
|
|
},
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-05-26 11:29:31 -07:00
|
|
|
// ----------
|
2010-06-17 15:57:45 -07:00
|
|
|
// Function: close
|
2010-08-06 15:46:55 -07:00
|
|
|
// Closes the groupItem, removing (but not closing) all of its children.
|
2010-09-08 10:02:08 -07:00
|
|
|
close: function GroupItem_close() {
|
2010-10-08 15:59:24 -07:00
|
|
|
this.removeAll({dontClose: true});
|
2010-08-06 15:46:55 -07:00
|
|
|
GroupItems.unregister(this);
|
2010-09-10 07:40:27 -07:00
|
|
|
|
2010-11-14 19:12:10 -08:00
|
|
|
if (this.hidden) {
|
|
|
|
iQ(this.container).remove();
|
|
|
|
if (this.$undoContainer) {
|
|
|
|
this.$undoContainer.remove();
|
|
|
|
this.$undoContainer = null;
|
|
|
|
}
|
2010-11-23 10:49:50 -08:00
|
|
|
this.removeTrenches();
|
2010-11-14 19:12:10 -08:00
|
|
|
Items.unsquish();
|
2010-11-23 10:49:50 -08:00
|
|
|
this._sendToSubscribers("close");
|
2010-12-16 21:43:31 -08:00
|
|
|
GroupItems.updateGroupCloseButtons();
|
2010-11-14 19:12:10 -08:00
|
|
|
} else {
|
2010-11-23 10:49:50 -08:00
|
|
|
let self = this;
|
2010-11-14 19:12:10 -08:00
|
|
|
iQ(this.container).animate({
|
|
|
|
opacity: 0,
|
|
|
|
"-moz-transform": "scale(.3)",
|
|
|
|
}, {
|
|
|
|
duration: 170,
|
|
|
|
complete: function() {
|
|
|
|
iQ(this).remove();
|
2010-11-23 10:49:50 -08:00
|
|
|
self.removeTrenches();
|
2010-11-14 19:12:10 -08:00
|
|
|
Items.unsquish();
|
2010-11-23 10:49:50 -08:00
|
|
|
self._sendToSubscribers("close");
|
2010-12-16 21:43:31 -08:00
|
|
|
GroupItems.updateGroupCloseButtons();
|
2010-11-14 19:12:10 -08:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2010-10-13 10:07:55 -07:00
|
|
|
this.deleteData();
|
2010-03-29 16:08:48 -07:00
|
|
|
},
|
2010-07-18 08:58:10 -07:00
|
|
|
|
|
|
|
// ----------
|
2010-06-17 15:57:45 -07:00
|
|
|
// Function: closeAll
|
2010-08-06 15:46:55 -07:00
|
|
|
// Closes the groupItem and all of its children.
|
2010-09-08 10:02:08 -07:00
|
|
|
closeAll: function GroupItem_closeAll() {
|
2010-09-10 07:40:27 -07:00
|
|
|
if (this._children.length > 0) {
|
|
|
|
this._children.forEach(function(child) {
|
|
|
|
iQ(child.container).hide();
|
|
|
|
});
|
|
|
|
|
|
|
|
iQ(this.container).animate({
|
|
|
|
opacity: 0,
|
|
|
|
"-moz-transform": "scale(.3)",
|
|
|
|
}, {
|
|
|
|
duration: 170,
|
|
|
|
complete: function() {
|
|
|
|
iQ(this).hide();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
this._createUndoButton();
|
|
|
|
} else {
|
|
|
|
if (!this.locked.close)
|
|
|
|
this.close();
|
|
|
|
}
|
2011-01-25 11:14:10 -08:00
|
|
|
|
|
|
|
this._makeClosestTabActive();
|
|
|
|
},
|
|
|
|
|
|
|
|
// ----------
|
|
|
|
// Function: _makeClosestTabActive
|
|
|
|
// Make the closest tab external to this group active.
|
|
|
|
// Used when closing the group.
|
|
|
|
_makeClosestTabActive: function GroupItem__makeClosestTabActive() {
|
|
|
|
let closeCenter = this.getBounds().center();
|
2010-09-27 16:12:00 -07:00
|
|
|
// Find closest tab to make active
|
2010-12-08 18:13:12 -08:00
|
|
|
let closestTabItem = UI.getClosestTab(closeCenter);
|
|
|
|
UI.setActiveTab(closestTabItem);
|
|
|
|
|
|
|
|
// set the active group or orphan tabitem.
|
|
|
|
if (closestTabItem) {
|
|
|
|
if (closestTabItem.parent) {
|
|
|
|
GroupItems.setActiveGroupItem(closestTabItem.parent);
|
|
|
|
} else {
|
|
|
|
GroupItems.setActiveOrphanTab(closestTabItem);
|
|
|
|
GroupItems.setActiveGroupItem(null);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
GroupItems.setActiveGroupItem(null);
|
|
|
|
GroupItems.setActiveOrphanTab(null);
|
|
|
|
}
|
2010-09-10 07:40:27 -07:00
|
|
|
},
|
|
|
|
|
2010-11-14 19:12:10 -08:00
|
|
|
// ----------
|
|
|
|
// Function: _unhide
|
|
|
|
// Shows the hidden group.
|
|
|
|
_unhide: function GroupItem__unhide() {
|
|
|
|
let self = this;
|
|
|
|
|
|
|
|
this._cancelFadeAwayUndoButtonTimer();
|
|
|
|
this.hidden = false;
|
|
|
|
this.$undoContainer.remove();
|
|
|
|
this.$undoContainer = null;
|
|
|
|
|
|
|
|
iQ(this.container).show().animate({
|
|
|
|
"-moz-transform": "scale(1)",
|
|
|
|
"opacity": 1
|
|
|
|
}, {
|
|
|
|
duration: 170,
|
|
|
|
complete: function() {
|
|
|
|
self._children.forEach(function(child) {
|
|
|
|
iQ(child.container).show();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2010-12-16 21:43:31 -08:00
|
|
|
GroupItems.updateGroupCloseButtons();
|
2010-11-14 19:12:10 -08:00
|
|
|
self._sendToSubscribers("groupShown", { groupItemId: self.id });
|
|
|
|
},
|
|
|
|
|
|
|
|
// ----------
|
|
|
|
// Function: closeHidden
|
|
|
|
// Removes the group item, its children and its container.
|
|
|
|
closeHidden: function GroupItem_closeHidden() {
|
|
|
|
let self = this;
|
|
|
|
|
|
|
|
this._cancelFadeAwayUndoButtonTimer();
|
|
|
|
|
2011-01-20 19:25:18 -08:00
|
|
|
// When the last non-empty groupItem is closed and there are no orphan or
|
|
|
|
// pinned tabs then create a new group with a blank tab.
|
|
|
|
let remainingGroups = GroupItems.groupItems.filter(function (groupItem) {
|
|
|
|
return (groupItem != self && groupItem.getChildren().length);
|
|
|
|
});
|
|
|
|
if (!gBrowser._numPinnedTabs && !GroupItems.getOrphanedTabs().length &&
|
|
|
|
!remainingGroups.length) {
|
|
|
|
let emptyGroups = GroupItems.groupItems.filter(function (groupItem) {
|
|
|
|
return (groupItem != self && !groupItem.getChildren().length);
|
|
|
|
});
|
|
|
|
let group = (emptyGroups.length ? emptyGroups[0] : GroupItems.newGroup());
|
|
|
|
group.newTab();
|
|
|
|
}
|
|
|
|
|
2010-11-14 19:12:10 -08:00
|
|
|
// when "TabClose" event is fired, the browser tab is about to close and our
|
|
|
|
// item "close" event is fired. And then, the browser tab gets closed.
|
|
|
|
// In other words, the group "close" event is fired before all browser
|
|
|
|
// tabs in the group are closed. The below code would fire the group "close"
|
|
|
|
// event only after all browser tabs in that group are closed.
|
|
|
|
let shouldRemoveTabItems = [];
|
|
|
|
let toClose = this._children.concat();
|
|
|
|
toClose.forEach(function(child) {
|
|
|
|
child.removeSubscriber(self, "close");
|
|
|
|
|
|
|
|
let removed = child.close();
|
|
|
|
if (removed) {
|
|
|
|
shouldRemoveTabItems.push(child);
|
|
|
|
} else {
|
|
|
|
// child.removeSubscriber() must be called before child.close(),
|
|
|
|
// therefore we call child.addSubscriber() if the tab is not removed.
|
|
|
|
child.addSubscriber(self, "close", function() {
|
|
|
|
self.remove(child);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
if (shouldRemoveTabItems.length != toClose.length) {
|
|
|
|
// remove children without the assiciated tab and show the group item
|
|
|
|
shouldRemoveTabItems.forEach(function(child) {
|
|
|
|
self.remove(child, { dontArrange: true });
|
|
|
|
});
|
|
|
|
|
|
|
|
this.$undoContainer.fadeOut(function() { self._unhide() });
|
|
|
|
} else {
|
|
|
|
this.close();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
// ----------
|
|
|
|
// Function: _fadeAwayUndoButton
|
|
|
|
// Fades away the undo button
|
|
|
|
_fadeAwayUndoButton: function GroupItem__fadeAwayUdoButton() {
|
|
|
|
let self = this;
|
|
|
|
|
|
|
|
if (this.$undoContainer) {
|
|
|
|
// if there is one or more orphan tabs or there is more than one group
|
|
|
|
// and other groupS are not empty, fade away the undo button.
|
|
|
|
let shouldFadeAway = GroupItems.getOrphanedTabs().length > 0;
|
|
|
|
|
|
|
|
if (!shouldFadeAway && GroupItems.groupItems.length > 1) {
|
|
|
|
shouldFadeAway =
|
|
|
|
GroupItems.groupItems.some(function(groupItem) {
|
|
|
|
return (groupItem != self && groupItem.getChildren().length > 0);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
if (shouldFadeAway) {
|
|
|
|
self.$undoContainer.animate({
|
|
|
|
color: "transparent",
|
|
|
|
opacity: 0
|
|
|
|
}, {
|
|
|
|
duration: this._fadeAwayUndoButtonDuration,
|
|
|
|
complete: function() { self.closeHidden(); }
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2010-09-10 07:40:27 -07:00
|
|
|
// ----------
|
|
|
|
// Function: _createUndoButton
|
|
|
|
// Makes the affordance for undo a close group action
|
2010-11-14 19:12:10 -08:00
|
|
|
_createUndoButton: function GroupItem__createUndoButton() {
|
2010-09-10 07:40:27 -07:00
|
|
|
let self = this;
|
|
|
|
this.$undoContainer = iQ("<div/>")
|
|
|
|
.addClass("undo")
|
|
|
|
.attr("type", "button")
|
2010-10-04 22:58:44 -07:00
|
|
|
.text(tabviewString("groupItem.undoCloseGroup"))
|
2010-09-10 07:40:27 -07:00
|
|
|
.appendTo("body");
|
|
|
|
let undoClose = iQ("<span/>")
|
|
|
|
.addClass("close")
|
|
|
|
.appendTo(this.$undoContainer);
|
|
|
|
|
|
|
|
this.$undoContainer.css({
|
|
|
|
left: this.bounds.left + this.bounds.width/2 - iQ(self.$undoContainer).width()/2,
|
|
|
|
top: this.bounds.top + this.bounds.height/2 - iQ(self.$undoContainer).height()/2,
|
|
|
|
"-moz-transform": "scale(.1)",
|
|
|
|
opacity: 0
|
|
|
|
});
|
|
|
|
this.hidden = true;
|
|
|
|
|
2010-11-14 19:12:10 -08:00
|
|
|
// hide group item and show undo container.
|
2010-09-10 07:40:27 -07:00
|
|
|
setTimeout(function() {
|
|
|
|
self.$undoContainer.animate({
|
|
|
|
"-moz-transform": "scale(1)",
|
|
|
|
"opacity": 1
|
|
|
|
}, {
|
|
|
|
easing: "tabviewBounce",
|
|
|
|
duration: 170,
|
|
|
|
complete: function() {
|
|
|
|
self._sendToSubscribers("groupHidden", { groupItemId: self.id });
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}, 50);
|
|
|
|
|
2010-11-14 19:12:10 -08:00
|
|
|
// add click handlers
|
2010-09-10 07:40:27 -07:00
|
|
|
this.$undoContainer.click(function(e) {
|
|
|
|
// Only do this for clicks on this actual element.
|
|
|
|
if (e.target.nodeName != self.$undoContainer[0].nodeName)
|
|
|
|
return;
|
|
|
|
|
2010-11-14 19:12:10 -08:00
|
|
|
self.$undoContainer.fadeOut(function() { self._unhide(); });
|
2010-09-10 07:40:27 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
undoClose.click(function() {
|
2010-11-14 19:12:10 -08:00
|
|
|
self.$undoContainer.fadeOut(function() { self.closeHidden(); });
|
2010-10-28 21:55:18 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
this.setupFadeAwayUndoButtonTimer();
|
|
|
|
// Cancel the fadeaway if you move the mouse over the undo
|
|
|
|
// button, and restart the countdown once you move out of it.
|
|
|
|
this.$undoContainer.mouseover(function() {
|
|
|
|
self._cancelFadeAwayUndoButtonTimer();
|
|
|
|
});
|
|
|
|
this.$undoContainer.mouseout(function() {
|
|
|
|
self.setupFadeAwayUndoButtonTimer();
|
2010-09-10 07:40:27 -07:00
|
|
|
});
|
2010-12-16 21:43:31 -08:00
|
|
|
|
|
|
|
GroupItems.updateGroupCloseButtons();
|
2010-10-28 21:55:18 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
// ----------
|
|
|
|
// Sets up fade away undo button timeout.
|
|
|
|
setupFadeAwayUndoButtonTimer: function() {
|
|
|
|
let self = this;
|
|
|
|
|
|
|
|
if (!this._undoButtonTimeoutId) {
|
|
|
|
this._undoButtonTimeoutId = setTimeout(function() {
|
|
|
|
self._fadeAwayUndoButton();
|
|
|
|
}, this.fadeAwayUndoButtonDelay);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
// ----------
|
|
|
|
// Cancels the fade away undo button timeout.
|
|
|
|
_cancelFadeAwayUndoButtonTimer: function() {
|
|
|
|
clearTimeout(this._undoButtonTimeoutId);
|
|
|
|
this._undoButtonTimeoutId = null;
|
|
|
|
},
|
2010-09-10 07:40:27 -07:00
|
|
|
|
2010-07-18 08:58:10 -07:00
|
|
|
// ----------
|
2010-05-26 11:29:31 -07:00
|
|
|
// Function: add
|
2010-08-06 15:46:55 -07:00
|
|
|
// Adds an item to the groupItem.
|
2010-07-18 08:58:10 -07:00
|
|
|
// Parameters:
|
|
|
|
//
|
2010-08-11 20:08:33 -07:00
|
|
|
// a - The item to add. Can be an <Item>, a DOM element or an iQ object.
|
2010-05-26 11:29:31 -07:00
|
|
|
// The latter two must refer to the container of an <Item>.
|
2011-01-12 13:48:42 -08:00
|
|
|
// options - An object with optional settings for this call.
|
2011-01-11 11:35:13 -08:00
|
|
|
//
|
2011-01-12 13:48:42 -08:00
|
|
|
// Options:
|
|
|
|
//
|
|
|
|
// index - (int) if set, add this tab at this index
|
|
|
|
// immediately - (bool) if true, no animation will be used
|
|
|
|
// dontArrange - (bool) if true, will not trigger an arrange on the group
|
|
|
|
add: function GroupItem_add(a, options) {
|
2010-05-26 11:29:31 -07:00
|
|
|
try {
|
|
|
|
var item;
|
|
|
|
var $el;
|
2010-07-11 17:54:42 -07:00
|
|
|
if (a.isAnItem) {
|
2010-05-26 11:29:31 -07:00
|
|
|
item = a;
|
2010-07-18 08:58:10 -07:00
|
|
|
$el = iQ(a.container);
|
2010-05-26 11:29:31 -07:00
|
|
|
} else {
|
2010-06-04 15:08:24 -07:00
|
|
|
$el = iQ(a);
|
2010-05-26 11:29:31 -07:00
|
|
|
item = Items.item($el);
|
2010-06-15 16:08:21 -07:00
|
|
|
}
|
2010-08-11 21:36:58 -07:00
|
|
|
Utils.assertThrow(!item.parent || item.parent == this,
|
2010-08-10 11:13:10 -07:00
|
|
|
"shouldn't already be in another groupItem");
|
2010-07-15 17:23:39 -07:00
|
|
|
|
2010-06-19 11:56:07 -07:00
|
|
|
item.removeTrenches();
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2011-01-17 09:31:39 -08:00
|
|
|
if (!options)
|
2010-05-26 11:29:31 -07:00
|
|
|
options = {};
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-05-26 11:29:31 -07:00
|
|
|
var self = this;
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-08-06 15:46:55 -07:00
|
|
|
var wasAlreadyInThisGroupItem = false;
|
2010-07-13 16:38:51 -07:00
|
|
|
var oldIndex = this._children.indexOf(item);
|
2010-07-11 17:54:42 -07:00
|
|
|
if (oldIndex != -1) {
|
2010-07-18 08:58:10 -07:00
|
|
|
this._children.splice(oldIndex, 1);
|
2010-08-06 15:46:55 -07:00
|
|
|
wasAlreadyInThisGroupItem = true;
|
2010-03-24 16:27:56 -07:00
|
|
|
}
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-05-26 11:29:31 -07:00
|
|
|
// Insert the tab into the right position.
|
2011-01-12 13:48:42 -08:00
|
|
|
var index = ("index" in options) ? options.index : this._children.length;
|
2010-07-30 02:54:30 -07:00
|
|
|
this._children.splice(index, 0, item);
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-05-26 11:29:31 -07:00
|
|
|
item.setZ(this.getZ() + 1);
|
2010-08-06 15:46:55 -07:00
|
|
|
$el.addClass("tabInGroupItem");
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-08-06 15:46:55 -07:00
|
|
|
if (!wasAlreadyInThisGroupItem) {
|
2010-06-22 16:42:06 -07:00
|
|
|
item.droppable(false);
|
2010-08-06 15:46:55 -07:00
|
|
|
item.groupItemData = {};
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-08-10 15:36:56 -07:00
|
|
|
item.addSubscriber(this, "close", function() {
|
2011-01-26 12:23:30 -08:00
|
|
|
self.remove(item);
|
2010-12-15 19:43:25 -08:00
|
|
|
if (self._children.length > 0 && self._activeTab) {
|
|
|
|
GroupItems.setActiveGroupItem(self);
|
|
|
|
UI.setActiveTab(self._activeTab);
|
|
|
|
}
|
2010-05-26 11:29:31 -07:00
|
|
|
});
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-05-26 11:29:31 -07:00
|
|
|
item.setParent(this);
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-08-10 16:20:05 -07:00
|
|
|
if (typeof item.setResizable == 'function')
|
2010-10-09 12:46:18 -07:00
|
|
|
item.setResizable(false, options.immediately);
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-09-17 08:24:39 -07:00
|
|
|
// if it is visually active, set it as the active tab.
|
|
|
|
if (iQ(item.container).hasClass("focus"))
|
|
|
|
this.setActiveTab(item);
|
|
|
|
|
2010-09-14 03:12:03 -07:00
|
|
|
// if it matches the selected tab or no active tab and the browser
|
|
|
|
// tab is hidden, the active group item would be set.
|
|
|
|
if (item.tab == gBrowser.selectedTab ||
|
|
|
|
(!GroupItems.getActiveGroupItem() && !item.tab.hidden))
|
2010-08-06 15:46:55 -07:00
|
|
|
GroupItems.setActiveGroupItem(this);
|
2010-05-26 11:29:31 -07:00
|
|
|
}
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-10-09 12:46:18 -07:00
|
|
|
if (!options.dontArrange)
|
|
|
|
this.arrange({animate: !options.immediately});
|
2010-09-11 19:51:19 -07:00
|
|
|
|
2010-09-08 11:52:07 -07:00
|
|
|
this._sendToSubscribers("childAdded",{ groupItemId: this.id, item: item });
|
2010-09-11 19:51:19 -07:00
|
|
|
|
2010-07-26 18:30:56 -07:00
|
|
|
UI.setReorderTabsOnHide(this);
|
2010-05-26 11:29:31 -07:00
|
|
|
} catch(e) {
|
2010-08-06 15:46:55 -07:00
|
|
|
Utils.log('GroupItem.add error', e);
|
2010-05-25 17:04:59 -07:00
|
|
|
}
|
2010-04-22 09:38:37 -07:00
|
|
|
},
|
2010-07-18 08:58:10 -07:00
|
|
|
|
|
|
|
// ----------
|
2010-05-26 11:29:31 -07:00
|
|
|
// Function: remove
|
2010-08-06 15:46:55 -07:00
|
|
|
// Removes an item from the groupItem.
|
2010-07-18 08:58:10 -07:00
|
|
|
// Parameters:
|
|
|
|
//
|
2010-08-11 20:08:33 -07:00
|
|
|
// a - The item to remove. Can be an <Item>, a DOM element or an iQ object.
|
2010-05-26 11:29:31 -07:00
|
|
|
// The latter two must refer to the container of an <Item>.
|
2010-10-08 15:59:24 -07:00
|
|
|
// options - An optional object with settings for this call. See below.
|
|
|
|
//
|
|
|
|
// Possible options:
|
|
|
|
// dontArrange - don't rearrange the remaining items
|
|
|
|
// dontClose - don't close the group even if it normally would
|
|
|
|
// immediately - don't animate
|
2010-09-08 10:02:08 -07:00
|
|
|
remove: function GroupItem_remove(a, options) {
|
2010-06-16 16:30:48 -07:00
|
|
|
try {
|
2010-07-18 08:58:10 -07:00
|
|
|
var $el;
|
2010-06-16 16:30:48 -07:00
|
|
|
var item;
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-07-11 17:54:42 -07:00
|
|
|
if (a.isAnItem) {
|
2010-06-16 16:30:48 -07:00
|
|
|
item = a;
|
|
|
|
$el = iQ(item.container);
|
|
|
|
} else {
|
2010-07-18 08:58:10 -07:00
|
|
|
$el = iQ(a);
|
2010-06-16 16:30:48 -07:00
|
|
|
item = Items.item($el);
|
|
|
|
}
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2011-01-17 09:31:39 -08:00
|
|
|
if (!options)
|
2010-06-16 16:30:48 -07:00
|
|
|
options = {};
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-07-13 16:38:51 -07:00
|
|
|
var index = this._children.indexOf(item);
|
2010-07-11 17:54:42 -07:00
|
|
|
if (index != -1)
|
2010-07-18 08:58:10 -07:00
|
|
|
this._children.splice(index, 1);
|
2010-09-11 19:51:19 -07:00
|
|
|
|
2010-12-15 19:43:25 -08:00
|
|
|
if (item == this._activeTab || !this._activeTab) {
|
2010-09-17 08:24:39 -07:00
|
|
|
if (this._children.length > 0)
|
2010-09-10 02:50:14 -07:00
|
|
|
this._activeTab = this._children[0];
|
|
|
|
else
|
|
|
|
this._activeTab = null;
|
|
|
|
}
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-06-16 16:30:48 -07:00
|
|
|
item.setParent(null);
|
2010-08-06 15:46:55 -07:00
|
|
|
item.removeClass("tabInGroupItem");
|
2010-06-16 16:30:48 -07:00
|
|
|
item.removeClass("stacked");
|
2010-09-26 23:25:00 -07:00
|
|
|
item.isStacked = false;
|
2010-11-05 06:32:00 -07:00
|
|
|
item.setHidden(false);
|
2010-06-16 16:30:48 -07:00
|
|
|
item.removeClass("stack-trayed");
|
|
|
|
item.setRotation(0);
|
2010-07-18 08:58:10 -07:00
|
|
|
|
|
|
|
item.droppable(true);
|
2010-08-10 15:36:56 -07:00
|
|
|
item.removeSubscriber(this, "close");
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-08-10 16:20:05 -07:00
|
|
|
if (typeof item.setResizable == 'function')
|
2010-10-09 12:46:18 -07:00
|
|
|
item.setResizable(true, options.immediately);
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2011-01-26 12:23:30 -08:00
|
|
|
if (this._children.length == 0 && !this.locked.close && !this.getTitle() &&
|
|
|
|
!options.dontClose) {
|
|
|
|
if (!GroupItems.getUnclosableGroupItemId()) {
|
|
|
|
this.close();
|
|
|
|
this._makeClosestTabActive();
|
|
|
|
} else {
|
|
|
|
// this.close(); this line is causing the leak but the leak doesn't happen after re-enabling it
|
|
|
|
}
|
|
|
|
} else if (!options.dontArrange) {
|
2010-10-09 12:46:18 -07:00
|
|
|
this.arrange({animate: !options.immediately});
|
2011-01-26 12:23:30 -08:00
|
|
|
}
|
2010-09-08 11:52:07 -07:00
|
|
|
|
|
|
|
this._sendToSubscribers("childRemoved",{ groupItemId: this.id, item: item });
|
2010-06-16 16:30:48 -07:00
|
|
|
} catch(e) {
|
|
|
|
Utils.log(e);
|
2010-04-22 09:38:37 -07:00
|
|
|
}
|
|
|
|
},
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-04-22 17:19:42 -07:00
|
|
|
// ----------
|
2010-06-17 15:57:45 -07:00
|
|
|
// Function: removeAll
|
2010-08-06 15:46:55 -07:00
|
|
|
// Removes all of the groupItem's children.
|
2010-10-08 15:59:24 -07:00
|
|
|
// The optional "options" param is passed to each remove call.
|
|
|
|
removeAll: function GroupItem_removeAll(options) {
|
|
|
|
let self = this;
|
|
|
|
let newOptions = {dontArrange: true};
|
|
|
|
if (options)
|
|
|
|
Utils.extend(newOptions, options);
|
|
|
|
|
|
|
|
let toRemove = this._children.concat();
|
2010-07-13 16:38:51 -07:00
|
|
|
toRemove.forEach(function(child) {
|
2010-10-08 15:59:24 -07:00
|
|
|
self.remove(child, newOptions);
|
2010-04-22 09:38:37 -07:00
|
|
|
});
|
2010-04-22 12:51:48 -07:00
|
|
|
},
|
2010-11-23 11:30:16 -08:00
|
|
|
|
|
|
|
// ----------
|
|
|
|
// Handles error event for loading app tab's fav icon.
|
|
|
|
_onAppTabError : function(event) {
|
|
|
|
iQ(".appTabIcon", this.$appTabTray).each(function(icon) {
|
|
|
|
let $icon = iQ(icon);
|
|
|
|
if ($icon.data("xulTab") == event.target) {
|
|
|
|
$icon.attr("src", Utils.defaultFaviconURL);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
},
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-09-10 02:50:14 -07:00
|
|
|
// ----------
|
|
|
|
// Adds the given xul:tab as an app tab in this group's apptab tray
|
|
|
|
addAppTab: function GroupItem_addAppTab(xulTab) {
|
|
|
|
let self = this;
|
|
|
|
|
2010-11-23 11:30:16 -08:00
|
|
|
xulTab.addEventListener("error", this._onAppTabError, false);
|
|
|
|
|
2010-09-12 20:45:02 -07:00
|
|
|
// add the icon
|
2010-11-23 11:30:16 -08:00
|
|
|
let iconUrl = xulTab.image || Utils.defaultFaviconURL;
|
2010-09-10 02:50:14 -07:00
|
|
|
let $appTab = iQ("<img>")
|
|
|
|
.addClass("appTabIcon")
|
2010-11-23 11:30:16 -08:00
|
|
|
.attr("src", iconUrl)
|
2010-09-10 02:50:14 -07:00
|
|
|
.data("xulTab", xulTab)
|
|
|
|
.appendTo(this.$appTabTray)
|
|
|
|
.click(function(event) {
|
2011-01-25 13:13:21 -08:00
|
|
|
if (!Utils.isLeftClick(event))
|
2010-09-10 02:50:14 -07:00
|
|
|
return;
|
|
|
|
|
|
|
|
GroupItems.setActiveGroupItem(self);
|
|
|
|
UI.goToTab(iQ(this).data("xulTab"));
|
|
|
|
});
|
2010-09-11 19:51:19 -07:00
|
|
|
|
2010-09-12 20:45:02 -07:00
|
|
|
// adjust the tray
|
2010-09-10 02:50:14 -07:00
|
|
|
let columnWidth = $appTab.width();
|
|
|
|
if (parseInt(this.$appTabTray.css("width")) != columnWidth) {
|
|
|
|
this.$appTabTray.css({width: columnWidth});
|
|
|
|
this.arrange();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2010-09-12 20:45:02 -07:00
|
|
|
// ----------
|
|
|
|
// Removes the given xul:tab as an app tab in this group's apptab tray
|
|
|
|
removeAppTab: function GroupItem_removeAppTab(xulTab) {
|
|
|
|
// remove the icon
|
|
|
|
iQ(".appTabIcon", this.$appTabTray).each(function(icon) {
|
|
|
|
let $icon = iQ(icon);
|
|
|
|
if ($icon.data("xulTab") != xulTab)
|
|
|
|
return;
|
|
|
|
|
|
|
|
$icon.remove();
|
|
|
|
});
|
|
|
|
|
|
|
|
// adjust the tray
|
|
|
|
if (!iQ(".appTabIcon", this.$appTabTray).length) {
|
|
|
|
this.$appTabTray.css({width: 0});
|
|
|
|
this.arrange();
|
|
|
|
}
|
2010-11-23 11:30:16 -08:00
|
|
|
|
|
|
|
xulTab.removeEventListener("error", this._onAppTabError, false);
|
2010-09-12 20:45:02 -07:00
|
|
|
},
|
|
|
|
|
2010-06-19 18:46:39 -07:00
|
|
|
// ----------
|
2010-06-19 18:49:08 -07:00
|
|
|
// Function: hideExpandControl
|
2010-08-06 15:46:55 -07:00
|
|
|
// Hide the control which expands a stacked groupItem into a quick-look view.
|
2010-09-08 10:02:08 -07:00
|
|
|
hideExpandControl: function GroupItem_hideExpandControl() {
|
2010-06-19 18:46:39 -07:00
|
|
|
this.$expander.hide();
|
|
|
|
},
|
|
|
|
|
|
|
|
// ----------
|
2010-06-19 18:49:08 -07:00
|
|
|
// Function: showExpandControl
|
2010-08-06 15:46:55 -07:00
|
|
|
// Show the control which expands a stacked groupItem into a quick-look view.
|
2010-09-08 10:02:08 -07:00
|
|
|
showExpandControl: function GroupItem_showExpandControl() {
|
2010-09-27 14:04:00 -07:00
|
|
|
let parentBB = this.getBounds();
|
|
|
|
let childBB = this.getChild(0).getBounds();
|
|
|
|
let padding = 7;
|
2010-06-19 18:46:39 -07:00
|
|
|
this.$expander
|
|
|
|
.show()
|
|
|
|
.css({
|
2010-09-27 14:04:00 -07:00
|
|
|
left: parentBB.width/2 - this.$expander.width()/2
|
2010-06-19 18:46:39 -07:00
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2010-07-18 08:58:10 -07:00
|
|
|
// ----------
|
2010-06-17 15:57:45 -07:00
|
|
|
// Function: shouldStack
|
2010-09-26 23:25:00 -07:00
|
|
|
// Returns true if the groupItem, given "count", should stack (instead of
|
|
|
|
// grid).
|
2010-09-08 10:02:08 -07:00
|
|
|
shouldStack: function GroupItem_shouldStack(count) {
|
2010-07-11 17:54:42 -07:00
|
|
|
if (count <= 1)
|
2010-05-26 11:29:31 -07:00
|
|
|
return false;
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-05-26 11:29:31 -07:00
|
|
|
var bb = this.getContentBounds();
|
|
|
|
var options = {
|
2010-11-06 17:08:57 -07:00
|
|
|
return: 'widthAndColumns',
|
2010-09-26 23:25:00 -07:00
|
|
|
count: count || this._children.length,
|
|
|
|
hideTitle: false
|
2010-05-26 11:29:31 -07:00
|
|
|
};
|
2010-11-16 14:35:00 -08:00
|
|
|
let arrObj = Items.arrange(null, bb, options);
|
|
|
|
|
|
|
|
let shouldStack = arrObj.childWidth < TabItems.minTabWidth * 1.35;
|
|
|
|
this._columns = shouldStack ? null : arrObj.columns;
|
2010-11-06 17:08:57 -07:00
|
|
|
|
|
|
|
return shouldStack;
|
2010-05-26 11:29:31 -07:00
|
|
|
},
|
2010-04-27 16:11:49 -07:00
|
|
|
|
2010-07-18 08:58:10 -07:00
|
|
|
// ----------
|
2010-06-17 15:57:45 -07:00
|
|
|
// Function: arrange
|
2010-07-18 08:58:10 -07:00
|
|
|
// Lays out all of the children.
|
|
|
|
//
|
|
|
|
// Parameters:
|
2011-01-12 13:48:42 -08:00
|
|
|
// options - passed to <Items.arrange> or <_stackArrange>, except those below
|
|
|
|
//
|
|
|
|
// Options:
|
|
|
|
// addTab - (boolean) if true, we add one to the child count
|
|
|
|
// oldDropIndex - if set, we will only set any bounds if the dropIndex has
|
|
|
|
// changed
|
|
|
|
// dropPos - (<Point>) a position where a tab is currently positioned, above
|
|
|
|
// this group.
|
|
|
|
// animate - (boolean) if true, movement of children will be animated.
|
|
|
|
//
|
|
|
|
// Returns:
|
|
|
|
// dropIndex - an index value for where an item would be dropped, if
|
|
|
|
// options.dropPos is given.
|
2010-09-08 10:02:08 -07:00
|
|
|
arrange: function GroupItem_arrange(options) {
|
2011-01-12 13:48:42 -08:00
|
|
|
if (!options)
|
|
|
|
options = {};
|
|
|
|
|
|
|
|
let childrenToArrange = [];
|
|
|
|
this._children.forEach(function(child) {
|
|
|
|
if (child.isDragging)
|
|
|
|
options.addTab = true;
|
|
|
|
else
|
|
|
|
childrenToArrange.push(child);
|
|
|
|
});
|
|
|
|
|
2010-10-11 11:57:00 -07:00
|
|
|
if (GroupItems._arrangePaused) {
|
|
|
|
GroupItems.pushArrange(this, options);
|
2011-01-16 07:38:48 -08:00
|
|
|
return false;
|
2010-10-11 11:57:00 -07:00
|
|
|
}
|
2011-01-16 07:38:48 -08:00
|
|
|
|
|
|
|
let shouldStack = this.shouldStack(childrenToArrange.length + (options.addTab ? 1 : 0));
|
|
|
|
let box = this.getContentBounds();
|
|
|
|
|
|
|
|
// if we should stack and we're not expanded
|
|
|
|
if (shouldStack && !this.expanded) {
|
|
|
|
this.showExpandControl();
|
|
|
|
this._stackArrange(childrenToArrange, box, options);
|
|
|
|
return false;
|
2010-05-26 11:29:31 -07:00
|
|
|
} else {
|
2011-01-16 07:38:48 -08:00
|
|
|
this.hideExpandControl();
|
|
|
|
// a dropIndex is returned
|
|
|
|
return this._gridArrange(childrenToArrange, box, options);
|
2010-05-26 11:29:31 -07:00
|
|
|
}
|
|
|
|
},
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-05-26 11:29:31 -07:00
|
|
|
// ----------
|
2010-06-17 15:57:45 -07:00
|
|
|
// Function: _stackArrange
|
2010-07-18 08:58:10 -07:00
|
|
|
// Arranges the children in a stack.
|
|
|
|
//
|
|
|
|
// Parameters:
|
2011-01-16 07:38:48 -08:00
|
|
|
// childrenToArrange - array of <TabItem> children
|
2010-06-17 15:57:45 -07:00
|
|
|
// bb - <Rect> to arrange within
|
|
|
|
// options - see below
|
|
|
|
//
|
2010-07-18 08:58:10 -07:00
|
|
|
// Possible "options" properties:
|
2010-06-17 15:57:45 -07:00
|
|
|
// animate - whether to animate; default: true.
|
2011-01-16 07:38:48 -08:00
|
|
|
_stackArrange: function GroupItem__stackArrange(childrenToArrange, bb, options) {
|
2011-01-17 09:31:39 -08:00
|
|
|
if (!options)
|
2010-11-30 11:50:42 -08:00
|
|
|
options = {};
|
2011-01-17 09:31:39 -08:00
|
|
|
var animate = "animate" in options ? options.animate : true;
|
2010-11-30 11:50:42 -08:00
|
|
|
|
2011-01-16 07:38:48 -08:00
|
|
|
var count = childrenToArrange.length;
|
2010-07-11 17:54:42 -07:00
|
|
|
if (!count)
|
2010-05-26 11:29:31 -07:00
|
|
|
return;
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-11-05 06:32:00 -07:00
|
|
|
let itemAspect = TabItems.tabHeight / TabItems.tabWidth;
|
|
|
|
let zIndex = this.getZ() + count + 1;
|
|
|
|
let maxRotation = 35; // degress
|
|
|
|
let scale = 0.7;
|
|
|
|
let newTabsPad = 10;
|
|
|
|
let bbAspect = bb.height / bb.width;
|
|
|
|
let numInPile = 6;
|
|
|
|
let angleDelta = 3.5; // degrees
|
2010-07-06 11:42:23 -07:00
|
|
|
|
2010-09-26 23:25:00 -07:00
|
|
|
// compute size of the entire stack, modulo rotation.
|
|
|
|
let size;
|
2010-11-30 11:50:42 -08:00
|
|
|
if (bbAspect > itemAspect) { // Tall, thin groupItem
|
2010-09-26 23:25:00 -07:00
|
|
|
size = TabItems.calcValidSize(new Point(bb.width * scale, -1),
|
|
|
|
{hideTitle:true});
|
|
|
|
} else { // Short, wide groupItem
|
|
|
|
size = TabItems.calcValidSize(new Point(-1, bb.height * scale),
|
|
|
|
{hideTitle:true});
|
|
|
|
}
|
|
|
|
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-07-06 11:42:23 -07:00
|
|
|
// x is the left margin that the stack will have, within the content area (bb)
|
|
|
|
// y is the vertical margin
|
2010-09-26 23:25:00 -07:00
|
|
|
var x = (bb.width - size.x) / 2;
|
|
|
|
var y = Math.min(size.x, (bb.height - size.y) / 2);
|
|
|
|
var box = new Rect(bb.left + x, bb.top + y, size.x, size.y);
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-05-26 11:29:31 -07:00
|
|
|
var self = this;
|
|
|
|
var children = [];
|
2011-01-16 07:38:48 -08:00
|
|
|
childrenToArrange.forEach(function GroupItem__stackArrange_order(child) {
|
2010-11-05 06:32:00 -07:00
|
|
|
// Children are still considered stacked even if they're hidden later.
|
|
|
|
child.addClass("stacked");
|
|
|
|
child.isStacked = true;
|
|
|
|
if (numInPile-- > 0) {
|
|
|
|
child.setHidden(false);
|
|
|
|
if (child == self.topChild)
|
|
|
|
children.unshift(child);
|
|
|
|
else
|
|
|
|
children.push(child);
|
|
|
|
} else {
|
|
|
|
child.setHidden(true);
|
|
|
|
}
|
2010-05-26 11:29:31 -07:00
|
|
|
});
|
2010-11-30 11:50:42 -08:00
|
|
|
|
2010-11-05 06:32:00 -07:00
|
|
|
let angleAccum = 0;
|
2011-01-12 07:20:28 -08:00
|
|
|
children.forEach(function GroupItem__stackArrange_apply(child, index) {
|
2010-07-11 17:54:42 -07:00
|
|
|
if (!child.locked.bounds) {
|
2010-05-26 11:29:31 -07:00
|
|
|
child.setZ(zIndex);
|
|
|
|
zIndex--;
|
2010-11-30 11:50:42 -08:00
|
|
|
|
2010-09-26 23:25:00 -07:00
|
|
|
// Force a recalculation of height because we've changed how the title
|
|
|
|
// is shown.
|
|
|
|
child.setBounds(box, !animate, {force:true});
|
2010-11-05 06:32:00 -07:00
|
|
|
child.setRotation((UI.rtl ? -1 : 1) * angleAccum);
|
|
|
|
angleAccum += angleDelta;
|
2010-05-26 11:29:31 -07:00
|
|
|
}
|
|
|
|
});
|
2010-11-30 11:50:42 -08:00
|
|
|
|
2010-05-26 11:29:31 -07:00
|
|
|
self._isStacked = true;
|
|
|
|
},
|
2011-01-16 07:38:48 -08:00
|
|
|
|
|
|
|
// ----------
|
|
|
|
// Function: _gridArrange
|
|
|
|
// Arranges the children into a grid.
|
|
|
|
//
|
|
|
|
// Parameters:
|
|
|
|
// childrenToArrange - array of <TabItem> children
|
|
|
|
// box - <Rect> to arrange within
|
|
|
|
// options - see below
|
|
|
|
//
|
|
|
|
// Possible "options" properties:
|
|
|
|
// animate - whether to animate; default: true.
|
|
|
|
// z - (int) a z-index to assign the children
|
|
|
|
// columns - the number of columns to use in the layout, if known in advance
|
|
|
|
//
|
|
|
|
// Returns:
|
|
|
|
// dropIndex - (int) the index at which a dragged item (if there is one) should be added
|
|
|
|
// if it is dropped. Otherwise (boolean) false.
|
|
|
|
_gridArrange: function GroupItem__gridArrange(childrenToArrange, box, options) {
|
|
|
|
this.topChild = null;
|
|
|
|
let arrangeOptions;
|
|
|
|
if (this.expanded) {
|
|
|
|
// if we're expanded, we actually want to use the expanded tray's bounds.
|
|
|
|
box = new Rect(this.expanded.bounds);
|
|
|
|
box.inset(8, 8);
|
|
|
|
arrangeOptions = Utils.extend({}, options, {z: 99999});
|
|
|
|
} else {
|
|
|
|
this._isStacked = false;
|
|
|
|
arrangeOptions = Utils.extend({}, options, {
|
|
|
|
columns: this._columns
|
|
|
|
});
|
|
|
|
|
|
|
|
childrenToArrange.forEach(function(child) {
|
2010-09-26 23:25:00 -07:00
|
|
|
child.removeClass("stacked");
|
|
|
|
child.isStacked = false;
|
2010-11-05 06:32:00 -07:00
|
|
|
child.setHidden(false);
|
2011-01-16 07:38:48 -08:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!childrenToArrange.length)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// Items.arrange will determine where/how the child items should be
|
|
|
|
// placed, but will *not* actually move them for us. This is our job.
|
|
|
|
let result = Items.arrange(childrenToArrange, box, arrangeOptions);
|
2010-09-26 23:25:00 -07:00
|
|
|
let {dropIndex, rects, columns} = result;
|
2011-01-16 07:38:48 -08:00
|
|
|
if ("oldDropIndex" in options && options.oldDropIndex === dropIndex)
|
|
|
|
return dropIndex;
|
|
|
|
|
2010-09-26 23:25:00 -07:00
|
|
|
this._columns = columns;
|
2011-01-16 07:38:48 -08:00
|
|
|
let index = 0;
|
|
|
|
let self = this;
|
|
|
|
childrenToArrange.forEach(function GroupItem_arrange_children_each(child, i) {
|
|
|
|
// If dropIndex spacing is active and this is a child after index,
|
|
|
|
// bump it up one so we actually use the correct rect
|
|
|
|
// (and skip one for the dropPos)
|
|
|
|
if (self._dropSpaceActive && index === dropIndex)
|
|
|
|
index++;
|
|
|
|
if (!child.locked.bounds) {
|
|
|
|
child.setBounds(rects[index], !options.animate);
|
|
|
|
child.setRotation(0);
|
|
|
|
if (arrangeOptions.z)
|
|
|
|
child.setZ(arrangeOptions.z);
|
|
|
|
}
|
|
|
|
index++;
|
|
|
|
});
|
|
|
|
|
|
|
|
return dropIndex;
|
|
|
|
},
|
2010-05-26 11:29:31 -07:00
|
|
|
|
|
|
|
// ----------
|
|
|
|
// Function: childHit
|
2010-08-06 15:46:55 -07:00
|
|
|
// Called by one of the groupItem's children when the child is clicked on.
|
2010-07-18 08:58:10 -07:00
|
|
|
//
|
2010-06-17 15:57:45 -07:00
|
|
|
// Returns an object:
|
2010-05-26 11:29:31 -07:00
|
|
|
// shouldZoom - true if the browser should launch into the tab represented by the child
|
|
|
|
// callback - called after the zoom animation is complete
|
2010-09-08 10:02:08 -07:00
|
|
|
childHit: function GroupItem_childHit(child) {
|
2010-05-26 11:29:31 -07:00
|
|
|
var self = this;
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-05-26 11:29:31 -07:00
|
|
|
// ___ normal click
|
2010-07-11 17:54:42 -07:00
|
|
|
if (!this._isStacked || this.expanded) {
|
2010-05-26 11:29:31 -07:00
|
|
|
return {
|
|
|
|
shouldZoom: true,
|
|
|
|
callback: function() {
|
|
|
|
self.collapse();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2010-08-06 15:46:55 -07:00
|
|
|
GroupItems.setActiveGroupItem(self);
|
2010-07-11 18:08:33 -07:00
|
|
|
return { shouldZoom: true };
|
2010-06-19 18:46:39 -07:00
|
|
|
},
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-09-08 10:02:08 -07:00
|
|
|
expand: function GroupItem_expand() {
|
2010-06-19 18:46:39 -07:00
|
|
|
var self = this;
|
2010-05-26 11:29:31 -07:00
|
|
|
// ___ we're stacked, and command is held down so expand
|
2010-08-06 15:46:55 -07:00
|
|
|
GroupItems.setActiveGroupItem(self);
|
2011-01-18 08:25:49 -08:00
|
|
|
let activeTab = this.topChild || this.getChildren()[0];
|
|
|
|
UI.setActiveTab(activeTab);
|
|
|
|
|
2010-06-19 18:46:39 -07:00
|
|
|
var startBounds = this.getChild(0).getBounds();
|
2010-06-04 15:08:24 -07:00
|
|
|
var $tray = iQ("<div>").css({
|
2010-05-26 11:29:31 -07:00
|
|
|
top: startBounds.top,
|
|
|
|
left: startBounds.left,
|
|
|
|
width: startBounds.width,
|
|
|
|
height: startBounds.height,
|
|
|
|
position: "absolute",
|
|
|
|
zIndex: 99998
|
|
|
|
}).appendTo("body");
|
2011-01-16 07:38:48 -08:00
|
|
|
$tray[0].id = "expandedTray";
|
2010-06-19 18:46:39 -07:00
|
|
|
|
2010-05-26 11:29:31 -07:00
|
|
|
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));
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-05-26 11:29:31 -07:00
|
|
|
var pos = {left: startBounds.left, top: startBounds.top};
|
2010-08-11 20:08:33 -07:00
|
|
|
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;
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-06-07 16:16:55 -07:00
|
|
|
$tray
|
|
|
|
.animate({
|
|
|
|
width: overlayWidth,
|
|
|
|
height: overlayHeight,
|
|
|
|
top: pos.top,
|
|
|
|
left: pos.left
|
|
|
|
}, {
|
2010-07-18 08:58:10 -07:00
|
|
|
duration: 200,
|
2011-01-16 07:38:48 -08:00
|
|
|
easing: "tabviewBounce",
|
|
|
|
complete: function GroupItem_expand_animate_complete() {
|
|
|
|
self._sendToSubscribers("expanded");
|
|
|
|
}
|
2010-06-07 16:16:55 -07:00
|
|
|
})
|
|
|
|
.addClass("overlay");
|
2010-05-26 11:29:31 -07:00
|
|
|
|
2010-07-30 02:54:30 -07:00
|
|
|
this._children.forEach(function(child) {
|
2010-05-26 11:29:31 -07:00
|
|
|
child.addClass("stack-trayed");
|
2010-11-05 06:32:00 -07:00
|
|
|
child.setHidden(false);
|
2010-05-26 11:29:31 -07:00
|
|
|
});
|
|
|
|
|
2010-06-04 15:08:24 -07:00
|
|
|
var $shield = iQ('<div>')
|
2010-08-11 20:08:33 -07:00
|
|
|
.addClass('shield')
|
2010-05-26 11:29:31 -07:00
|
|
|
.css({
|
|
|
|
zIndex: 99997
|
|
|
|
})
|
|
|
|
.appendTo('body')
|
|
|
|
.click(function() { // just in case
|
|
|
|
self.collapse();
|
|
|
|
});
|
2010-06-19 18:46:39 -07:00
|
|
|
|
|
|
|
// There is a race-condition here. If there is
|
|
|
|
// a mouse-move while the shield is coming up
|
|
|
|
// it will collapse, which we don't want. Thus,
|
|
|
|
// we wait a little bit before adding this event
|
|
|
|
// handler.
|
2010-07-30 02:54:30 -07:00
|
|
|
setTimeout(function() {
|
2010-06-19 18:46:39 -07:00
|
|
|
$shield.mouseover(function() {
|
|
|
|
self.collapse();
|
|
|
|
});
|
2010-06-21 14:49:38 -07:00
|
|
|
}, 200);
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-05-26 11:29:31 -07:00
|
|
|
this.expanded = {
|
|
|
|
$tray: $tray,
|
|
|
|
$shield: $shield,
|
|
|
|
bounds: new Rect(pos.left, pos.top, overlayWidth, overlayHeight)
|
|
|
|
};
|
2010-07-18 08:58:10 -07:00
|
|
|
|
|
|
|
this.arrange();
|
2010-05-26 11:29:31 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
// ----------
|
2010-06-17 15:57:45 -07:00
|
|
|
// Function: collapse
|
2010-08-06 15:46:55 -07:00
|
|
|
// Collapses the groupItem from the expanded "tray" mode.
|
2010-09-08 10:02:08 -07:00
|
|
|
collapse: function GroupItem_collapse() {
|
2010-07-11 17:54:42 -07:00
|
|
|
if (this.expanded) {
|
2010-05-26 11:29:31 -07:00
|
|
|
var z = this.getZ();
|
|
|
|
var box = this.getBounds();
|
2011-01-16 07:38:48 -08:00
|
|
|
let self = this;
|
2010-05-26 11:29:31 -07:00
|
|
|
this.expanded.$tray
|
|
|
|
.css({
|
|
|
|
zIndex: z + 1
|
|
|
|
})
|
|
|
|
.animate({
|
|
|
|
width: box.width,
|
|
|
|
height: box.height,
|
|
|
|
top: box.top,
|
|
|
|
left: box.left,
|
|
|
|
opacity: 0
|
2010-06-07 16:16:55 -07:00
|
|
|
}, {
|
|
|
|
duration: 350,
|
2010-07-29 12:37:25 -07:00
|
|
|
easing: "tabviewBounce",
|
2011-01-16 07:38:48 -08:00
|
|
|
complete: function GroupItem_collapse_animate_complete() {
|
2010-07-18 08:58:10 -07:00
|
|
|
iQ(this).remove();
|
2011-01-16 07:38:48 -08:00
|
|
|
self._sendToSubscribers("collapsed");
|
2010-06-07 16:16:55 -07:00
|
|
|
}
|
2010-05-26 11:29:31 -07:00
|
|
|
});
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-05-26 11:29:31 -07:00
|
|
|
this.expanded.$shield.remove();
|
|
|
|
this.expanded = null;
|
|
|
|
|
2010-07-30 02:54:30 -07:00
|
|
|
this._children.forEach(function(child) {
|
2010-05-26 11:29:31 -07:00
|
|
|
child.removeClass("stack-trayed");
|
|
|
|
});
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-05-26 11:29:31 -07:00
|
|
|
this.arrange({z: z + 2});
|
|
|
|
}
|
2010-03-17 01:07:00 -07:00
|
|
|
},
|
2010-07-18 08:58:10 -07:00
|
|
|
|
|
|
|
// ----------
|
2010-06-17 15:57:45 -07:00
|
|
|
// Function: _addHandlers
|
2010-07-18 08:58:10 -07:00
|
|
|
// Helper routine for the constructor; adds various event handlers to the container.
|
2010-09-08 10:02:08 -07:00
|
|
|
_addHandlers: function GroupItem__addHandlers(container) {
|
2011-01-09 19:23:56 -08:00
|
|
|
let self = this;
|
|
|
|
|
|
|
|
// Create new tab and zoom in on it after a double click
|
|
|
|
container.mousedown(function(e) {
|
|
|
|
if (Date.now() - self._lastClick <= UI.DBLCLICK_INTERVAL &&
|
|
|
|
(self._lastClickPositions.x - UI.DBLCLICK_OFFSET) <= e.clientX &&
|
|
|
|
(self._lastClickPositions.x + UI.DBLCLICK_OFFSET) >= e.clientX &&
|
|
|
|
(self._lastClickPositions.y - UI.DBLCLICK_OFFSET) <= e.clientY &&
|
|
|
|
(self._lastClickPositions.y + UI.DBLCLICK_OFFSET) >= e.clientY) {
|
|
|
|
self.newTab();
|
|
|
|
self._lastClick = 0;
|
|
|
|
self._lastClickPositions = null;
|
|
|
|
} else {
|
|
|
|
self._lastClick = Date.now();
|
|
|
|
self._lastClickPositions = new Point(e.clientX, e.clientY);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2011-01-12 13:48:42 -08:00
|
|
|
var dropIndex = false;
|
|
|
|
var dropSpaceTimer = null;
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2011-01-12 13:48:42 -08:00
|
|
|
// When the _dropSpaceActive flag is turned on on a group, and a tab is
|
|
|
|
// dragged on top, a space will open up.
|
|
|
|
this._dropSpaceActive = false;
|
|
|
|
|
|
|
|
this.dropOptions.over = function GroupItem_dropOptions_over(event) {
|
2010-08-08 15:32:08 -07:00
|
|
|
iQ(this.container).addClass("acceptsDrop");
|
2010-06-19 11:56:07 -07:00
|
|
|
};
|
2011-01-12 13:48:42 -08:00
|
|
|
this.dropOptions.move = function GroupItem_dropOptions_move(event) {
|
|
|
|
let oldDropIndex = dropIndex;
|
|
|
|
let dropPos = drag.info.item.getBounds().center();
|
|
|
|
let options = {dropPos: dropPos,
|
|
|
|
addTab: self._dropSpaceActive && drag.info.item.parent != self,
|
|
|
|
oldDropIndex: oldDropIndex};
|
2011-01-18 04:25:59 -08:00
|
|
|
let newDropIndex = self.arrange(options);
|
2011-01-12 13:48:42 -08:00
|
|
|
// If this is a new drop index, start a timer!
|
|
|
|
if (newDropIndex !== oldDropIndex) {
|
|
|
|
dropIndex = newDropIndex;
|
|
|
|
if (this._dropSpaceActive)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (dropSpaceTimer) {
|
|
|
|
clearTimeout(dropSpaceTimer);
|
|
|
|
dropSpaceTimer = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
dropSpaceTimer = setTimeout(function GroupItem_arrange_evaluateDropSpace() {
|
|
|
|
// Note that dropIndex's scope is GroupItem__addHandlers, but
|
|
|
|
// newDropIndex's scope is GroupItem_dropOptions_move. Thus,
|
|
|
|
// dropIndex may change with other movement events before we come
|
|
|
|
// back and check this. If it's still the same dropIndex, activate
|
|
|
|
// drop space display!
|
|
|
|
if (dropIndex === newDropIndex) {
|
|
|
|
self._dropSpaceActive = true;
|
|
|
|
dropIndex = self.arrange({dropPos: dropPos,
|
|
|
|
addTab: drag.info.item.parent != self,
|
|
|
|
animate: true});
|
|
|
|
}
|
|
|
|
dropSpaceTimer = null;
|
|
|
|
}, 250);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
this.dropOptions.drop = function GroupItem_dropOptions_drop(event) {
|
2010-06-22 16:42:06 -07:00
|
|
|
iQ(this.container).removeClass("acceptsDrop");
|
2011-01-12 13:48:42 -08:00
|
|
|
let options = {};
|
|
|
|
if (this._dropSpaceActive)
|
|
|
|
this._dropSpaceActive = false;
|
|
|
|
|
|
|
|
if (dropSpaceTimer) {
|
|
|
|
clearTimeout(dropSpaceTimer);
|
|
|
|
dropSpaceTimer = null;
|
|
|
|
// If we drop this item before the timed rearrange was executed,
|
|
|
|
// we won't have an accurate dropIndex value. Get that now.
|
|
|
|
let dropPos = drag.info.item.getBounds().center();
|
|
|
|
dropIndex = self.arrange({dropPos: dropPos,
|
|
|
|
addTab: drag.info.item.parent != self,
|
|
|
|
animate: true});
|
|
|
|
}
|
2011-01-21 13:12:12 -08:00
|
|
|
|
|
|
|
// remove the item from its parent if that's not the current groupItem.
|
|
|
|
// this may occur when dragging too quickly so the out event is not fired.
|
|
|
|
var groupItem = drag.info.item.parent;
|
|
|
|
if (groupItem && self !== groupItem)
|
|
|
|
groupItem.remove(drag.info.$el, {dontClose: true});
|
|
|
|
|
2011-01-12 13:48:42 -08:00
|
|
|
if (dropIndex !== false)
|
2011-01-21 13:12:12 -08:00
|
|
|
options = {index: dropIndex};
|
2011-01-12 13:48:42 -08:00
|
|
|
this.add(drag.info.$el, options);
|
2010-08-08 17:27:47 -07:00
|
|
|
GroupItems.setActiveGroupItem(this);
|
2011-01-12 13:48:42 -08:00
|
|
|
dropIndex = false;
|
2010-06-19 11:56:07 -07:00
|
|
|
};
|
2011-01-12 13:48:42 -08:00
|
|
|
this.dropOptions.out = function GroupItem_dropOptions_out(event) {
|
|
|
|
dropIndex = false;
|
|
|
|
if (this._dropSpaceActive)
|
|
|
|
this._dropSpaceActive = false;
|
|
|
|
|
|
|
|
if (dropSpaceTimer) {
|
|
|
|
clearTimeout(dropSpaceTimer);
|
|
|
|
dropSpaceTimer = null;
|
|
|
|
}
|
|
|
|
self.arrange();
|
|
|
|
var groupItem = drag.info.item.parent;
|
|
|
|
if (groupItem)
|
|
|
|
groupItem.remove(drag.info.$el, {dontClose: true});
|
|
|
|
iQ(this.container).removeClass("acceptsDrop");
|
|
|
|
}
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-07-11 17:54:42 -07:00
|
|
|
if (!this.locked.bounds)
|
2010-06-22 16:42:06 -07:00
|
|
|
this.draggable();
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-06-22 16:42:06 -07:00
|
|
|
this.droppable(true);
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-07-30 02:54:30 -07:00
|
|
|
this.$expander.click(function() {
|
2010-06-19 18:46:39 -07:00
|
|
|
self.expand();
|
|
|
|
});
|
2010-05-26 11:29:31 -07:00
|
|
|
},
|
|
|
|
|
2010-07-18 08:58:10 -07:00
|
|
|
// ----------
|
2010-06-17 15:57:45 -07:00
|
|
|
// Function: setResizable
|
2010-08-06 15:46:55 -07:00
|
|
|
// Sets whether the groupItem is resizable and updates the UI accordingly.
|
2010-10-09 12:46:18 -07:00
|
|
|
setResizable: function GroupItem_setResizable(value, immediately) {
|
2011-01-12 07:20:28 -08:00
|
|
|
this.resizeOptions.minWidth = GroupItems.minGroupWidth;
|
|
|
|
this.resizeOptions.minHeight = GroupItems.minGroupHeight;
|
2010-06-19 17:45:23 -07:00
|
|
|
|
2010-07-11 17:54:42 -07:00
|
|
|
if (value) {
|
2010-10-09 12:46:18 -07:00
|
|
|
immediately ? this.$resizer.show() : this.$resizer.fadeIn();
|
2010-06-22 16:42:06 -07:00
|
|
|
this.resizable(true);
|
2010-05-26 11:29:31 -07:00
|
|
|
} else {
|
2010-10-09 12:46:18 -07:00
|
|
|
immediately ? this.$resizer.hide() : this.$resizer.fadeOut();
|
2010-06-22 16:42:06 -07:00
|
|
|
this.resizable(false);
|
2010-05-17 16:04:36 -07:00
|
|
|
}
|
2010-05-26 11:29:31 -07:00
|
|
|
},
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-05-26 11:29:31 -07:00
|
|
|
// ----------
|
2010-06-17 15:57:45 -07:00
|
|
|
// Function: newTab
|
2010-08-06 15:46:55 -07:00
|
|
|
// Creates a new tab within this groupItem.
|
2010-09-08 10:02:08 -07:00
|
|
|
newTab: function GroupItem_newTab(url) {
|
2010-08-06 15:46:55 -07:00
|
|
|
GroupItems.setActiveGroupItem(this);
|
2010-07-22 12:34:23 -07:00
|
|
|
let newTab = gBrowser.loadOneTab(url || "about:blank", {inBackground: true});
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-09-11 19:51:19 -07:00
|
|
|
// TabItems will have handled the new tab and added the tabItem property.
|
|
|
|
// We don't have to check if it's an app tab (and therefore wouldn't have a
|
2010-09-10 02:50:14 -07:00
|
|
|
// TabItem), since we've just created it.
|
2011-01-11 00:20:08 -08:00
|
|
|
newTab._tabViewTabItem.zoomIn(!url);
|
2010-05-26 11:29:31 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
// ----------
|
2010-07-25 20:53:21 -07:00
|
|
|
// Function: reorderTabItemsBasedOnTabOrder
|
2010-08-06 15:46:55 -07:00
|
|
|
// Reorders the tabs in a groupItem based on the arrangment of the tabs
|
2010-06-17 16:38:07 -07:00
|
|
|
// shown in the tab bar. It does it by sorting the children
|
2010-08-06 15:46:55 -07:00
|
|
|
// of the groupItem by the positions of their respective tabs in the
|
2010-05-26 11:29:31 -07:00
|
|
|
// tab bar.
|
2010-09-08 10:02:08 -07:00
|
|
|
reorderTabItemsBasedOnTabOrder: function GroupItem_reorderTabItemsBasedOnTabOrder() {
|
2010-07-22 15:09:36 -07:00
|
|
|
this._children.sort(function(a,b) a.tab._tPos - b.tab._tPos);
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-05-26 11:29:31 -07:00
|
|
|
this.arrange({animate: false});
|
|
|
|
// this.arrange calls this.save for us
|
|
|
|
},
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-07-25 20:53:21 -07:00
|
|
|
// Function: reorderTabsBasedOnTabItemOrder
|
|
|
|
// Reorders the tabs in the tab bar based on the arrangment of the tabs
|
2010-08-06 15:46:55 -07:00
|
|
|
// shown in the groupItem.
|
2010-09-08 10:02:08 -07:00
|
|
|
reorderTabsBasedOnTabItemOrder: function GroupItem_reorderTabsBasedOnTabItemOrder() {
|
2011-01-21 22:25:12 -08:00
|
|
|
let indices;
|
|
|
|
let tabs = this._children.map(function (tabItem) tabItem.tab);
|
2010-11-24 13:23:29 -08:00
|
|
|
|
2011-01-21 22:25:12 -08:00
|
|
|
tabs.forEach(function (tab, index) {
|
|
|
|
if (!indices)
|
|
|
|
indices = tabs.map(function (tab) tab._tPos);
|
|
|
|
|
|
|
|
let start = index ? indices[index - 1] + 1 : 0;
|
|
|
|
let end = index + 1 < indices.length ? indices[index + 1] - 1 : Infinity;
|
|
|
|
let targetRange = new Range(start, end);
|
|
|
|
|
|
|
|
if (!targetRange.contains(tab._tPos)) {
|
|
|
|
gBrowser.moveTabTo(tab, start);
|
|
|
|
indices = null;
|
2010-11-24 13:23:29 -08:00
|
|
|
}
|
2010-07-25 20:53:21 -07:00
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2010-06-17 16:38:07 -07:00
|
|
|
// ----------
|
|
|
|
// Function: setTopChild
|
|
|
|
// Sets the <Item> that should be displayed on top when in stack mode.
|
2010-09-08 10:02:08 -07:00
|
|
|
setTopChild: function GroupItem_setTopChild(topChild) {
|
2010-06-17 16:38:07 -07:00
|
|
|
this.topChild = topChild;
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-06-17 16:38:07 -07:00
|
|
|
this.arrange({animate: false});
|
|
|
|
// this.arrange calls this.save for us
|
|
|
|
},
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-05-26 11:29:31 -07:00
|
|
|
// ----------
|
|
|
|
// Function: getChild
|
|
|
|
// Returns the nth child tab or null if index is out of range.
|
|
|
|
//
|
|
|
|
// Parameters:
|
|
|
|
// index - the index of the child tab to return, use negative
|
|
|
|
// numbers to index from the end (-1 is the last child)
|
2010-09-08 10:02:08 -07:00
|
|
|
getChild: function GroupItem_getChild(index) {
|
2010-07-30 02:54:30 -07:00
|
|
|
if (index < 0)
|
2010-07-17 22:03:31 -07:00
|
|
|
index = this._children.length + index;
|
2010-07-30 02:54:30 -07:00
|
|
|
if (index >= this._children.length || index < 0)
|
2010-07-17 22:03:31 -07:00
|
|
|
return null;
|
2010-05-26 11:29:31 -07:00
|
|
|
return this._children[index];
|
|
|
|
},
|
2010-07-16 00:00:20 -07:00
|
|
|
|
|
|
|
// ----------
|
|
|
|
// Function: getChildren
|
|
|
|
// Returns all children.
|
2010-09-08 10:02:08 -07:00
|
|
|
getChildren: function GroupItem_getChildren() {
|
2010-07-16 00:00:20 -07:00
|
|
|
return this._children;
|
2010-05-26 11:29:31 -07:00
|
|
|
}
|
|
|
|
});
|
2010-04-01 17:20:59 -07:00
|
|
|
|
2010-05-26 11:29:31 -07:00
|
|
|
// ##########
|
2010-08-06 15:46:55 -07:00
|
|
|
// Class: GroupItems
|
2010-09-26 23:25:00 -07:00
|
|
|
// Singleton for managing all <GroupItem>s.
|
2010-09-08 10:02:08 -07:00
|
|
|
let GroupItems = {
|
2010-08-06 15:46:55 -07:00
|
|
|
groupItems: [],
|
2010-07-26 17:15:19 -07:00
|
|
|
nextID: 1,
|
|
|
|
_inited: false,
|
2010-08-08 17:27:47 -07:00
|
|
|
_activeGroupItem: null,
|
2010-08-08 15:32:08 -07:00
|
|
|
_activeOrphanTab: null,
|
2010-09-11 19:51:19 -07:00
|
|
|
_cleanupFunctions: [],
|
2010-10-11 11:57:00 -07:00
|
|
|
_arrangePaused: false,
|
|
|
|
_arrangesPending: [],
|
2010-11-14 19:12:10 -08:00
|
|
|
_removingHiddenGroups: false,
|
2010-11-16 14:35:00 -08:00
|
|
|
_delayedModUpdates: [],
|
2011-01-12 07:20:28 -08:00
|
|
|
minGroupHeight: 110,
|
|
|
|
minGroupWidth: 125,
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-08-06 17:02:21 -07:00
|
|
|
// ----------
|
|
|
|
// Function: init
|
2010-09-08 10:02:08 -07:00
|
|
|
init: function GroupItems_init() {
|
2010-09-11 19:51:19 -07:00
|
|
|
let self = this;
|
|
|
|
|
|
|
|
// setup attr modified handler, and prepare for its uninit
|
|
|
|
function handleAttrModified(xulTab) {
|
|
|
|
self._handleAttrModified(xulTab);
|
|
|
|
}
|
|
|
|
|
2010-11-16 14:35:00 -08:00
|
|
|
// make sure any closed tabs are removed from the delay update list
|
|
|
|
function handleClose(xulTab) {
|
|
|
|
let idx = self._delayedModUpdates.indexOf(xulTab);
|
|
|
|
if (idx != -1)
|
|
|
|
self._delayedModUpdates.splice(idx, 1);
|
|
|
|
}
|
|
|
|
|
2010-09-11 19:51:19 -07:00
|
|
|
AllTabs.register("attrModified", handleAttrModified);
|
2010-11-16 14:35:00 -08:00
|
|
|
AllTabs.register("close", handleClose);
|
2010-09-11 19:51:19 -07:00
|
|
|
this._cleanupFunctions.push(function() {
|
|
|
|
AllTabs.unregister("attrModified", handleAttrModified);
|
2010-11-16 14:35:00 -08:00
|
|
|
AllTabs.unregister("close", handleClose);
|
2010-09-11 19:51:19 -07:00
|
|
|
});
|
2010-08-06 17:02:21 -07:00
|
|
|
},
|
|
|
|
|
2010-08-11 21:36:58 -07:00
|
|
|
// ----------
|
|
|
|
// Function: uninit
|
2010-09-08 10:02:08 -07:00
|
|
|
uninit : function GroupItems_uninit () {
|
2010-09-12 20:45:02 -07:00
|
|
|
// call our cleanup functions
|
2010-09-11 19:51:19 -07:00
|
|
|
this._cleanupFunctions.forEach(function(func) {
|
|
|
|
func();
|
|
|
|
});
|
|
|
|
|
|
|
|
this._cleanupFunctions = [];
|
|
|
|
|
2010-09-12 20:45:02 -07:00
|
|
|
// additional clean up
|
2010-08-11 21:36:58 -07:00
|
|
|
this.groupItems = null;
|
|
|
|
},
|
|
|
|
|
2011-01-20 19:25:18 -08:00
|
|
|
// ----------
|
|
|
|
// Function: newGroup
|
|
|
|
// Creates a new empty group.
|
|
|
|
newGroup: function () {
|
|
|
|
let bounds = new Rect(20, 20, 250, 200);
|
|
|
|
return new GroupItem([], {bounds: bounds, immediately: true});
|
|
|
|
},
|
|
|
|
|
2010-10-11 11:57:00 -07:00
|
|
|
// ----------
|
|
|
|
// Function: pauseArrange
|
|
|
|
// Bypass arrange() calls and collect for resolution in
|
|
|
|
// resumeArrange()
|
|
|
|
pauseArrange: function GroupItems_pauseArrange() {
|
|
|
|
Utils.assert(this._arrangePaused == false,
|
|
|
|
"pauseArrange has been called while already paused");
|
|
|
|
Utils.assert(this._arrangesPending.length == 0,
|
|
|
|
"There are bypassed arrange() calls that haven't been resolved");
|
|
|
|
this._arrangePaused = true;
|
|
|
|
},
|
|
|
|
|
|
|
|
// ----------
|
|
|
|
// Function: pushArrange
|
|
|
|
// Push an arrange() call and its arguments onto an array
|
|
|
|
// to be resolved in resumeArrange()
|
|
|
|
pushArrange: function GroupItems_pushArrange(groupItem, options) {
|
|
|
|
Utils.assert(this._arrangePaused,
|
|
|
|
"Ensure pushArrange() called while arrange()s aren't paused");
|
|
|
|
let i;
|
|
|
|
for (i = 0; i < this._arrangesPending.length; i++)
|
|
|
|
if (this._arrangesPending[i].groupItem === groupItem)
|
|
|
|
break;
|
|
|
|
let arrangeInfo = {
|
|
|
|
groupItem: groupItem,
|
|
|
|
options: options
|
|
|
|
};
|
|
|
|
if (i < this._arrangesPending.length)
|
|
|
|
this._arrangesPending[i] = arrangeInfo;
|
|
|
|
else
|
|
|
|
this._arrangesPending.push(arrangeInfo);
|
|
|
|
},
|
|
|
|
|
|
|
|
// ----------
|
|
|
|
// Function: resumeArrange
|
|
|
|
// Resolve bypassed and collected arrange() calls
|
|
|
|
resumeArrange: function GroupItems_resumeArrange() {
|
2011-01-18 04:32:03 -08:00
|
|
|
this._arrangePaused = false;
|
2010-10-11 11:57:00 -07:00
|
|
|
for (let i = 0; i < this._arrangesPending.length; i++) {
|
|
|
|
let g = this._arrangesPending[i];
|
|
|
|
g.groupItem.arrange(g.options);
|
|
|
|
}
|
|
|
|
this._arrangesPending = [];
|
|
|
|
},
|
|
|
|
|
2010-09-11 19:51:19 -07:00
|
|
|
// ----------
|
2010-10-06 11:33:18 -07:00
|
|
|
// Function: _handleAttrModified
|
2010-09-11 19:51:19 -07:00
|
|
|
// watch for icon changes on app tabs
|
|
|
|
_handleAttrModified: function GroupItems__handleAttrModified(xulTab) {
|
2010-11-16 14:35:00 -08:00
|
|
|
if (!UI.isTabViewVisible()) {
|
|
|
|
if (this._delayedModUpdates.indexOf(xulTab) == -1) {
|
|
|
|
this._delayedModUpdates.push(xulTab);
|
|
|
|
}
|
|
|
|
} else
|
|
|
|
this._updateAppTabIcons(xulTab);
|
|
|
|
},
|
|
|
|
|
|
|
|
// ----------
|
|
|
|
// Function: flushTabUpdates
|
|
|
|
// Update apptab icons based on xulTabs which have been updated
|
|
|
|
// while the TabView hasn't been visible
|
|
|
|
flushAppTabUpdates: function GroupItems_flushAppTabUpdates() {
|
|
|
|
let self = this;
|
|
|
|
this._delayedModUpdates.forEach(function(xulTab) {
|
|
|
|
self._updateAppTabIcons(xulTab);
|
|
|
|
});
|
|
|
|
this._delayedModUpdates = [];
|
|
|
|
},
|
|
|
|
|
|
|
|
// ----------
|
|
|
|
// Function: _updateAppTabIcons
|
|
|
|
// Update images of any apptab icons that point to passed in xultab
|
|
|
|
_updateAppTabIcons: function GroupItems__updateAppTabIcons(xulTab) {
|
2010-09-11 19:51:19 -07:00
|
|
|
if (xulTab.ownerDocument.defaultView != gWindow || !xulTab.pinned)
|
|
|
|
return;
|
|
|
|
|
|
|
|
let iconUrl = xulTab.image || Utils.defaultFaviconURL;
|
|
|
|
this.groupItems.forEach(function(groupItem) {
|
|
|
|
iQ(".appTabIcon", groupItem.$appTabTray).each(function(icon) {
|
|
|
|
let $icon = iQ(icon);
|
|
|
|
if ($icon.data("xulTab") != xulTab)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (iconUrl != $icon.attr("src"))
|
|
|
|
$icon.attr("src", iconUrl);
|
|
|
|
});
|
|
|
|
});
|
2010-11-16 14:35:00 -08:00
|
|
|
},
|
2010-09-11 19:51:19 -07:00
|
|
|
|
2010-09-12 20:45:02 -07:00
|
|
|
// ----------
|
2010-10-06 11:33:18 -07:00
|
|
|
// Function: addAppTab
|
|
|
|
// Adds the given xul:tab to the app tab tray in all groups
|
|
|
|
addAppTab: function GroupItems_addAppTab(xulTab) {
|
2010-09-12 20:45:02 -07:00
|
|
|
this.groupItems.forEach(function(groupItem) {
|
|
|
|
groupItem.addAppTab(xulTab);
|
|
|
|
});
|
2010-12-16 21:43:31 -08:00
|
|
|
this.updateGroupCloseButtons();
|
2010-09-12 20:45:02 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
// ----------
|
2010-10-06 11:33:18 -07:00
|
|
|
// Function: removeAppTab
|
|
|
|
// Removes the given xul:tab from the app tab tray in all groups
|
|
|
|
removeAppTab: function GroupItems_removeAppTab(xulTab) {
|
2010-09-12 20:45:02 -07:00
|
|
|
this.groupItems.forEach(function(groupItem) {
|
|
|
|
groupItem.removeAppTab(xulTab);
|
|
|
|
});
|
2010-12-16 21:43:31 -08:00
|
|
|
this.updateGroupCloseButtons();
|
2010-09-12 20:45:02 -07:00
|
|
|
},
|
|
|
|
|
2010-05-26 11:29:31 -07:00
|
|
|
// ----------
|
2010-06-17 15:57:45 -07:00
|
|
|
// Function: getNextID
|
2010-08-06 15:46:55 -07:00
|
|
|
// Returns the next unused groupItem ID.
|
2010-09-08 10:02:08 -07:00
|
|
|
getNextID: function GroupItems_getNextID() {
|
2010-05-26 11:29:31 -07:00
|
|
|
var result = this.nextID;
|
|
|
|
this.nextID++;
|
2010-10-06 11:56:13 -07:00
|
|
|
this._save();
|
2010-05-26 11:29:31 -07:00
|
|
|
return result;
|
|
|
|
},
|
|
|
|
|
|
|
|
// ----------
|
2010-06-17 15:57:45 -07:00
|
|
|
// Function: getStorageData
|
2010-08-06 15:46:55 -07:00
|
|
|
// Returns an object for saving GroupItems state to persistent storage.
|
2010-09-08 10:02:08 -07:00
|
|
|
getStorageData: function GroupItems_getStorageData() {
|
2010-08-06 15:46:55 -07:00
|
|
|
var data = {nextID: this.nextID, groupItems: []};
|
|
|
|
this.groupItems.forEach(function(groupItem) {
|
|
|
|
data.groupItems.push(groupItem.getStorageData());
|
2010-05-26 11:29:31 -07:00
|
|
|
});
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-05-26 11:29:31 -07:00
|
|
|
return data;
|
|
|
|
},
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-05-26 11:29:31 -07:00
|
|
|
// ----------
|
2010-06-17 15:57:45 -07:00
|
|
|
// Function: saveAll
|
2010-08-06 15:46:55 -07:00
|
|
|
// Saves GroupItems state, as well as the state of all of the groupItems.
|
2010-09-08 10:02:08 -07:00
|
|
|
saveAll: function GroupItems_saveAll() {
|
2010-10-06 11:56:13 -07:00
|
|
|
this._save();
|
2010-08-06 15:46:55 -07:00
|
|
|
this.groupItems.forEach(function(groupItem) {
|
|
|
|
groupItem.save();
|
2010-05-26 11:29:31 -07:00
|
|
|
});
|
|
|
|
},
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-05-26 11:29:31 -07:00
|
|
|
// ----------
|
2010-10-06 11:56:13 -07:00
|
|
|
// Function: _save
|
2010-08-06 15:46:55 -07:00
|
|
|
// Saves GroupItems state.
|
2010-10-06 11:56:13 -07:00
|
|
|
_save: function GroupItems__save() {
|
2010-05-26 11:29:31 -07:00
|
|
|
if (!this._inited) // too soon to save now
|
|
|
|
return;
|
|
|
|
|
2010-10-06 11:56:13 -07:00
|
|
|
let activeGroupId = this._activeGroupItem ? this._activeGroupItem.id : null;
|
|
|
|
Storage.saveGroupItemsData(
|
|
|
|
gWindow, { nextID: this.nextID, activeGroupId: activeGroupId });
|
2010-05-26 11:29:31 -07:00
|
|
|
},
|
|
|
|
|
2010-07-18 08:58:10 -07:00
|
|
|
// ----------
|
2010-07-03 18:13:31 -07:00
|
|
|
// Function: getBoundingBox
|
|
|
|
// Given an array of DOM elements, returns a <Rect> with (roughly) the union of their locations.
|
2010-08-06 15:46:55 -07:00
|
|
|
getBoundingBox: function GroupItems_getBoundingBox(els) {
|
2010-07-03 18:13:31 -07:00
|
|
|
var bounds = [iQ(el).bounds() for each (el in els)];
|
2010-07-31 12:24:29 -07:00
|
|
|
var left = Math.min.apply({},[ b.left for each (b in bounds) ]);
|
|
|
|
var top = Math.min.apply({},[ b.top for each (b in bounds) ]);
|
|
|
|
var right = Math.max.apply({},[ b.right for each (b in bounds) ]);
|
|
|
|
var bottom = Math.max.apply({},[ b.bottom for each (b in bounds) ]);
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-07-03 18:13:31 -07:00
|
|
|
return new Rect(left, top, right-left, bottom-top);
|
|
|
|
},
|
|
|
|
|
2010-05-26 11:29:31 -07:00
|
|
|
// ----------
|
2010-06-17 15:57:45 -07:00
|
|
|
// Function: reconstitute
|
2010-08-06 15:46:55 -07:00
|
|
|
// Restores to stored state, creating groupItems as needed.
|
|
|
|
// If no data, sets up blank slate (including "new tabs" groupItem).
|
2010-09-08 10:02:08 -07:00
|
|
|
reconstitute: function GroupItems_reconstitute(groupItemsData, groupItemData) {
|
2010-05-26 11:29:31 -07:00
|
|
|
try {
|
2010-10-06 11:56:13 -07:00
|
|
|
let activeGroupId;
|
|
|
|
|
|
|
|
if (groupItemsData) {
|
|
|
|
if (groupItemsData.nextID)
|
2010-10-08 15:59:24 -07:00
|
|
|
this.nextID = Math.max(this.nextID, groupItemsData.nextID);
|
2010-10-06 11:56:13 -07:00
|
|
|
if (groupItemsData.activeGroupId)
|
|
|
|
activeGroupId = groupItemsData.activeGroupId;
|
|
|
|
}
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-08-06 15:46:55 -07:00
|
|
|
if (groupItemData) {
|
2011-01-05 12:54:35 -08:00
|
|
|
var toClose = this.groupItems.concat();
|
2010-08-06 15:46:55 -07:00
|
|
|
for (var id in groupItemData) {
|
2011-01-05 12:54:35 -08:00
|
|
|
let data = groupItemData[id];
|
|
|
|
if (this.groupItemStorageSanity(data)) {
|
|
|
|
let groupItem = this.groupItem(data.id);
|
|
|
|
if (groupItem) {
|
|
|
|
groupItem.userSize = data.userSize;
|
|
|
|
groupItem.setTitle(data.title);
|
|
|
|
groupItem.setBounds(data.bounds, true);
|
|
|
|
|
|
|
|
let index = toClose.indexOf(groupItem);
|
|
|
|
if (index != -1)
|
|
|
|
toClose.splice(index, 1);
|
|
|
|
} else {
|
|
|
|
var options = {
|
|
|
|
dontPush: true,
|
|
|
|
immediately: true
|
|
|
|
};
|
|
|
|
|
|
|
|
new GroupItem([], Utils.extend({}, data, options));
|
|
|
|
}
|
2010-05-26 11:29:31 -07:00
|
|
|
}
|
|
|
|
}
|
2011-01-05 12:54:35 -08:00
|
|
|
|
|
|
|
toClose.forEach(function(groupItem) {
|
|
|
|
groupItem.close();
|
|
|
|
});
|
2010-05-26 11:29:31 -07:00
|
|
|
}
|
2011-01-05 12:54:35 -08:00
|
|
|
|
2010-10-06 11:56:13 -07:00
|
|
|
// set active group item
|
|
|
|
if (activeGroupId) {
|
|
|
|
let activeGroupItem = this.groupItem(activeGroupId);
|
|
|
|
if (activeGroupItem)
|
|
|
|
this.setActiveGroupItem(activeGroupItem);
|
|
|
|
}
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-05-26 11:29:31 -07:00
|
|
|
this._inited = true;
|
2010-10-06 11:56:13 -07:00
|
|
|
this._save(); // for nextID
|
2010-07-30 02:54:30 -07:00
|
|
|
} catch(e) {
|
2010-05-26 11:29:31 -07:00
|
|
|
Utils.log("error in recons: "+e);
|
2010-03-17 01:07:00 -07:00
|
|
|
}
|
2010-05-26 11:29:31 -07:00
|
|
|
},
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-10-08 15:59:24 -07:00
|
|
|
// ----------
|
|
|
|
// Function: load
|
|
|
|
// Loads the storage data for groups.
|
|
|
|
// Returns true if there was global group data.
|
|
|
|
load: function GroupItems_load() {
|
|
|
|
let groupItemsData = Storage.readGroupItemsData(gWindow);
|
|
|
|
let groupItemData = Storage.readGroupItemData(gWindow);
|
|
|
|
this.reconstitute(groupItemsData, groupItemData);
|
|
|
|
|
|
|
|
return (groupItemsData && !Utils.isEmptyObject(groupItemsData));
|
|
|
|
},
|
|
|
|
|
2010-05-26 11:29:31 -07:00
|
|
|
// ----------
|
2010-08-06 15:46:55 -07:00
|
|
|
// Function: groupItemStorageSanity
|
|
|
|
// Given persistent storage data for a groupItem, returns true if it appears to not be damaged.
|
2010-09-08 10:02:08 -07:00
|
|
|
groupItemStorageSanity: function GroupItems_groupItemStorageSanity(groupItemData) {
|
2010-07-18 08:58:10 -07:00
|
|
|
// TODO: check everything
|
2010-08-11 20:08:33 -07:00
|
|
|
// Bug 586555
|
2010-05-26 11:29:31 -07:00
|
|
|
var sane = true;
|
2010-08-06 15:46:55 -07:00
|
|
|
if (!Utils.isRect(groupItemData.bounds)) {
|
|
|
|
Utils.log('GroupItems.groupItemStorageSanity: bad bounds', groupItemData.bounds);
|
2010-05-26 11:29:31 -07:00
|
|
|
sane = false;
|
|
|
|
}
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-05-26 11:29:31 -07:00
|
|
|
return sane;
|
|
|
|
},
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-09-07 16:23:29 -07:00
|
|
|
// ----------
|
2010-06-17 15:57:45 -07:00
|
|
|
// Function: register
|
2010-08-06 15:46:55 -07:00
|
|
|
// Adds the given <GroupItem> to the list of groupItems we're tracking.
|
2010-09-08 10:02:08 -07:00
|
|
|
register: function GroupItems_register(groupItem) {
|
2010-08-10 11:13:10 -07:00
|
|
|
Utils.assert(groupItem, 'groupItem');
|
|
|
|
Utils.assert(this.groupItems.indexOf(groupItem) == -1, 'only register once per groupItem');
|
2010-08-06 15:46:55 -07:00
|
|
|
this.groupItems.push(groupItem);
|
2010-09-10 14:51:14 -07:00
|
|
|
UI.updateTabButton();
|
2010-05-26 11:29:31 -07:00
|
|
|
},
|
2010-07-18 08:58:10 -07:00
|
|
|
|
|
|
|
// ----------
|
2010-06-17 15:57:45 -07:00
|
|
|
// Function: unregister
|
2010-08-06 15:46:55 -07:00
|
|
|
// Removes the given <GroupItem> from the list of groupItems we're tracking.
|
2010-09-08 10:02:08 -07:00
|
|
|
unregister: function GroupItems_unregister(groupItem) {
|
2010-08-06 15:46:55 -07:00
|
|
|
var index = this.groupItems.indexOf(groupItem);
|
2010-07-11 17:54:42 -07:00
|
|
|
if (index != -1)
|
2010-08-06 15:46:55 -07:00
|
|
|
this.groupItems.splice(index, 1);
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-08-06 15:46:55 -07:00
|
|
|
if (groupItem == this._activeGroupItem)
|
|
|
|
this._activeGroupItem = null;
|
2010-09-10 14:51:14 -07:00
|
|
|
|
2011-01-18 04:40:13 -08:00
|
|
|
this._arrangesPending = this._arrangesPending.filter(function (pending) {
|
|
|
|
return groupItem != pending.groupItem;
|
|
|
|
});
|
|
|
|
|
2010-09-10 14:51:14 -07:00
|
|
|
UI.updateTabButton();
|
2010-05-26 11:29:31 -07:00
|
|
|
},
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-05-26 11:29:31 -07:00
|
|
|
// ----------
|
2010-08-06 15:46:55 -07:00
|
|
|
// Function: groupItem
|
|
|
|
// Given some sort of identifier, returns the appropriate groupItem.
|
|
|
|
// Currently only supports groupItem ids.
|
2010-09-08 10:02:08 -07:00
|
|
|
groupItem: function GroupItems_groupItem(a) {
|
2010-05-26 11:29:31 -07:00
|
|
|
var result = null;
|
2010-08-06 15:46:55 -07:00
|
|
|
this.groupItems.forEach(function(candidate) {
|
2010-08-11 19:01:29 -07:00
|
|
|
if (candidate.id == a)
|
2010-05-26 11:29:31 -07:00
|
|
|
result = candidate;
|
|
|
|
});
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-05-26 11:29:31 -07:00
|
|
|
return result;
|
|
|
|
},
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-03-26 11:34:09 -07:00
|
|
|
// ----------
|
2010-06-17 15:57:45 -07:00
|
|
|
// Function: removeAll
|
2010-08-06 15:46:55 -07:00
|
|
|
// Removes all tabs from all groupItems (which automatically closes all unnamed groupItems).
|
2010-09-08 10:02:08 -07:00
|
|
|
removeAll: function GroupItems_removeAll() {
|
2010-08-06 15:46:55 -07:00
|
|
|
var toRemove = this.groupItems.concat();
|
|
|
|
toRemove.forEach(function(groupItem) {
|
|
|
|
groupItem.removeAll();
|
2010-03-30 11:05:53 -07:00
|
|
|
});
|
2010-05-26 11:29:31 -07:00
|
|
|
},
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-05-26 11:29:31 -07:00
|
|
|
// ----------
|
2010-06-17 15:57:45 -07:00
|
|
|
// Function: newTab
|
2010-08-06 15:46:55 -07:00
|
|
|
// Given a <TabItem>, files it in the appropriate groupItem.
|
2010-10-09 12:46:18 -07:00
|
|
|
newTab: function GroupItems_newTab(tabItem, options) {
|
2010-08-08 17:27:47 -07:00
|
|
|
let activeGroupItem = this.getActiveGroupItem();
|
2010-10-06 11:56:13 -07:00
|
|
|
|
|
|
|
// 1. Active group
|
|
|
|
// 2. Active orphan
|
|
|
|
// 3. First visible non-app tab (that's not the tab in question), whether it's an
|
|
|
|
// orphan or not (make a new group if it's an orphan, add it to the group if it's
|
|
|
|
// not)
|
|
|
|
// 4. First group
|
|
|
|
// 5. First orphan that's not the tab in question
|
|
|
|
// 6. At this point there should be no groups or tabs (except for app tabs and the
|
|
|
|
// tab in question): make a new group
|
|
|
|
|
2010-08-08 17:27:47 -07:00
|
|
|
if (activeGroupItem) {
|
2011-01-12 13:48:42 -08:00
|
|
|
activeGroupItem.add(tabItem, options);
|
2010-10-06 11:56:13 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let orphanTabItem = this.getActiveOrphanTab();
|
|
|
|
if (!orphanTabItem) {
|
2010-12-08 18:13:12 -08:00
|
|
|
let targetGroupItem;
|
2010-10-06 11:56:13 -07:00
|
|
|
// find first visible non-app tab in the tabbar.
|
|
|
|
gBrowser.visibleTabs.some(function(tab) {
|
|
|
|
if (!tab.pinned && tab != tabItem.tab) {
|
2011-01-11 00:20:08 -08:00
|
|
|
if (tab._tabViewTabItem) {
|
|
|
|
if (!tab._tabViewTabItem.parent) {
|
2010-12-08 18:13:12 -08:00
|
|
|
// the first visible tab is an orphan tab, set the orphan tab, and
|
|
|
|
// create a new group for orphan tab and new tabItem
|
2011-01-11 00:20:08 -08:00
|
|
|
orphanTabItem = tab._tabViewTabItem;
|
|
|
|
} else if (!tab._tabViewTabItem.parent.hidden) {
|
2010-12-08 18:13:12 -08:00
|
|
|
// the first visible tab belongs to a group, add the new tabItem to
|
|
|
|
// that group
|
2011-01-11 00:20:08 -08:00
|
|
|
targetGroupItem = tab._tabViewTabItem.parent;
|
2010-12-08 18:13:12 -08:00
|
|
|
}
|
|
|
|
}
|
2010-10-06 11:56:13 -07:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
});
|
|
|
|
|
2010-12-08 18:13:12 -08:00
|
|
|
let visibleGroupItems;
|
2010-10-06 11:56:13 -07:00
|
|
|
if (!orphanTabItem) {
|
2010-12-08 18:13:12 -08:00
|
|
|
if (targetGroupItem) {
|
|
|
|
// add the new tabItem to the first group item
|
|
|
|
targetGroupItem.add(tabItem);
|
|
|
|
this.setActiveGroupItem(targetGroupItem);
|
2010-10-06 11:56:13 -07:00
|
|
|
return;
|
2010-12-08 18:13:12 -08:00
|
|
|
} else {
|
|
|
|
// find the first visible group item
|
|
|
|
visibleGroupItems = this.groupItems.filter(function(groupItem) {
|
|
|
|
return (!groupItem.hidden);
|
|
|
|
});
|
|
|
|
if (visibleGroupItems.length > 0) {
|
|
|
|
visibleGroupItems[0].add(tabItem);
|
|
|
|
this.setActiveGroupItem(visibleGroupItems[0]);
|
|
|
|
return;
|
|
|
|
}
|
2010-10-06 11:56:13 -07:00
|
|
|
}
|
2010-12-08 18:13:12 -08:00
|
|
|
let orphanedTabs = this.getOrphanedTabs();
|
2010-10-06 11:56:13 -07:00
|
|
|
// set the orphan tab, and create a new group for orphan tab and
|
|
|
|
// new tabItem
|
|
|
|
if (orphanedTabs.length > 0)
|
|
|
|
orphanTabItem = orphanedTabs[0];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// create new group for orphan tab and new tabItem
|
|
|
|
let tabItems;
|
2010-12-08 18:13:12 -08:00
|
|
|
let newGroupItemBounds;
|
2010-10-06 11:56:13 -07:00
|
|
|
// the orphan tab would be the same as tabItem when all tabs are app tabs
|
|
|
|
// and a new tab is created.
|
|
|
|
if (orphanTabItem && orphanTabItem.tab != tabItem.tab) {
|
2010-09-26 23:25:00 -07:00
|
|
|
newGroupItemBounds = orphanTabItem.getBounds();
|
2010-10-06 11:56:13 -07:00
|
|
|
tabItems = [orphanTabItem, tabItem];
|
2010-08-04 22:43:05 -07:00
|
|
|
} else {
|
2010-10-06 11:56:13 -07:00
|
|
|
tabItem.setPosition(60, 60, true);
|
|
|
|
newGroupItemBounds = tabItem.getBounds();
|
|
|
|
tabItems = [tabItem];
|
2010-08-08 15:32:08 -07:00
|
|
|
}
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-10-06 11:56:13 -07:00
|
|
|
newGroupItemBounds.inset(-40,-40);
|
2010-12-08 18:13:12 -08:00
|
|
|
let newGroupItem = new GroupItem(tabItems, { bounds: newGroupItemBounds });
|
2010-10-06 11:56:13 -07:00
|
|
|
newGroupItem.snap();
|
|
|
|
this.setActiveGroupItem(newGroupItem);
|
2010-08-08 15:32:08 -07:00
|
|
|
},
|
2010-08-04 22:43:05 -07:00
|
|
|
|
2010-05-26 11:29:31 -07:00
|
|
|
// ----------
|
2010-08-06 15:46:55 -07:00
|
|
|
// Function: getActiveGroupItem
|
2010-08-08 17:27:47 -07:00
|
|
|
// Returns the active groupItem. Active means its tabs are
|
2010-07-29 12:37:25 -07:00
|
|
|
// shown in the tab bar when not in the TabView interface.
|
2010-09-08 10:02:08 -07:00
|
|
|
getActiveGroupItem: function GroupItems_getActiveGroupItem() {
|
2010-08-06 15:46:55 -07:00
|
|
|
return this._activeGroupItem;
|
2010-05-26 11:29:31 -07:00
|
|
|
},
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-05-26 11:29:31 -07:00
|
|
|
// ----------
|
2010-08-06 15:46:55 -07:00
|
|
|
// Function: setActiveGroupItem
|
2010-09-10 07:40:27 -07:00
|
|
|
// Sets the active groupItem, thereby showing only the relevant tabs and
|
2010-08-08 17:27:47 -07:00
|
|
|
// setting the groupItem which will receive new tabs.
|
2010-05-26 11:29:31 -07:00
|
|
|
//
|
2010-06-17 15:57:45 -07:00
|
|
|
// Paramaters:
|
2010-08-06 15:46:55 -07:00
|
|
|
// groupItem - the active <GroupItem> or <null> if no groupItem is active
|
2010-05-26 11:29:31 -07:00
|
|
|
// (which means we have an orphaned tab selected)
|
2010-09-08 10:02:08 -07:00
|
|
|
setActiveGroupItem: function GroupItems_setActiveGroupItem(groupItem) {
|
2010-08-08 17:27:47 -07:00
|
|
|
if (this._activeGroupItem)
|
|
|
|
iQ(this._activeGroupItem.container).removeClass('activeGroupItem');
|
|
|
|
|
|
|
|
if (groupItem !== null) {
|
|
|
|
if (groupItem)
|
|
|
|
iQ(groupItem.container).addClass('activeGroupItem');
|
|
|
|
// if a groupItem is active, we surely are not in an orphaned tab.
|
|
|
|
this.setActiveOrphanTab(null);
|
|
|
|
}
|
|
|
|
|
2010-08-06 15:46:55 -07:00
|
|
|
this._activeGroupItem = groupItem;
|
2010-10-06 11:56:13 -07:00
|
|
|
this._save();
|
2010-08-04 22:43:05 -07:00
|
|
|
},
|
2010-08-08 20:52:26 -07:00
|
|
|
|
2010-08-04 22:43:05 -07:00
|
|
|
// ----------
|
|
|
|
// Function: getActiveOrphanTab
|
2010-08-06 15:46:55 -07:00
|
|
|
// Returns the active orphan tab, in cases when there is no active groupItem.
|
2010-09-08 10:02:08 -07:00
|
|
|
getActiveOrphanTab: function GroupItems_getActiveOrphanTab() {
|
2010-08-04 22:43:05 -07:00
|
|
|
return this._activeOrphanTab;
|
|
|
|
},
|
|
|
|
|
|
|
|
// ----------
|
|
|
|
// Function: setActiveOrphanTab
|
2010-08-06 15:46:55 -07:00
|
|
|
// In cases where an orphan tab (not in a groupItem) is active by itself,
|
2010-08-04 22:43:05 -07:00
|
|
|
// this function is called and the "active orphan tab" is set.
|
|
|
|
//
|
|
|
|
// Paramaters:
|
2010-08-06 15:46:55 -07:00
|
|
|
// groupItem - the active <TabItem> or <null>
|
2010-09-08 10:02:08 -07:00
|
|
|
setActiveOrphanTab: function GroupItems_setActiveOrphanTab(tabItem) {
|
2010-08-04 22:43:05 -07:00
|
|
|
this._activeOrphanTab = tabItem;
|
2010-05-26 11:29:31 -07:00
|
|
|
},
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-05-26 11:29:31 -07:00
|
|
|
// ----------
|
2010-08-17 07:13:45 -07:00
|
|
|
// Function: _updateTabBar
|
2010-08-08 18:54:22 -07:00
|
|
|
// Hides and shows tabs in the tab bar based on the active groupItem or
|
|
|
|
// currently active orphan tabItem
|
2010-09-08 10:02:08 -07:00
|
|
|
_updateTabBar: function GroupItems__updateTabBar() {
|
2010-07-11 17:54:42 -07:00
|
|
|
if (!window.UI)
|
2010-05-26 11:29:31 -07:00
|
|
|
return; // called too soon
|
2010-11-22 16:28:38 -08:00
|
|
|
|
2010-08-08 18:49:43 -07:00
|
|
|
if (!this._activeGroupItem && !this._activeOrphanTab) {
|
2010-08-10 11:13:10 -07:00
|
|
|
Utils.assert(false, "There must be something to show in the tab bar!");
|
2010-08-08 18:49:43 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let tabItems = this._activeGroupItem == null ?
|
|
|
|
[this._activeOrphanTab] : this._activeGroupItem._children;
|
2010-07-25 22:01:11 -07:00
|
|
|
gBrowser.showOnlyTheseTabs(tabItems.map(function(item) item.tab));
|
2010-05-26 11:29:31 -07:00
|
|
|
},
|
2010-07-18 08:58:10 -07:00
|
|
|
|
2010-08-17 07:13:45 -07:00
|
|
|
// ----------
|
|
|
|
// Function: updateActiveGroupItemAndTabBar
|
2010-09-10 02:50:14 -07:00
|
|
|
// Sets active TabItem and GroupItem, and updates tab bar appropriately.
|
2010-09-08 10:02:08 -07:00
|
|
|
updateActiveGroupItemAndTabBar: function GroupItems_updateActiveGroupItemAndTabBar(tabItem) {
|
2010-09-10 02:50:14 -07:00
|
|
|
Utils.assertThrow(tabItem && tabItem.isATabItem, "tabItem must be a TabItem");
|
|
|
|
|
|
|
|
let groupItem = tabItem.parent;
|
|
|
|
this.setActiveGroupItem(groupItem);
|
|
|
|
|
|
|
|
if (groupItem)
|
2010-08-17 07:13:45 -07:00
|
|
|
groupItem.setActiveTab(tabItem);
|
2010-09-10 02:50:14 -07:00
|
|
|
else
|
2010-08-17 07:13:45 -07:00
|
|
|
this.setActiveOrphanTab(tabItem);
|
2010-09-10 02:50:14 -07:00
|
|
|
|
2010-08-17 07:13:45 -07:00
|
|
|
this._updateTabBar();
|
|
|
|
},
|
|
|
|
|
2010-05-26 11:29:31 -07:00
|
|
|
// ----------
|
|
|
|
// Function: getOrphanedTabs
|
2010-08-06 15:46:55 -07:00
|
|
|
// Returns an array of all tabs that aren't in a groupItem.
|
2010-09-08 10:02:08 -07:00
|
|
|
getOrphanedTabs: function GroupItems_getOrphanedTabs() {
|
2010-05-26 11:29:31 -07:00
|
|
|
var tabs = TabItems.getItems();
|
2010-07-30 02:54:30 -07:00
|
|
|
tabs = tabs.filter(function(tab) {
|
2010-05-26 11:29:31 -07:00
|
|
|
return tab.parent == null;
|
2010-05-18 17:08:45 -07:00
|
|
|
});
|
2010-05-26 11:29:31 -07:00
|
|
|
return tabs;
|
2010-07-21 20:29:58 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
// ----------
|
2010-08-06 15:46:55 -07:00
|
|
|
// Function: getNextGroupItemTab
|
2010-07-21 20:29:58 -07:00
|
|
|
// Paramaters:
|
2010-08-06 15:46:55 -07:00
|
|
|
// reverse - the boolean indicates the direction to look for the next groupItem.
|
2010-07-21 20:29:58 -07:00
|
|
|
// Returns the <tabItem>. If nothing is found, return null.
|
2010-09-08 10:02:08 -07:00
|
|
|
getNextGroupItemTab: function GroupItems_getNextGroupItemTab(reverse) {
|
2010-08-08 20:41:07 -07:00
|
|
|
var groupItems = Utils.copy(GroupItems.groupItems);
|
2010-08-06 15:46:55 -07:00
|
|
|
var activeGroupItem = GroupItems.getActiveGroupItem();
|
2010-08-08 20:41:07 -07:00
|
|
|
var activeOrphanTab = GroupItems.getActiveOrphanTab();
|
2010-07-21 20:29:58 -07:00
|
|
|
var tabItem = null;
|
|
|
|
|
2010-08-17 19:51:43 -07:00
|
|
|
if (reverse)
|
|
|
|
groupItems = groupItems.reverse();
|
|
|
|
|
2010-08-06 15:46:55 -07:00
|
|
|
if (!activeGroupItem) {
|
|
|
|
if (groupItems.length > 0) {
|
|
|
|
groupItems.some(function(groupItem) {
|
2010-09-10 07:40:27 -07:00
|
|
|
if (!groupItem.hidden) {
|
2010-09-30 03:08:28 -07:00
|
|
|
// restore the last active tab in the group
|
|
|
|
let activeTab = groupItem.getActiveTab();
|
|
|
|
if (activeTab) {
|
|
|
|
tabItem = activeTab;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
// if no tab is active, use the first one
|
2010-09-10 07:40:27 -07:00
|
|
|
var child = groupItem.getChild(0);
|
|
|
|
if (child) {
|
|
|
|
tabItem = child;
|
|
|
|
return true;
|
|
|
|
}
|
2010-07-21 20:29:58 -07:00
|
|
|
}
|
2010-08-11 19:01:29 -07:00
|
|
|
return false;
|
2010-07-21 20:29:58 -07:00
|
|
|
});
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
var currentIndex;
|
2010-08-06 15:46:55 -07:00
|
|
|
groupItems.some(function(groupItem, index) {
|
2010-09-10 07:40:27 -07:00
|
|
|
if (!groupItem.hidden && groupItem == activeGroupItem) {
|
2010-07-21 20:29:58 -07:00
|
|
|
currentIndex = index;
|
|
|
|
return true;
|
|
|
|
}
|
2010-08-11 19:01:29 -07:00
|
|
|
return false;
|
2010-07-21 20:29:58 -07:00
|
|
|
});
|
2010-08-06 15:46:55 -07:00
|
|
|
var firstGroupItems = groupItems.slice(currentIndex + 1);
|
|
|
|
firstGroupItems.some(function(groupItem) {
|
2010-09-10 07:40:27 -07:00
|
|
|
if (!groupItem.hidden) {
|
2010-09-30 03:08:28 -07:00
|
|
|
// restore the last active tab in the group
|
|
|
|
let activeTab = groupItem.getActiveTab();
|
|
|
|
if (activeTab) {
|
|
|
|
tabItem = activeTab;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
// if no tab is active, use the first one
|
2010-09-10 07:40:27 -07:00
|
|
|
var child = groupItem.getChild(0);
|
|
|
|
if (child) {
|
|
|
|
tabItem = child;
|
|
|
|
return true;
|
|
|
|
}
|
2010-07-21 20:29:58 -07:00
|
|
|
}
|
2010-08-11 19:01:29 -07:00
|
|
|
return false;
|
2010-07-21 20:29:58 -07:00
|
|
|
});
|
|
|
|
if (!tabItem) {
|
2010-08-06 15:46:55 -07:00
|
|
|
var orphanedTabs = GroupItems.getOrphanedTabs();
|
2010-07-21 20:29:58 -07:00
|
|
|
if (orphanedTabs.length > 0)
|
|
|
|
tabItem = orphanedTabs[0];
|
|
|
|
}
|
|
|
|
if (!tabItem) {
|
2010-08-06 15:46:55 -07:00
|
|
|
var secondGroupItems = groupItems.slice(0, currentIndex);
|
|
|
|
secondGroupItems.some(function(groupItem) {
|
2010-09-10 07:40:27 -07:00
|
|
|
if (!groupItem.hidden) {
|
2010-09-30 03:08:28 -07:00
|
|
|
// restore the last active tab in the group
|
|
|
|
let activeTab = groupItem.getActiveTab();
|
|
|
|
if (activeTab) {
|
|
|
|
tabItem = activeTab;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
// if no tab is active, use the first one
|
2010-09-10 07:40:27 -07:00
|
|
|
var child = groupItem.getChild(0);
|
|
|
|
if (child) {
|
|
|
|
tabItem = child;
|
|
|
|
return true;
|
|
|
|
}
|
2010-07-21 20:29:58 -07:00
|
|
|
}
|
2010-08-11 19:01:29 -07:00
|
|
|
return false;
|
2010-07-21 20:29:58 -07:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return tabItem;
|
2010-08-06 07:17:01 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
// ----------
|
2010-08-06 15:46:55 -07:00
|
|
|
// Function: moveTabToGroupItem
|
2010-09-11 19:51:19 -07:00
|
|
|
// Used for the right click menu in the tab strip; moves the given tab
|
2010-09-10 02:50:14 -07:00
|
|
|
// into the given group. Does nothing if the tab is an app tab.
|
2010-08-06 07:17:01 -07:00
|
|
|
// Paramaters:
|
|
|
|
// tab - the <xul:tab>.
|
2010-08-06 15:46:55 -07:00
|
|
|
// groupItemId - the <groupItem>'s id. If nothing, create a new <groupItem>.
|
2010-09-08 10:02:08 -07:00
|
|
|
moveTabToGroupItem : function GroupItems_moveTabToGroupItem (tab, groupItemId) {
|
2010-09-11 19:51:19 -07:00
|
|
|
if (tab.pinned)
|
2010-09-10 02:50:14 -07:00
|
|
|
return;
|
2010-09-11 19:51:19 -07:00
|
|
|
|
2011-01-11 00:20:08 -08:00
|
|
|
Utils.assertThrow(tab._tabViewTabItem, "tab must be linked to a TabItem");
|
2010-09-11 19:51:19 -07:00
|
|
|
|
2011-01-12 12:10:47 -08:00
|
|
|
// given tab is already contained in target group
|
|
|
|
if (tab._tabViewTabItem.parent && tab._tabViewTabItem.parent.id == groupItemId)
|
|
|
|
return;
|
|
|
|
|
2010-08-06 07:17:01 -07:00
|
|
|
let shouldUpdateTabBar = false;
|
2010-08-08 20:52:26 -07:00
|
|
|
let shouldShowTabView = false;
|
2010-08-06 15:46:55 -07:00
|
|
|
let groupItem;
|
2010-08-06 07:17:01 -07:00
|
|
|
|
|
|
|
// switch to the appropriate tab first.
|
|
|
|
if (gBrowser.selectedTab == tab) {
|
2011-01-17 01:54:32 -08:00
|
|
|
if (gBrowser.visibleTabs.length > 1) {
|
|
|
|
gBrowser._blurTab(tab);
|
2010-08-15 21:46:22 -07:00
|
|
|
shouldUpdateTabBar = true;
|
2010-08-08 20:52:26 -07:00
|
|
|
} else {
|
|
|
|
shouldShowTabView = true;
|
2010-08-06 07:17:01 -07:00
|
|
|
}
|
2011-01-17 01:54:32 -08:00
|
|
|
} else {
|
2010-08-06 07:17:01 -07:00
|
|
|
shouldUpdateTabBar = true
|
2011-01-17 01:54:32 -08:00
|
|
|
}
|
2010-08-06 07:17:01 -07:00
|
|
|
|
2010-08-06 15:46:55 -07:00
|
|
|
// remove tab item from a groupItem
|
2011-01-11 00:20:08 -08:00
|
|
|
if (tab._tabViewTabItem.parent)
|
|
|
|
tab._tabViewTabItem.parent.remove(tab._tabViewTabItem);
|
2010-08-06 07:17:01 -07:00
|
|
|
|
2010-08-06 15:46:55 -07:00
|
|
|
// add tab item to a groupItem
|
|
|
|
if (groupItemId) {
|
|
|
|
groupItem = GroupItems.groupItem(groupItemId);
|
2011-01-11 00:20:08 -08:00
|
|
|
groupItem.add(tab._tabViewTabItem);
|
2010-08-06 15:46:55 -07:00
|
|
|
UI.setReorderTabItemsOnShow(groupItem);
|
2010-08-06 07:17:01 -07:00
|
|
|
} else {
|
|
|
|
let pageBounds = Items.getPageBounds();
|
|
|
|
pageBounds.inset(20, 20);
|
|
|
|
|
|
|
|
let box = new Rect(pageBounds);
|
|
|
|
box.width = 250;
|
|
|
|
box.height = 200;
|
|
|
|
|
2011-01-11 00:20:08 -08:00
|
|
|
new GroupItem([ tab._tabViewTabItem ], { bounds: box });
|
2010-08-06 07:17:01 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if (shouldUpdateTabBar)
|
2010-08-17 07:13:45 -07:00
|
|
|
this._updateTabBar();
|
2010-08-08 20:52:26 -07:00
|
|
|
else if (shouldShowTabView) {
|
2011-01-11 00:20:08 -08:00
|
|
|
tab._tabViewTabItem.setZoomPrep(false);
|
2010-08-06 07:17:01 -07:00
|
|
|
UI.showTabView();
|
|
|
|
}
|
2010-08-08 15:47:06 -07:00
|
|
|
},
|
2010-08-08 20:52:26 -07:00
|
|
|
|
2010-08-08 15:47:06 -07:00
|
|
|
// ----------
|
2010-09-10 07:40:27 -07:00
|
|
|
// Function: removeHiddenGroups
|
|
|
|
// Removes all hidden groups' data and its browser tabs.
|
|
|
|
removeHiddenGroups: function GroupItems_removeHiddenGroups() {
|
2010-11-14 19:12:10 -08:00
|
|
|
if (this._removingHiddenGroups)
|
|
|
|
return;
|
|
|
|
this._removingHiddenGroups = true;
|
2010-09-10 07:40:27 -07:00
|
|
|
|
2010-11-14 19:12:10 -08:00
|
|
|
let groupItems = this.groupItems.concat();
|
|
|
|
groupItems.forEach(function(groupItem) {
|
|
|
|
if (groupItem.hidden)
|
|
|
|
groupItem.closeHidden();
|
|
|
|
});
|
|
|
|
|
|
|
|
this._removingHiddenGroups = false;
|
2010-12-16 21:43:31 -08:00
|
|
|
},
|
|
|
|
|
|
|
|
// ----------
|
|
|
|
// Function: getUnclosableGroupItemId
|
|
|
|
// If there's only one (non-hidden) group, and there are app tabs present,
|
|
|
|
// returns that group.
|
|
|
|
// Return the <GroupItem>'s Id
|
|
|
|
getUnclosableGroupItemId: function GroupItems_getUnclosableGroupItemId() {
|
|
|
|
let unclosableGroupItemId = null;
|
|
|
|
|
|
|
|
if (gBrowser._numPinnedTabs > 0) {
|
|
|
|
let hiddenGroupItems =
|
|
|
|
this.groupItems.concat().filter(function(groupItem) {
|
|
|
|
return !groupItem.hidden;
|
|
|
|
});
|
|
|
|
if (hiddenGroupItems.length == 1)
|
|
|
|
unclosableGroupItemId = hiddenGroupItems[0].id;
|
|
|
|
}
|
|
|
|
|
|
|
|
return unclosableGroupItemId;
|
|
|
|
},
|
|
|
|
|
|
|
|
// ----------
|
|
|
|
// Function: updateGroupCloseButtons
|
|
|
|
// Updates group close buttons.
|
|
|
|
updateGroupCloseButtons: function GroupItems_updateGroupCloseButtons() {
|
|
|
|
let unclosableGroupItemId = this.getUnclosableGroupItemId();
|
|
|
|
|
|
|
|
if (unclosableGroupItemId) {
|
|
|
|
let groupItem = this.groupItem(unclosableGroupItemId);
|
|
|
|
|
|
|
|
if (groupItem) {
|
|
|
|
groupItem.$closeButton.hide();
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
this.groupItems.forEach(function(groupItem) {
|
|
|
|
groupItem.$closeButton.show();
|
|
|
|
});
|
|
|
|
}
|
2010-09-26 23:25:00 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
// ----------
|
|
|
|
// Function: calcValidSize
|
|
|
|
// Basic measure rules. Assures that item is a minimum size.
|
|
|
|
calcValidSize: function GroupItems_calcValidSize(size, options) {
|
|
|
|
Utils.assert(Utils.isPoint(size), 'input is a Point');
|
|
|
|
Utils.assert((size.x>0 || size.y>0) && (size.x!=0 && size.y!=0),
|
|
|
|
"dimensions are valid:"+size.x+","+size.y);
|
|
|
|
return new Point(
|
|
|
|
Math.max(size.x, GroupItems.minGroupWidth),
|
|
|
|
Math.max(size.y, GroupItems.minGroupHeight));
|
2010-03-26 11:34:09 -07:00
|
|
|
}
|
|
|
|
};
|