diff options
Diffstat (limited to 'application/basilisk/components/customizableui/PanelWideWidgetTracker.jsm')
-rw-r--r-- | application/basilisk/components/customizableui/PanelWideWidgetTracker.jsm | 172 |
1 files changed, 172 insertions, 0 deletions
diff --git a/application/basilisk/components/customizableui/PanelWideWidgetTracker.jsm b/application/basilisk/components/customizableui/PanelWideWidgetTracker.jsm new file mode 100644 index 000000000..768cebbca --- /dev/null +++ b/application/basilisk/components/customizableui/PanelWideWidgetTracker.jsm @@ -0,0 +1,172 @@ +/* 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"); + +var gPanel = CustomizableUI.AREA_PANEL; +// We keep track of the widget placements for the panel locally: +var gPanelPlacements = []; + +// All the wide widgets we know of: +var gWideWidgets = new Set(); +// All the widgets we know of: +var gSeenWidgets = new Set(); + +var PanelWideWidgetTracker = { + // Listeners used to validate panel contents whenever they change: + onWidgetAdded: function(aWidgetId, aArea, aPosition) { + if (aArea == gPanel) { + gPanelPlacements = CustomizableUI.getWidgetIdsInArea(gPanel); + let moveForward = this.shouldMoveForward(aWidgetId, aPosition); + this.adjustWidgets(aWidgetId, moveForward); + } + }, + onWidgetMoved: function(aWidgetId, aArea, aOldPosition, aNewPosition) { + if (aArea == gPanel) { + gPanelPlacements = CustomizableUI.getWidgetIdsInArea(gPanel); + let moveForward = this.shouldMoveForward(aWidgetId, aNewPosition); + this.adjustWidgets(aWidgetId, moveForward); + } + }, + onWidgetRemoved: function(aWidgetId, aPrevArea) { + if (aPrevArea == gPanel) { + gPanelPlacements = CustomizableUI.getWidgetIdsInArea(gPanel); + this.adjustWidgets(aWidgetId, false); + } + }, + onWidgetReset: function(aWidgetId) { + gPanelPlacements = CustomizableUI.getWidgetIdsInArea(gPanel); + }, + // 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)) { + if (aNode.classList.contains(CustomizableUI.WIDE_PANEL_CLASS)) { + 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) { + gSeenWidgets.delete(aWidgetId); + gWideWidgets.delete(aWidgetId); + }, + shouldMoveForward: function(aWidgetId, aPosition) { + let currentWidgetAtPosition = gPanelPlacements[aPosition + 1]; + 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; + }, + adjustWidgets: function(aWidgetId, aMoveForwards) { + if (this.adjusting) { + return; + } + this.adjusting = true; + let widgetsAffected = gPanelPlacements.filter((w) => 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 ? ((a, b) => a < b) : ((a, b) => a > b); + widgetsAffected.sort((a, b) => compareFn(gPanelPlacements.indexOf(a), + gPanelPlacements.indexOf(b))); + for (let widget of widgetsAffected) { + this.adjustPosition(widget, aMoveForwards); + } + this.adjusting = false; + }, + // This function is called whenever an item gets moved in the menu panel. It + // 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) { + // 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; + } + let widgetStatus = this.checkWidgetStatus(thisWidgetId); + if (!widgetStatus) { + continue; + } + if (widgetStatus == "public-only") { + fixedPos = !fixedPos ? placementIndex : Math.min(fixedPos, placementIndex); + prevSiblingCount = 0; + } else { + prevSiblingCount++; + } + } + + if (fixedPos !== null || prevSiblingCount % CustomizableUI.PANEL_COLUMN_COUNT) { + let desiredPos = (fixedPos !== null) ? fixedPos : gPanelPlacements.indexOf(aWidgetId); + let desiredChange = -(prevSiblingCount % CustomizableUI.PANEL_COLUMN_COUNT); + if (aMoveForwards && fixedPos == null) { + // +1 because otherwise we'd count ourselves: + desiredChange = CustomizableUI.PANEL_COLUMN_COUNT + desiredChange + 1; + } + desiredPos += desiredChange; + CustomizableUI.moveWidgetWithinArea(aWidgetId, desiredPos); + } + }, + + /* + * 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"; + }, + + init: function() { + // Initialize our local placements copy and register the listener + gPanelPlacements = CustomizableUI.getWidgetIdsInArea(gPanel); + CustomizableUI.addListener(this); + }, +}; |