Files
tubestation/browser/base/content/tabview/ui.js
Edward Lee cdba58cb1f Bug 581813 - Firefox with Tab Candy caused the Title and Tab Bars or the Menu and Tab Bars to appear black in colour
Push the TabView iframe down behind the tabbrowser when hiding TabView and restore the max height on show. This has slight issues in that it squishes the iframe contents as well as flashes black as we animate. Followup in bug 586679.
2010-08-12 09:38:21 -07:00

1073 lines
31 KiB
JavaScript

/* ***** 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.
*
* The Original Code is ui.js.
*
* The Initial Developer of the Original Code is
* the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Ian Gilman <ian@iangilman.com>
* Aza Raskin <aza@mozilla.com>
* Michael Yoshitaka Erlewine <mitcho@mitcho.com>
* Ehsan Akhgari <ehsan@mozilla.com>
* Raymond Lee <raymond@appcoast.com>
*
* 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 ***** */
// **********
// Title: ui.js
(function() {
window.Keys = { meta: false };
// ##########
// Class: UIManager
// Singleton top-level UI manager.
var UIManager = {
// Variable: _frameInitalized
// True if the Tab View UI frame has been initialized.
_frameInitalized: false,
// Variable: _pageBounds
// Stores the page bounds.
_pageBounds : null,
// Variable: _closedLastVisibleTab
// If true, the last visible tab has just been closed in the tab strip.
_closedLastVisibleTab : false,
// Variable: _closedSelectedTabInTabView
// If true, a select tab has just been closed in TabView.
_closedSelectedTabInTabView : false,
// Variable: _stopZoomPreparation
// If true, prevent the next zoom preparation.
_stopZoomPreparation : false,
// Variable: _reorderTabItemsOnShow
// Keeps track of the <GroupItem>s which their tab items' tabs have been moved
// and re-orders the tab items when switching to TabView.
_reorderTabItemsOnShow : [],
// Variable: _reorderTabsOnHide
// Keeps track of the <GroupItem>s which their tab items have been moved in
// TabView UI and re-orders the tabs when switcing back to main browser.
_reorderTabsOnHide : [],
// Variable: _currentTab
// Keeps track of which xul:tab we are currently on.
// Used to facilitate zooming down from a previous tab.
_currentTab : null,
// ----------
// Function: init
// Must be called after the object is created.
init: function() {
try {
let self = this;
// ___ storage
Storage.init();
let data = Storage.readUIData(gWindow);
this._storageSanity(data);
this._pageBounds = data.pageBounds;
// ___ hook into the browser
gWindow.addEventListener("tabviewshow", function() {
self.showTabView(true);
}, false);
// ___ currentTab
this._currentTab = gBrowser.selectedTab;
// ___ Dev Menu
this._addDevMenu();
// When you click on the background/empty part of TabView,
// we create a new groupItem.
iQ(gTabViewFrame.contentDocument).mousedown(function(e) {
if (iQ(":focus").length > 0) {
iQ(":focus").each(function(element) {
if (element.nodeName == "INPUT")
element.blur();
});
}
if (e.originalTarget.id == "content")
self._createGroupItemOnDrag(e)
});
iQ(window).bind("beforeunload", function() {
Array.forEach(gBrowser.tabs, function(tab) {
tab.hidden = false;
});
});
iQ(window).bind("unload", function() {
self.uninit();
});
gWindow.addEventListener("tabviewhide", function() {
var activeTab = self.getActiveTab();
if (activeTab)
activeTab.zoomIn();
}, false);
// ___ setup key handlers
this._setTabViewFrameKeyHandlers();
// ___ add tab action handlers
this._addTabActionHandlers();
// ___ Storage
GroupItems.init();
var groupItemsData = Storage.readGroupItemsData(gWindow);
var firstTime = !groupItemsData || Utils.isEmptyObject(groupItemsData);
var groupItemData = Storage.readGroupItemData(gWindow);
GroupItems.reconstitute(groupItemsData, groupItemData);
GroupItems.killNewTabGroup(); // temporary?
if (firstTime) {
var padding = 10;
var infoWidth = 350;
var infoHeight = 350;
var pageBounds = Items.getPageBounds();
pageBounds.inset(padding, padding);
// ___ make a fresh groupItem
var box = new Rect(pageBounds);
box.width =
Math.min(box.width * 0.667, pageBounds.width - (infoWidth + padding));
box.height = box.height * 0.667;
var options = {
bounds: box
};
var groupItem = new GroupItem([], options);
var items = TabItems.getItems();
items.forEach(function(item) {
if (item.parent)
item.parent.remove(item);
groupItem.add(item);
});
// ___ make info item
var html =
"<div class='intro'>"
+ "<h1>Welcome to Firefox Tab Sets</h1>" // TODO: This needs to be localized if it's kept in
+ "<div>(more goes here)</div><br>"
+ "<video src='http://people.mozilla.org/~araskin/movies/tabcandy_howto.webm' "
+ "width='100%' preload controls>"
+ "</div>";
box.left = box.right + padding;
box.width = infoWidth;
box.height = infoHeight;
var infoItem = new InfoItem(box);
infoItem.html(html);
}
// ___ tabs
TabItems.init();
TabItems.pausePainting();
// ___ resizing
if (this._pageBounds)
this._resize(true);
else
this._pageBounds = Items.getPageBounds();
iQ(window).resize(function() {
self._resize();
});
// ___ setup observer to save canvas images
var observer = {
observe : function(subject, topic, data) {
if (topic == "quit-application-requested") {
if (self._isTabViewVisible())
TabItems.saveAll(true);
self._save();
}
}
};
Services.obs.addObserver(observer, "quit-application-requested", false);
// ___ Done
this._frameInitalized = true;
this._save();
} catch(e) {
Utils.log(e);
}
},
uninit: function() {
TabItems.uninit();
GroupItems.uninit();
Storage.uninit();
this._currentTab = null;
this._pageBounds = null;
this._reorderTabItemsOnShow = null;
this._reorderTabsOnHide = null;
},
// ----------
// Function: getActiveTab
// Returns the currently active tab as a <TabItem>
//
getActiveTab: function() {
return this._activeTab;
},
// ----------
// Function: setActiveTab
// Sets the currently active tab. The idea of a focused tab is useful
// for keyboard navigation and returning to the last zoomed-in tab.
// Hitting return/esc brings you to the focused tab, and using the
// arrow keys lets you navigate between open tabs.
//
// Parameters:
// - Takes a <TabItem>
setActiveTab: function(tab) {
if (tab == this._activeTab)
return;
if (this._activeTab) {
this._activeTab.makeDeactive();
this._activeTab.removeSubscriber(this, "close");
}
this._activeTab = tab;
if (this._activeTab) {
var self = this;
this._activeTab.addSubscriber(this, "close", function() {
self._activeTab = null;
});
this._activeTab.makeActive();
}
},
// ----------
// Function: _isTabViewVisible
// Returns true if the TabView UI is currently shown.
_isTabViewVisible: function() {
return gTabViewDeck.selectedIndex == 1;
},
// ----------
// Function: showTabView
// Shows TabView and hides the main browser UI.
// Parameters:
// zoomOut - true for zoom out animation, false for nothing.
showTabView: function(zoomOut) {
if (this._isTabViewVisible())
return;
var self = this;
var currentTab = this._currentTab;
var item = null;
this._reorderTabItemsOnShow.forEach(function(groupItem) {
groupItem.reorderTabItemsBasedOnTabOrder();
});
this._reorderTabItemsOnShow = [];
#ifdef XP_WIN
// Restore the full height when showing TabView
gTabViewFrame.style.marginTop = 0;
#endif
gTabViewDeck.selectedIndex = 1;
gTabViewFrame.contentWindow.focus();
gBrowser.updateTitlebar();
#ifdef XP_MACOSX
this._setActiveTitleColor(true);
#endif
let event = document.createEvent("Events");
event.initEvent("tabviewshown", true, false);
if (zoomOut && currentTab && currentTab.tabItem) {
item = currentTab.tabItem;
// If there was a previous currentTab we want to animate
// its thumbnail (canvas) for the zoom out.
// Note that we start the animation on the chrome thread.
// Zoom out!
item.zoomOut(function() {
if (!currentTab.tabItem) // if the tab's been destroyed
item = null;
self.setActiveTab(item);
if (item.parent) {
var activeGroupItem = GroupItems.getActiveGroupItem();
if (activeGroupItem)
activeGroupItem.setTopChild(item);
}
self._resize(true);
dispatchEvent(event);
});
} else
dispatchEvent(event);
TabItems.resumePainting();
},
// ----------
// Function: hideTabView
// Hides TabView and shows the main browser UI.
hideTabView: function() {
if (!this._isTabViewVisible())
return;
TabItems.pausePainting();
this._reorderTabsOnHide.forEach(function(groupItem) {
groupItem.reorderTabsBasedOnTabItemOrder();
});
this._reorderTabsOnHide = [];
#ifdef XP_WIN
// Push the top of TabView frame to behind the tabbrowser, so glass can show
// XXX bug 586679: avoid shrinking the iframe and squishing iframe contents
// as well as avoiding the flash of black as we animate out
gTabViewFrame.style.marginTop = gBrowser.boxObject.y + "px";
#endif
gTabViewDeck.selectedIndex = 0;
gBrowser.contentWindow.focus();
// set the close button on tab
gBrowser.tabContainer.adjustTabstrip();
gBrowser.updateTitlebar();
#ifdef XP_MACOSX
this._setActiveTitleColor(false);
#endif
let event = document.createEvent("Events");
event.initEvent("tabviewhidden", true, false);
dispatchEvent(event);
},
#ifdef XP_MACOSX
// ----------
// Function: _setActiveTitleColor
// Used on the Mac to make the title bar match the gradient in the rest of the
// TabView UI.
//
// Parameters:
// set - true for the special TabView color, false for the normal color.
_setActiveTitleColor: function(set) {
// Mac Only
var mainWindow = gWindow.document.getElementById("main-window");
if (set)
mainWindow.setAttribute("activetitlebarcolor", "#C4C4C4");
else
mainWindow.removeAttribute("activetitlebarcolor");
},
#endif
// ----------
// Function: _addTabActionHandlers
// Adds handlers to handle tab actions.
_addTabActionHandlers: function() {
var self = this;
AllTabs.register("close", function(tab) {
if (tab.ownerDocument.defaultView != gWindow)
return;
if (self._isTabViewVisible()) {
// just closed the selected tab in the TabView interface.
if (self._currentTab == tab)
self._closedSelectedTabInTabView = true;
} else {
// if not closing the last tab
if (gBrowser.tabs.length > 1) {
var groupItem = GroupItems.getActiveGroupItem();
// 1) Only go back to the TabView tab when there you close the last
// tab of a groupItem.
// 2) Take care of the case where you've closed the last tab in
// an un-named groupItem, which means that the groupItem is gone (null) and
// there are no visible tabs.
// Can't use timeout here because user would see a flicker of
// switching to another tab before the TabView interface shows up.
if ((groupItem && groupItem._children.length == 1) ||
(groupItem == null && gBrowser.visibleTabs.length == 1)) {
// for the tab focus event to pick up.
self._closedLastVisibleTab = true;
// remove the zoom prep.
if (tab && tab.tabItem)
tab.tabItem.setZoomPrep(false);
self.showTabView();
}
// ToDo: When running unit tests, everything happens so quick so
// new tabs might be added after a tab is closing. Therefore, this
// hack is used. We should look for a better solution.
setTimeout(function() { // Marshal event from chrome thread to DOM thread
if ((groupItem && groupItem._children.length > 0) ||
(groupItem == null && gBrowser.visibleTabs.length > 0))
self.hideTabView();
}, 1);
}
}
});
AllTabs.register("move", function(tab) {
if (tab.ownerDocument.defaultView != gWindow)
return;
setTimeout(function() { // Marshal event from chrome thread to DOM thread
var activeGroupItem = GroupItems.getActiveGroupItem();
if (activeGroupItem)
self.setReorderTabItemsOnShow(activeGroupItem);
}, 1);
});
AllTabs.register("select", function(tab) {
if (tab.ownerDocument.defaultView != gWindow)
return;
self.tabOnFocus(tab);
});
},
// ----------
// Function: tabOnFocus
// Called when the user switches from one tab to another outside of the TabView UI.
tabOnFocus: function(tab) {
var self = this;
var focusTab = tab;
var currentTab = this._currentTab;
this._currentTab = focusTab;
// if the last visible tab has just been closed, don't show the chrome UI.
if (this._isTabViewVisible() &&
(this._closedLastVisibleTab || this._closedSelectedTabInTabView)) {
this._closedLastVisibleTab = false;
this._closedSelectedTabInTabView = false;
return;
}
// if TabView is visible but we didn't just close the last tab or
// selected tab, show chrome.
if (this._isTabViewVisible())
this.hideTabView();
// reset these vars, just in case.
this._closedLastVisibleTab = false;
this._closedSelectedTabInTabView = false;
setTimeout(function() { // Marshal event from chrome thread to DOM thread
// this value is true when TabView is open at browser startup.
if (self._stopZoomPreparation) {
self._stopZoomPreparation = false;
if (focusTab && focusTab.tabItem)
self.setActiveTab(focusTab.tabItem);
return;
}
if (focusTab != self._currentTab) {
// things have changed while we were in timeout
return;
}
var visibleTabCount = gBrowser.visibleTabs.length;
var newItem = null;
if (focusTab && focusTab.tabItem) {
newItem = focusTab.tabItem;
if (newItem.parent)
GroupItems.setActiveGroupItem(newItem.parent);
else {
GroupItems.setActiveGroupItem(null);
GroupItems.setActiveOrphanTab(newItem);
}
GroupItems.updateTabBar();
}
// ___ prepare for when we return to TabView
var oldItem = null;
if (currentTab && currentTab.tabItem)
oldItem = currentTab.tabItem;
if (newItem != oldItem) {
if (oldItem)
oldItem.setZoomPrep(false);
// if the last visible tab is removed, don't set zoom prep because
// we shoud be in the TabView interface.
if (visibleTabCount > 0 && newItem && !self._isTabViewVisible())
newItem.setZoomPrep(true);
} else {
// the tab is already focused so the new and old items are the
// same.
if (oldItem)
oldItem.setZoomPrep(!self._isTabViewVisible());
}
}, 1);
},
// ----------
// Function: setReorderTabsOnHide
// Sets the groupItem which the tab items' tabs should be re-ordered when
// switching to the main browser UI.
// Parameters:
// groupItem - the groupItem which would be used for re-ordering tabs.
setReorderTabsOnHide: function(groupItem) {
if (this._isTabViewVisible()) {
var index = this._reorderTabsOnHide.indexOf(groupItem);
if (index == -1)
this._reorderTabsOnHide.push(groupItem);
}
},
// ----------
// Function: setReorderTabItemsOnShow
// Sets the groupItem which the tab items should be re-ordered when
// switching to the tab view UI.
// Parameters:
// groupItem - the groupItem which would be used for re-ordering tab items.
setReorderTabItemsOnShow: function(groupItem) {
if (!this._isTabViewVisible()) {
var index = this._reorderTabItemsOnShow.indexOf(groupItem);
if (index == -1)
this._reorderTabItemsOnShow.push(groupItem);
}
},
// ----------
// Function: _setTabViewFrameKeyHandlers
// Sets up the key handlers for navigating between tabs within the TabView UI.
_setTabViewFrameKeyHandlers: function() {
var self = this;
iQ(window).keyup(function(event) {
if (!event.metaKey) window.Keys.meta = false;
});
iQ(window).keydown(function(event) {
if (event.metaKey) window.Keys.meta = true;
if (!self.getActiveTab() || iQ(":focus").length > 0) {
// prevent the default action when tab is pressed so it doesn't gives
// us problem with content focus.
if (event.which == 9) {
event.stopPropagation();
event.preventDefault();
}
return;
}
function getClosestTabBy(norm) {
var centers =
[[item.bounds.center(), item] for each(item in TabItems.getItems())];
var myCenter = self.getActiveTab().bounds.center();
var matches = centers
.filter(function(item){return norm(item[0], myCenter)})
.sort(function(a,b){
return myCenter.distance(a[0]) - myCenter.distance(b[0]);
});
if (matches.length > 0)
return matches[0][1];
return null;
}
var norm = null;
switch (event.which) {
case 39: // Right
norm = function(a, me){return a.x > me.x};
break;
case 37: // Left
norm = function(a, me){return a.x < me.x};
break;
case 40: // Down
norm = function(a, me){return a.y > me.y};
break;
case 38: // Up
norm = function(a, me){return a.y < me.y}
break;
}
if (norm != null) {
var nextTab = getClosestTabBy(norm);
if (nextTab) {
if (nextTab.inStack() && !nextTab.parent.expanded)
nextTab = nextTab.parent.getChild(0);
self.setActiveTab(nextTab);
}
event.stopPropagation();
event.preventDefault();
} else if (event.which == 32) {
// alt/control + space to zoom into the active tab.
#ifdef XP_MACOSX
if (event.altKey && !event.metaKey && !event.shiftKey &&
!event.ctrlKey) {
#else
if (event.ctrlKey && !event.metaKey && !event.shiftKey &&
!event.altKey) {
#endif
var activeTab = self.getActiveTab();
if (activeTab)
activeTab.zoomIn();
event.stopPropagation();
event.preventDefault();
}
} else if (event.which == 27 || event.which == 13) {
// esc or return to zoom into the active tab.
var activeTab = self.getActiveTab();
if (activeTab)
activeTab.zoomIn();
event.stopPropagation();
event.preventDefault();
} else if (event.which == 9) {
// tab/shift + tab to go to the next tab.
var activeTab = self.getActiveTab();
if (activeTab) {
var tabItems = (activeTab.parent ? activeTab.parent.getChildren() :
[activeTab]);
var length = tabItems.length;
var currentIndex = tabItems.indexOf(activeTab);
if (length > 1) {
if (event.shiftKey) {
if (currentIndex == 0)
newIndex = (length - 1);
else
newIndex = (currentIndex - 1);
} else {
if (currentIndex == (length - 1))
newIndex = 0;
else
newIndex = (currentIndex + 1);
}
self.setActiveTab(tabItems[newIndex]);
}
}
event.stopPropagation();
event.preventDefault();
}
});
},
// ----------
// Function: _createGroupItemOnDrag
// Called in response to a mousedown in empty space in the TabView UI;
// creates a new groupItem based on the user's drag.
_createGroupItemOnDrag: function(e) {
const minSize = 60;
const minMinSize = 15;
let lastActiveGroupItem = GroupItems.getActiveGroupItem();
GroupItems.setActiveGroupItem(null);
var startPos = { x: e.clientX, y: e.clientY };
var phantom = iQ("<div>")
.addClass("groupItem phantom activeGroupItem")
.css({
position: "absolute",
opacity: .7,
zIndex: -1,
cursor: "default"
})
.appendTo("body");
var item = { // a faux-Item
container: phantom,
isAFauxItem: true,
bounds: {},
getBounds: function FauxItem_getBounds() {
return this.container.bounds();
},
setBounds: function FauxItem_setBounds(bounds) {
this.container.css(bounds);
},
setZ: function FauxItem_setZ(z) {
this.container.css("z-index", z);
},
setOpacity: function FauxItem_setOpacity(opacity) {
this.container.css("opacity", opacity);
},
// we don't need to pushAway the phantom item at the end, because
// when we create a new GroupItem, it'll do the actual pushAway.
pushAway: function () {},
};
item.setBounds(new Rect(startPos.y, startPos.x, 0, 0));
var dragOutInfo = new Drag(item, e, true); // true = isResizing
function updateSize(e) {
var box = new Rect();
box.left = Math.min(startPos.x, e.clientX);
box.right = Math.max(startPos.x, e.clientX);
box.top = Math.min(startPos.y, e.clientY);
box.bottom = Math.max(startPos.y, e.clientY);
item.setBounds(box);
// compute the stationaryCorner
var stationaryCorner = "";
if (startPos.y == box.top)
stationaryCorner += "top";
else
stationaryCorner += "bottom";
if (startPos.x == box.left)
stationaryCorner += "left";
else
stationaryCorner += "right";
dragOutInfo.snap(stationaryCorner, false, false); // null for ui, which we don't use anyway.
box = item.getBounds();
if (box.width > minMinSize && box.height > minMinSize &&
(box.width > minSize || box.height > minSize))
item.setOpacity(1);
else
item.setOpacity(0.7);
e.preventDefault();
}
function collapse() {
phantom.animate({
width: 0,
height: 0,
top: phantom.position().x + phantom.height()/2,
left: phantom.position().y + phantom.width()/2
}, {
duration: 300,
complete: function() {
phantom.remove();
}
});
GroupItems.setActiveGroupItem(lastActiveGroupItem);
}
function finalize(e) {
iQ(window).unbind("mousemove", updateSize);
dragOutInfo.stop();
if (phantom.css("opacity") != 1)
collapse();
else {
var bounds = item.getBounds();
// Add all of the orphaned tabs that are contained inside the new groupItem
// to that groupItem.
var tabs = GroupItems.getOrphanedTabs();
var insideTabs = [];
for each(tab in tabs) {
if (bounds.contains(tab.bounds))
insideTabs.push(tab);
}
var groupItem = new GroupItem(insideTabs,{bounds:bounds});
GroupItems.setActiveGroupItem(groupItem);
phantom.remove();
dragOutInfo = null;
}
}
iQ(window).mousemove(updateSize)
iQ(gWindow).one("mouseup", finalize);
e.preventDefault();
return false;
},
// ----------
// Function: _resize
// Update the TabView UI contents in response to a window size change.
// Won't do anything if it doesn't deem the resize necessary.
// Parameters:
// force - true to update even when "unnecessary"; default false
_resize: function(force) {
if (typeof force == "undefined")
force = false;
// If TabView isn't focused and is not showing, don't perform a resize.
// This resize really slows things down.
if (!force && !this._isTabViewVisible())
return;
var oldPageBounds = new Rect(this._pageBounds);
var newPageBounds = Items.getPageBounds();
if (newPageBounds.equals(oldPageBounds))
return;
var items = Items.getTopLevelItems();
// compute itemBounds: the union of all the top-level items' bounds.
var itemBounds = new Rect(this._pageBounds);
// We start with pageBounds so that we respect the empty space the user
// has left on the page.
itemBounds.width = 1;
itemBounds.height = 1;
items.forEach(function(item) {
if (item.locked.bounds)
return;
var bounds = item.getBounds();
itemBounds = (itemBounds ? itemBounds.union(bounds) : new Rect(bounds));
});
if (newPageBounds.width < this._pageBounds.width &&
newPageBounds.width > itemBounds.width)
newPageBounds.width = this._pageBounds.width;
if (newPageBounds.height < this._pageBounds.height &&
newPageBounds.height > itemBounds.height)
newPageBounds.height = this._pageBounds.height;
var wScale;
var hScale;
if (Math.abs(newPageBounds.width - this._pageBounds.width)
> Math.abs(newPageBounds.height - this._pageBounds.height)) {
wScale = newPageBounds.width / this._pageBounds.width;
hScale = newPageBounds.height / itemBounds.height;
} else {
wScale = newPageBounds.width / itemBounds.width;
hScale = newPageBounds.height / this._pageBounds.height;
}
var scale = Math.min(hScale, wScale);
var self = this;
var pairs = [];
items.forEach(function(item) {
if (item.locked.bounds)
return;
var bounds = item.getBounds();
bounds.left += newPageBounds.left - self._pageBounds.left;
bounds.left *= scale;
bounds.width *= scale;
bounds.top += newPageBounds.top - self._pageBounds.top;
bounds.top *= scale;
bounds.height *= scale;
pairs.push({
item: item,
bounds: bounds
});
});
Items.unsquish(pairs);
pairs.forEach(function(pair) {
pair.item.setBounds(pair.bounds, true);
pair.item.snap();
});
this._pageBounds = Items.getPageBounds();
this._save();
},
// ----------
// Function: _addDevMenu
// Fills out the "dev menu" in the TabView UI.
_addDevMenu: function() {
try {
var self = this;
var $select = iQ("<select>")
.css({
position: "absolute",
bottom: 5,
right: 5,
zIndex: 99999,
opacity: .2
})
.appendTo("#content")
.change(function () {
var index = iQ(this).val();
try {
commands[index].code.apply(commands[index].element);
} catch(e) {
Utils.log("dev menu error", e);
}
iQ(this).val(0);
});
var commands = [{
name: "dev menu",
code: function() { }
}, {
name: "show trenches",
code: function() {
Trenches.toggleShown();
iQ(this).html((Trenches.showDebug ? "hide" : "show") + " trenches");
}
}, {
/*
name: "refresh",
code: function() {
location.href = "tabview.html";
}
}, {
name: "reset",
code: function() {
self._reset();
}
}, {
*/
name: "save",
code: function() {
self._saveAll();
}
}, {
name: "group sites",
code: function() {
self._arrangeBySite();
}
}];
var count = commands.length;
var a;
for (a = 0; a < count; a++) {
commands[a].element = (iQ("<option>")
.val(a)
.html(commands[a].name)
.appendTo($select))[0];
}
} catch(e) {
Utils.log(e);
}
},
// -----------
// Function: _reset
// Wipes all TabView storage and refreshes, giving you the "first-run" state.
_reset: function() {
Storage.wipe();
location.href = "";
},
// ----------
// Function: storageSanity
// Given storage data for this object, returns true if it looks valid.
_storageSanity: function(data) {
if (Utils.isEmptyObject(data))
return true;
if (!Utils.isRect(data.pageBounds)) {
Utils.log("UI.storageSanity: bad pageBounds", data.pageBounds);
data.pageBounds = null;
return false;
}
return true;
},
// ----------
// Function: _save
// Saves the data for this object to persistent storage
_save: function() {
if (!this._frameInitalized)
return;
var data = {
pageBounds: this._pageBounds
};
if (this._storageSanity(data))
Storage.saveUIData(gWindow, data);
},
// ----------
// Function: _saveAll
// Saves all data associated with TabView.
// TODO: Save info items
_saveAll: function() {
this._save();
GroupItems.saveAll();
TabItems.saveAll();
},
// ----------
// Function: _arrangeBySite
// Blows away all existing groupItems and organizes the tabs into new groupItems based
// on domain.
_arrangeBySite: function() {
function putInGroupItem(set, key) {
var groupItem = GroupItems.getGroupItemWithTitle(key);
if (groupItem) {
set.forEach(function(el) {
groupItem.add(el);
});
} else
new GroupItem(set, { dontPush: true, dontArrange: true, title: key });
}
GroupItems.removeAll();
var groupItems = [];
var leftovers = [];
var items = TabItems.getItems();
items.forEach(function(item) {
var url = item.tab.linkedBrowser.currentURI.spec;
var domain = url.split('/')[2];
if (!domain)
leftovers.push(item.container);
else {
var domainParts = domain.split(".");
var mainDomain = domainParts[domainParts.length - 2];
if (groupItems[mainDomain])
groupItems[mainDomain].push(item.container);
else
groupItems[mainDomain] = [item.container];
}
});
for (key in groupItems) {
var set = groupItems[key];
if (set.length > 1) {
putInGroupItem(set, key);
} else
leftovers.push(set[0]);
}
if (leftovers.length)
putInGroupItem(leftovers, "mixed");
GroupItems.arrange();
},
};
// ----------
window.UI = UIManager;
window.UI.init();
})();