Bug 610208 - When closing a tab, other tabs should not resize until cursor leaves the group [r=ian]

This commit is contained in:
Tim Taubert 2011-01-21 18:15:22 -05:00
parent 0ad6a5cdd2
commit eccb79f4df
6 changed files with 369 additions and 40 deletions

View File

@ -84,6 +84,7 @@ function GroupItem(listOfEls, options) {
this.fadeAwayUndoButtonDuration = 300;
this.keepProportional = false;
this._frozenItemSizeData = {};
// Double click tracker
this._lastClick = 0;
@ -194,6 +195,7 @@ function GroupItem(listOfEls, options) {
gTabView.firstUseExperienced = true;
})
.focus(function() {
self._unfreezeItemSize();
if (!self._titleFocused) {
(self.$title)[0].select();
self._titleFocused = true;
@ -644,6 +646,9 @@ GroupItem.prototype = Utils.extend(new Item(), new Subscribable(), {
this.removeAll({dontClose: true});
GroupItems.unregister(this);
// remove unfreeze event handlers, if item size is frozen
this._unfreezeItemSize({dontArrange: true});
let self = this;
let destroyGroup = function () {
iQ(self.container).remove();
@ -677,6 +682,7 @@ GroupItem.prototype = Utils.extend(new Item(), new Subscribable(), {
// Closes the groupItem and all of its children.
closeAll: function GroupItem_closeAll() {
if (this._children.length > 0) {
this._unfreezeItemSize();
this._children.forEach(function(child) {
iQ(child.container).hide();
});
@ -1009,8 +1015,13 @@ GroupItem.prototype = Utils.extend(new Item(), new Subscribable(), {
item.groupItemData = {};
item.addSubscriber(this, "close", function() {
let count = self._children.length;
let dontArrange = self.expanded || !self.shouldStack(count);
let dontClose = !item.closedManually && gBrowser._numPinnedTabs > 0;
self.remove(item, { dontClose: dontClose });
self.remove(item, {dontArrange: dontArrange, dontClose: dontClose});
if (dontArrange)
self._freezeItemSize(count);
if (self._children.length > 0 && self._activeTab) {
GroupItems.setActiveGroupItem(self);
@ -1037,6 +1048,7 @@ GroupItem.prototype = Utils.extend(new Item(), new Subscribable(), {
if (!options.dontArrange)
this.arrange({animate: !options.immediately});
this._unfreezeItemSize({dontArrange: true});
this._sendToSubscribers("childAdded",{ groupItemId: this.id, item: item });
UI.setReorderTabsOnHide(this);
@ -1107,8 +1119,10 @@ GroupItem.prototype = Utils.extend(new Item(), new Subscribable(), {
let closed = options.dontClose ? false : this.closeIfEmpty();
if (closed)
this._makeClosestTabActive();
else if (!options.dontArrange)
else if (!options.dontArrange) {
this.arrange({animate: !options.immediately});
this._unfreezeItemSize({dontArrange: true});
}
this._sendToSubscribers("childRemoved",{ groupItemId: this.id, item: item });
} catch(e) {
@ -1239,6 +1253,66 @@ GroupItem.prototype = Utils.extend(new Item(), new Subscribable(), {
return shouldStack;
},
// ----------
// Function: _freezeItemSize
// Freezes current item size (when removing a child).
//
// Parameters:
// itemCount - the number of children before the last one was removed
_freezeItemSize: function GroupItem__freezeItemSize(itemCount) {
let data = this._frozenItemSizeData;
if (!data.lastItemCount) {
let self = this;
data.lastItemCount = itemCount;
// unfreeze item size when tabview is hidden
data.onTabViewHidden = function () self._unfreezeItemSize();
window.addEventListener('tabviewhidden', data.onTabViewHidden, false);
// we don't need to observe mouse movement when expanded because the
// tray is closed when we leave it and collapse causes unfreezing
if (self.expanded)
return;
// unfreeze item size when cursor is moved out of group bounds
data.onMouseMove = function (e) {
let cursor = new Point(e.pageX, e.pageY);
if (!self.bounds.contains(cursor))
self._unfreezeItemSize();
}
iQ(window).mousemove(data.onMouseMove);
}
this.arrange({animate: true, count: data.lastItemCount});
},
// ----------
// Function: _unfreezeItemSize
// Unfreezes and updates item size.
//
// Parameters:
// options - various options (see below)
//
// Possible options:
// dontArrange - do not arrange items when unfreezing
_unfreezeItemSize: function GroupItem__unfreezeItemSize(options) {
let data = this._frozenItemSizeData;
if (!data.lastItemCount)
return;
if (!options || !options.dontArrange)
this.arrange({animate: true});
// unbind event listeners
window.removeEventListener('tabviewhidden', data.onTabViewHidden, false);
if (data.onMouseMove)
iQ(window).unbind('mousemove', data.onMouseMove);
// reset freeze status
this._frozenItemSizeData = {};
},
// ----------
// Function: arrange
// Lays out all of the children.
@ -1563,6 +1637,7 @@ GroupItem.prototype = Utils.extend(new Item(), new Subscribable(), {
});
this.arrange({z: z + 2});
this._unfreezeItemSize({dontArrange: true});
}
},
@ -1690,8 +1765,11 @@ GroupItem.prototype = Utils.extend(new Item(), new Subscribable(), {
// Function: setResizable
// Sets whether the groupItem is resizable and updates the UI accordingly.
setResizable: function GroupItem_setResizable(value, immediately) {
var self = this;
this.resizeOptions.minWidth = GroupItems.minGroupWidth;
this.resizeOptions.minHeight = GroupItems.minGroupHeight;
this.resizeOptions.start = function () self._unfreezeItemSize();
if (value) {
immediately ? this.$resizer.show() : this.$resizer.fadeIn();

View File

@ -23,6 +23,7 @@
* Aza Raskin <aza@mozilla.com>
* Michael Yoshitaka Erlewine <mitcho@mitcho.com>
* Sean Dunn <seanedunn@yahoo.com>
* Tim Taubert <tim.taubert@gmx.de>
*
* 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
@ -169,8 +170,10 @@ Item.prototype = {
this.dragOptions = {
cancelClass: 'close stackExpander',
start: function(e, ui) {
if (this.isAGroupItem)
if (this.isAGroupItem) {
GroupItems.setActiveGroupItem(this);
this._unfreezeItemSize();
}
// if we start dragging a tab within a group, start with dropSpace on.
else if (this.parent != null)
this.parent._dropSpaceActive = true;
@ -598,6 +601,35 @@ Item.prototype = {
var droppables;
var dropTarget;
// determine the best drop target based on the current mouse coordinates
let determineBestDropTarget = function (e, box) {
// drop events
var best = {
dropTarget: null,
score: 0
};
droppables.forEach(function(droppable) {
var intersection = box.intersection(droppable.bounds);
if (intersection && intersection.area() > best.score) {
var possibleDropTarget = droppable.item;
var accept = true;
if (possibleDropTarget != dropTarget) {
var dropOptions = possibleDropTarget.dropOptions;
if (dropOptions && typeof dropOptions.accept == "function")
accept = dropOptions.accept.apply(possibleDropTarget, [self]);
}
if (accept) {
best.dropTarget = possibleDropTarget;
best.score = intersection.area();
}
}
});
return best.dropTarget;
}
// ___ mousemove
var handleMouseMove = function(e) {
// global drag tracking
@ -624,31 +656,9 @@ Item.prototype = {
if (typeof self.dragOptions.drag == "function")
self.dragOptions.drag.apply(self, [e]);
// drop events
var best = {
dropTarget: null,
score: 0
};
let bestDropTarget = determineBestDropTarget(e, box);
droppables.forEach(function(droppable) {
var intersection = box.intersection(droppable.bounds);
if (intersection && intersection.area() > best.score) {
var possibleDropTarget = droppable.item;
var accept = true;
if (possibleDropTarget != dropTarget) {
var dropOptions = possibleDropTarget.dropOptions;
if (dropOptions && typeof dropOptions.accept == "function")
accept = dropOptions.accept.apply(possibleDropTarget, [self]);
}
if (accept) {
best.dropTarget = possibleDropTarget;
best.score = intersection.area();
}
}
});
if (best.dropTarget != dropTarget) {
if (bestDropTarget != dropTarget) {
var dropOptions;
if (dropTarget) {
dropOptions = dropTarget.dropOptions;
@ -656,7 +666,7 @@ Item.prototype = {
dropOptions.out.apply(dropTarget, [e]);
}
dropTarget = best.dropTarget;
dropTarget = bestDropTarget;
if (dropTarget) {
dropOptions = dropTarget.dropOptions;
@ -710,10 +720,10 @@ Item.prototype = {
}
startMouse = new Point(e.pageX, e.pageY);
startPos = self.getBounds().position();
let bounds = self.getBounds();
startPos = bounds.position();
startEvent = e;
startSent = false;
dropTarget = null;
droppables = [];
iQ('.iq-droppable').each(function(elem) {
@ -726,6 +736,8 @@ Item.prototype = {
}
});
dropTarget = determineBestDropTarget(e, bounds);
iQ(gWindow)
.mousemove(handleMouseMove)
.mouseup(handleMouseUp);

View File

@ -21,6 +21,7 @@
* Aza Raskin <aza@mozilla.com>
* Ian Gilman <ian@iangilman.com>
* Michael Yoshitaka Erlewine <mitcho@mitcho.com>
* Tim Taubert <tim.taubert@gmx.de>
*
* This file incorporates work from:
* jQuery JavaScript Library v1.4.2: http://code.jquery.com/jquery-1.4.2.js
@ -170,16 +171,22 @@ Rect.prototype = {
// ----------
// Function: contains
// Returns a boolean denoting if the given <Rect> is contained within
// Returns a boolean denoting if the <Rect> or <Point> is contained inside
// this rectangle.
//
// Paramaters
// - A <Rect>
contains: function Rect_contains(rect) {
return (rect.left >= this.left &&
rect.right <= this.right &&
rect.top >= this.top &&
rect.bottom <= this.bottom);
// Parameters
// - A <Rect> or a <Point>
contains: function Rect_contains(a) {
if (Utils.isPoint(a))
return (a.x > this.left &&
a.x < this.right &&
a.y > this.top &&
a.y < this.bottom);
return (a.left >= this.left &&
a.right <= this.right &&
a.top >= this.top &&
a.bottom <= this.bottom);
},
// ----------

View File

@ -85,6 +85,7 @@ _BROWSER_FILES = \
browser_tabview_bug608184.js \
browser_tabview_bug608158.js \
browser_tabview_bug608405.js \
browser_tabview_bug610208.js \
browser_tabview_bug610242.js \
browser_tabview_bug612470.js \
browser_tabview_bug613541.js \

View File

@ -15,8 +15,8 @@ function test() {
}
let testVeryQuickDragAndDrop = function () {
let sourceGroup = cw.GroupItems.groupItems[0];
let targetGroup = createGroupItem();
let sourceGroup = createGroupItem();
let targetGroup = cw.GroupItems.groupItems[0];
sourceGroup.pushAway(true);
targetGroup.pushAway(true);

View File

@ -0,0 +1,231 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
function test() {
let cw;
let win;
let groupItem;
let next = function () {
let test = tests.shift();
if (test) {
test();
return;
}
win.close();
finish();
}
let prepareTest = function (testName) {
let originalBounds = groupItem.getChild(0).getBounds();
let tabItem = groupItem.getChild(1);
let bounds = tabItem.getBounds();
tabItem.close();
ok(originalBounds.equals(groupItem.getChild(0).getBounds()), testName + ': tabs did not change their size');
ok(bounds.equals(groupItem.getChild(1).getBounds()), testName + ': third tab is now on second tab\'s previous position');
return originalBounds;
}
let cleanUpTest = function (testName, originalBounds, callback) {
// Use setTimeout here because the groupItem.arrange() call uses
// animation to re-arrange the tabItems.
win.setTimeout(function () {
ok(!originalBounds.equals(groupItem.getChild(0).getBounds()), testName + ': tabs changed their size');
// cleanup
cw.GroupItems.setActiveGroupItem(groupItem);
win.gBrowser.loadOneTab('about:blank', {inBackground: true});
afterAllTabsLoaded(callback, win);
}, 500);
}
let tests = [];
// focus group title's input field to cause item arrange
let testFocusTitle = function () {
let originalBounds = prepareTest('testFocusTitle');
let target = groupItem.$titleShield[0];
EventUtils.synthesizeMouseAtCenter(target, {}, cw);
cleanUpTest('testFocusTitle', originalBounds, next);
}
// hide tabview to cause item arrange
let testHideTabView = function () {
let originalBounds = prepareTest('testHideTabView');
hideTabView(function () {
cleanUpTest('testHideTabView', originalBounds, function () {
showTabView(next, win);
});
}, win);
}
// (undo) close a group to cause item arrange
let testCloseGroupUndo = function () {
let originalBounds = prepareTest('testCloseGroupUndo');
hideGroupItem(groupItem, function () {
unhideGroupItem(groupItem, function () {
cleanUpTest('testCloseGroupUndo', originalBounds, next);
});
});
}
// leave the group's container with the mouse to cause item arrange
let testMouseOut = function () {
let originalBounds = prepareTest('testMouseOut');
let doc = cw.document.documentElement;
let bounds = groupItem.getBounds();
EventUtils.synthesizeMouse(doc, bounds.right - 5, bounds.bottom - 5, {type: 'mousemove'}, cw);
ok(originalBounds.equals(groupItem.getChild(0).getBounds()), 'testMouseOut: tabs did not change their size');
EventUtils.synthesizeMouse(doc, bounds.right + 1, bounds.bottom + 1, {type: 'mousemove'}, cw);
cleanUpTest('testMouseOut', originalBounds, next);
}
// sort item (drag it around) in its group to cause item arrange
let testSortInGroup = function () {
let originalBounds = prepareTest('testSortInGroup');
let target = groupItem.getChild(0).container;
// simulate drag/drop sorting
EventUtils.synthesizeMouse(target, 20, 20, {type: 'mousedown'}, cw);
EventUtils.synthesizeMouse(target, 40, 20, {type: 'mousemove'}, cw);
EventUtils.synthesizeMouse(target, 20, 20, {type: 'mouseup'}, cw);
cleanUpTest('testSortInGroup', originalBounds, next);
}
// arrange items when the containing group is resized
let testResizeGroup = function () {
let originalBounds = prepareTest('testResizeGroup');
let oldBounds = groupItem.getBounds();
let resizer = groupItem.$resizer[0];
// simulate drag/drop resizing
EventUtils.synthesizeMouse(resizer, 5, 5, {type: 'mousedown'}, cw);
EventUtils.synthesizeMouse(resizer, 40, 20, {type: 'mousemove'}, cw);
EventUtils.synthesizeMouse(resizer, 20, 20, {type: 'mouseup'}, cw);
// reset group size
groupItem.setBounds(oldBounds);
groupItem.setUserSize();
cleanUpTest('testResizeGroup', originalBounds, next);
}
// make sure we don't freeze item size when removing an item from a stack
let testRemoveWhileStacked = function () {
let oldBounds = groupItem.getBounds();
groupItem.setSize(150, 200, true);
groupItem.setUserSize();
let originalBounds = groupItem.getChild(0).getBounds();
ok(!groupItem._isStacked, 'testRemoveWhileStacked: group is not stacked');
// add a new tab to let the group stack
win.gBrowser.loadOneTab('about:blank', {inBackground: true});
ok(groupItem._isStacked, 'testRemoveWhileStacked: group is now stacked');
afterAllTabsLoaded(function () {
groupItem.getChild(0).close();
let bounds = groupItem.getChild(0).getBounds();
ok(originalBounds.equals(bounds), 'testRemoveWhileStacked: tabs did not change their size');
// reset group size
groupItem.setBounds(oldBounds);
groupItem.setUserSize();
next();
}, win);
}
// 1) make sure item size is frozen when removing an item in expanded mode
// 2) make sure item size stays frozen while moving the mouse in the expanded
// layer
let testExpandedMode = function () {
let oldBounds = groupItem.getBounds();
groupItem.setSize(100, 100, true);
groupItem.setUserSize();
ok(groupItem._isStacked, 'testExpandedMode: group is stacked');
groupItem.addSubscriber(groupItem, 'expanded', function () {
groupItem.removeSubscriber(groupItem, 'expanded');
onExpanded();
});
groupItem.addSubscriber(groupItem, 'collapsed', function () {
groupItem.removeSubscriber(groupItem, 'collapsed');
onCollapsed();
});
let onExpanded = function () {
let originalBounds = groupItem.getChild(0).getBounds();
let tabItem = groupItem.getChild(1);
let bounds = tabItem.getBounds();
for (let i=0; i<3; i++)
groupItem.getChild(1).close();
ok(originalBounds.equals(groupItem.getChild(0).getBounds()), 'testExpandedMode: tabs did not change their size');
// move the mouse over the expanded layer
let trayBounds = groupItem.expanded.bounds;
let target = groupItem.expanded.$tray[0];
EventUtils.synthesizeMouse(target, trayBounds.right - 5, trayBounds.bottom -5, {type: 'mousemove'}, cw);
ok(originalBounds.equals(groupItem.getChild(0).getBounds()), 'testExpandedMode: tabs did not change their size');
groupItem.collapse();
}
let onCollapsed = function () {
// reset group size
groupItem.setBounds(oldBounds);
groupItem.setUserSize();
next();
}
groupItem.expand();
}
tests.push(testFocusTitle);
tests.push(testHideTabView);
tests.push(testCloseGroupUndo);
tests.push(testMouseOut);
tests.push(testSortInGroup);
tests.push(testResizeGroup);
tests.push(testRemoveWhileStacked);
tests.push(testExpandedMode);
waitForExplicitFinish();
newWindowWithTabView(function (tvwin) {
win = tvwin;
registerCleanupFunction(function () {
if (!win.closed)
win.close();
});
cw = win.TabView.getContentWindow();
groupItem = cw.GroupItems.groupItems[0];
groupItem.setSize(400, 200, true);
groupItem.setUserSize();
for (let i=0; i<3; i++)
win.gBrowser.loadOneTab('about:blank', {inBackground: true});
afterAllTabsLoaded(next, win);
});
}