2013-09-17 20:33:02 +00:00
|
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
|
|
|
|
"use strict";
|
|
|
|
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
|
|
|
|
|
|
|
|
this.EXPORTED_SYMBOLS = ["PanelWideWidgetTracker"];
|
|
|
|
|
|
|
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
|
|
|
XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
|
|
|
|
"resource:///modules/CustomizableUI.jsm");
|
|
|
|
|
|
|
|
let gModuleName = "[PanelWideWidgetTracker]";
|
|
|
|
#include logging.js
|
|
|
|
|
|
|
|
let gPanel = CustomizableUI.AREA_PANEL;
|
|
|
|
// We keep track of the widget placements for the panel locally:
|
|
|
|
let gPanelPlacements = [];
|
|
|
|
|
|
|
|
// All the wide widgets we know of:
|
|
|
|
let gWideWidgets = new Set();
|
|
|
|
// All the widgets we know of:
|
|
|
|
let gSeenWidgets = new Set();
|
|
|
|
|
|
|
|
let PanelWideWidgetTracker = {
|
|
|
|
// Listeners used to validate panel contents whenever they change:
|
|
|
|
onWidgetAdded: function(aWidgetId, aArea, aPosition) {
|
|
|
|
if (aArea == gPanel) {
|
2013-10-10 11:40:44 +00:00
|
|
|
gPanelPlacements = CustomizableUI.getWidgetIdsInArea(gPanel);
|
2013-09-17 20:33:02 +00:00
|
|
|
let moveForward = this.shouldMoveForward(aWidgetId, aPosition);
|
2013-10-10 11:40:44 +00:00
|
|
|
this.adjustWidgets(aWidgetId, moveForward);
|
2013-09-17 20:33:02 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
onWidgetMoved: function(aWidgetId, aArea, aOldPosition, aNewPosition) {
|
|
|
|
if (aArea == gPanel) {
|
2013-10-10 11:40:44 +00:00
|
|
|
gPanelPlacements = CustomizableUI.getWidgetIdsInArea(gPanel);
|
2013-09-17 20:33:02 +00:00
|
|
|
let moveForward = this.shouldMoveForward(aWidgetId, aNewPosition);
|
2013-10-10 11:40:44 +00:00
|
|
|
this.adjustWidgets(aWidgetId, moveForward);
|
2013-09-17 20:33:02 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
onWidgetRemoved: function(aWidgetId, aPrevArea) {
|
|
|
|
if (aPrevArea == gPanel) {
|
2013-10-10 11:40:44 +00:00
|
|
|
gPanelPlacements = CustomizableUI.getWidgetIdsInArea(gPanel);
|
2013-09-17 20:33:02 +00:00
|
|
|
let pos = gPanelPlacements.indexOf(aWidgetId);
|
2013-10-10 11:40:44 +00:00
|
|
|
this.adjustWidgets(aWidgetId, false);
|
2013-09-17 20:33:02 +00:00
|
|
|
}
|
|
|
|
},
|
2013-10-10 11:40:44 +00:00
|
|
|
onWidgetReset: function(aWidgetId) {
|
|
|
|
gPanelPlacements = CustomizableUI.getWidgetIdsInArea(gPanel);
|
|
|
|
},
|
2013-09-17 20:33:02 +00:00
|
|
|
// Listener to keep abreast of any new nodes. We use the DOM one because
|
|
|
|
// we need access to the actual node's classlist, so we can't use the ones above.
|
|
|
|
// Furthermore, onWidgetCreated only fires for API-based widgets, not for XUL ones.
|
|
|
|
onWidgetAfterDOMChange: function(aNode, aNextNode, aContainer) {
|
|
|
|
if (!gSeenWidgets.has(aNode.id)) {
|
2013-11-13 13:20:32 +00:00
|
|
|
if (aNode.classList.contains(CustomizableUI.WIDE_PANEL_CLASS)) {
|
2013-09-17 20:33:02 +00:00
|
|
|
gWideWidgets.add(aNode.id);
|
|
|
|
}
|
|
|
|
gSeenWidgets.add(aNode.id);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
// When widgets get destroyed, we remove them from our sets of stuff we care about:
|
|
|
|
onWidgetDestroyed: function(aWidgetId) {
|
2013-09-24 09:39:01 +00:00
|
|
|
gSeenWidgets.delete(aWidgetId);
|
|
|
|
gWideWidgets.delete(aWidgetId);
|
2013-09-17 20:33:02 +00:00
|
|
|
},
|
|
|
|
shouldMoveForward: function(aWidgetId, aPosition) {
|
2013-10-10 11:40:44 +00:00
|
|
|
let currentWidgetAtPosition = gPanelPlacements[aPosition + 1];
|
2013-11-15 14:25:41 +00:00
|
|
|
let rv = gWideWidgets.has(currentWidgetAtPosition) && !gWideWidgets.has(aWidgetId);
|
|
|
|
// We might now think we can move forward, but for that we need at least 2 more small
|
|
|
|
// widgets to be present:
|
|
|
|
if (rv) {
|
|
|
|
let furtherWidgets = gPanelPlacements.slice(aPosition + 2);
|
|
|
|
let realWidgets = 0;
|
|
|
|
if (furtherWidgets.length >= 2) {
|
|
|
|
while (furtherWidgets.length && realWidgets < 2) {
|
|
|
|
let w = furtherWidgets.shift();
|
|
|
|
if (!gWideWidgets.has(w) && this.checkWidgetStatus(w)) {
|
|
|
|
realWidgets++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (realWidgets < 2) {
|
|
|
|
rv = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return rv;
|
2013-09-17 20:33:02 +00:00
|
|
|
},
|
2013-10-10 11:40:44 +00:00
|
|
|
adjustWidgets: function(aWidgetId, aMoveForwards) {
|
|
|
|
if (this.adjusting) {
|
2013-09-17 20:33:02 +00:00
|
|
|
return;
|
|
|
|
}
|
2013-10-10 11:40:44 +00:00
|
|
|
this.adjusting = true;
|
|
|
|
let widgetsAffected = [w for (w of gPanelPlacements) if (gWideWidgets.has(w))];
|
|
|
|
// If we're moving the wide widgets forwards (down/to the right in the panel)
|
|
|
|
// we want to start with the last widgets. Otherwise we move widgets over other wide
|
|
|
|
// widgets, which might mess up their order. Likewise, if moving backwards we should start with
|
|
|
|
// the first widget and work our way down/right from there.
|
|
|
|
let compareFn = aMoveForwards ? (function(a, b) a < b) : (function(a, b) a > b)
|
|
|
|
widgetsAffected.sort(function(a, b) compareFn(gPanelPlacements.indexOf(a),
|
|
|
|
gPanelPlacements.indexOf(b)));
|
|
|
|
for (let widget of widgetsAffected) {
|
|
|
|
this.adjustPosition(widget, aMoveForwards);
|
2013-09-17 20:33:02 +00:00
|
|
|
}
|
2013-10-10 11:40:44 +00:00
|
|
|
this.adjusting = false;
|
2013-09-17 20:33:02 +00:00
|
|
|
},
|
|
|
|
// This function is called whenever an item gets moved in the menu panel. It
|
2013-10-10 11:40:44 +00:00
|
|
|
// adjusts the position of widgets within the panel to prevent "gaps" between
|
|
|
|
// wide widgets that could be filled up with single column widgets
|
|
|
|
adjustPosition: function(aWidgetId, aMoveForwards) {
|
2013-09-17 20:33:02 +00:00
|
|
|
// Make sure that there are n % columns = 0 narrow buttons before the widget.
|
|
|
|
let placementIndex = gPanelPlacements.indexOf(aWidgetId);
|
|
|
|
let prevSiblingCount = 0;
|
|
|
|
let fixedPos = null;
|
|
|
|
while (placementIndex--) {
|
|
|
|
let thisWidgetId = gPanelPlacements[placementIndex];
|
|
|
|
if (gWideWidgets.has(thisWidgetId)) {
|
|
|
|
continue;
|
|
|
|
}
|
2013-11-15 14:25:41 +00:00
|
|
|
let widgetStatus = this.checkWidgetStatus(thisWidgetId);
|
|
|
|
if (!widgetStatus) {
|
2013-09-17 20:33:02 +00:00
|
|
|
continue;
|
|
|
|
}
|
2013-11-15 14:25:41 +00:00
|
|
|
if (widgetStatus == "public-only") {
|
|
|
|
fixedPos = !fixedPos ? placementIndex : Math.min(fixedPos, placementIndex);
|
2013-09-17 20:33:02 +00:00
|
|
|
prevSiblingCount = 0;
|
|
|
|
} else {
|
|
|
|
prevSiblingCount++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-11-13 13:20:32 +00:00
|
|
|
if (fixedPos !== null || prevSiblingCount % CustomizableUI.PANEL_COLUMN_COUNT) {
|
2013-09-17 20:33:02 +00:00
|
|
|
let desiredPos = (fixedPos !== null) ? fixedPos : gPanelPlacements.indexOf(aWidgetId);
|
2013-11-13 13:20:32 +00:00
|
|
|
let desiredChange = -(prevSiblingCount % CustomizableUI.PANEL_COLUMN_COUNT);
|
2013-10-10 11:40:44 +00:00
|
|
|
if (aMoveForwards && fixedPos == null) {
|
|
|
|
// +1 because otherwise we'd count ourselves:
|
2013-11-13 13:20:32 +00:00
|
|
|
desiredChange = CustomizableUI.PANEL_COLUMN_COUNT + desiredChange + 1;
|
2013-09-17 20:33:02 +00:00
|
|
|
}
|
2013-10-10 11:40:44 +00:00
|
|
|
desiredPos += desiredChange;
|
2013-09-17 20:33:02 +00:00
|
|
|
CustomizableUI.moveWidgetWithinArea(aWidgetId, desiredPos);
|
|
|
|
}
|
|
|
|
},
|
2013-11-15 14:25:41 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Check whether a widget id is actually known anywhere.
|
|
|
|
* @returns false if the widget doesn't exist,
|
|
|
|
* "public-only" if it's not shown in private windows
|
|
|
|
* "real" if it does exist and is shown even in private windows
|
|
|
|
*/
|
|
|
|
checkWidgetStatus: function(aWidgetId) {
|
|
|
|
let widgetWrapper = CustomizableUI.getWidget(aWidgetId);
|
|
|
|
// This widget might not actually exist:
|
|
|
|
if (!widgetWrapper) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
// This widget might still not actually exist:
|
|
|
|
if (widgetWrapper.provider == CustomizableUI.PROVIDER_XUL &&
|
|
|
|
widgetWrapper.instances.length == 0) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Or it might only be there some of the time:
|
|
|
|
if (widgetWrapper.provider == CustomizableUI.PROVIDER_API &&
|
|
|
|
widgetWrapper.showInPrivateBrowsing === false) {
|
|
|
|
return "public-only";
|
|
|
|
}
|
|
|
|
return "real";
|
|
|
|
},
|
|
|
|
|
2013-09-17 20:33:02 +00:00
|
|
|
init: function() {
|
|
|
|
// Initialize our local placements copy and register the listener
|
|
|
|
gPanelPlacements = CustomizableUI.getWidgetIdsInArea(gPanel);
|
|
|
|
CustomizableUI.addListener(this);
|
|
|
|
},
|
|
|
|
};
|