browser/components/customizableui/src/PanelWideWidgetTracker.jsm

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/browser/components/customizableui/src/PanelWideWidgetTracker.jsm	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,176 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +
     1.8 +"use strict";
     1.9 +const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
    1.10 +
    1.11 +this.EXPORTED_SYMBOLS = ["PanelWideWidgetTracker"];
    1.12 +
    1.13 +Cu.import("resource://gre/modules/XPCOMUtils.jsm");
    1.14 +XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
    1.15 +  "resource:///modules/CustomizableUI.jsm");
    1.16 +
    1.17 +let gModuleName = "[PanelWideWidgetTracker]";
    1.18 +#include logging.js
    1.19 +
    1.20 +let gPanel = CustomizableUI.AREA_PANEL;
    1.21 +// We keep track of the widget placements for the panel locally:
    1.22 +let gPanelPlacements = [];
    1.23 +
    1.24 +// All the wide widgets we know of:
    1.25 +let gWideWidgets = new Set();
    1.26 +// All the widgets we know of:
    1.27 +let gSeenWidgets = new Set();
    1.28 +
    1.29 +let PanelWideWidgetTracker = {
    1.30 +  // Listeners used to validate panel contents whenever they change:
    1.31 +  onWidgetAdded: function(aWidgetId, aArea, aPosition) {
    1.32 +    if (aArea == gPanel) {
    1.33 +      gPanelPlacements = CustomizableUI.getWidgetIdsInArea(gPanel);
    1.34 +      let moveForward = this.shouldMoveForward(aWidgetId, aPosition);
    1.35 +      this.adjustWidgets(aWidgetId, moveForward);
    1.36 +    }
    1.37 +  },
    1.38 +  onWidgetMoved: function(aWidgetId, aArea, aOldPosition, aNewPosition) {
    1.39 +    if (aArea == gPanel) {
    1.40 +      gPanelPlacements = CustomizableUI.getWidgetIdsInArea(gPanel);
    1.41 +      let moveForward = this.shouldMoveForward(aWidgetId, aNewPosition);
    1.42 +      this.adjustWidgets(aWidgetId, moveForward);
    1.43 +    }
    1.44 +  },
    1.45 +  onWidgetRemoved: function(aWidgetId, aPrevArea) {
    1.46 +    if (aPrevArea == gPanel) {
    1.47 +      gPanelPlacements = CustomizableUI.getWidgetIdsInArea(gPanel);
    1.48 +      let pos = gPanelPlacements.indexOf(aWidgetId);
    1.49 +      this.adjustWidgets(aWidgetId, false);
    1.50 +    }
    1.51 +  },
    1.52 +  onWidgetReset: function(aWidgetId) {
    1.53 +    gPanelPlacements = CustomizableUI.getWidgetIdsInArea(gPanel);
    1.54 +  },
    1.55 +  // Listener to keep abreast of any new nodes. We use the DOM one because
    1.56 +  // we need access to the actual node's classlist, so we can't use the ones above.
    1.57 +  // Furthermore, onWidgetCreated only fires for API-based widgets, not for XUL ones.
    1.58 +  onWidgetAfterDOMChange: function(aNode, aNextNode, aContainer) {
    1.59 +    if (!gSeenWidgets.has(aNode.id)) {
    1.60 +      if (aNode.classList.contains(CustomizableUI.WIDE_PANEL_CLASS)) {
    1.61 +        gWideWidgets.add(aNode.id);
    1.62 +      }
    1.63 +      gSeenWidgets.add(aNode.id);
    1.64 +    }
    1.65 +  },
    1.66 +  // When widgets get destroyed, we remove them from our sets of stuff we care about:
    1.67 +  onWidgetDestroyed: function(aWidgetId) {
    1.68 +    gSeenWidgets.delete(aWidgetId);
    1.69 +    gWideWidgets.delete(aWidgetId);
    1.70 +  },
    1.71 +  shouldMoveForward: function(aWidgetId, aPosition) {
    1.72 +    let currentWidgetAtPosition = gPanelPlacements[aPosition + 1];
    1.73 +    let rv = gWideWidgets.has(currentWidgetAtPosition) && !gWideWidgets.has(aWidgetId);
    1.74 +    // We might now think we can move forward, but for that we need at least 2 more small
    1.75 +    // widgets to be present:
    1.76 +    if (rv) {
    1.77 +      let furtherWidgets = gPanelPlacements.slice(aPosition + 2);
    1.78 +      let realWidgets = 0;
    1.79 +      if (furtherWidgets.length >= 2) {
    1.80 +        while (furtherWidgets.length && realWidgets < 2) {
    1.81 +          let w = furtherWidgets.shift();
    1.82 +          if (!gWideWidgets.has(w) && this.checkWidgetStatus(w)) {
    1.83 +            realWidgets++;
    1.84 +          }
    1.85 +        }
    1.86 +      }
    1.87 +      if (realWidgets < 2) {
    1.88 +        rv = false;
    1.89 +      }
    1.90 +    }
    1.91 +    return rv;
    1.92 +  },
    1.93 +  adjustWidgets: function(aWidgetId, aMoveForwards) {
    1.94 +    if (this.adjusting) {
    1.95 +      return;
    1.96 +    }
    1.97 +    this.adjusting = true;
    1.98 +    let widgetsAffected = [w for (w of gPanelPlacements) if (gWideWidgets.has(w))];
    1.99 +    // If we're moving the wide widgets forwards (down/to the right in the panel)
   1.100 +    // we want to start with the last widgets. Otherwise we move widgets over other wide
   1.101 +    // widgets, which might mess up their order. Likewise, if moving backwards we should start with
   1.102 +    // the first widget and work our way down/right from there.
   1.103 +    let compareFn = aMoveForwards ? (function(a, b) a < b) : (function(a, b) a > b)
   1.104 +    widgetsAffected.sort(function(a, b) compareFn(gPanelPlacements.indexOf(a),
   1.105 +                                                  gPanelPlacements.indexOf(b)));
   1.106 +    for (let widget of widgetsAffected) {
   1.107 +      this.adjustPosition(widget, aMoveForwards);
   1.108 +    }
   1.109 +    this.adjusting = false;
   1.110 +  },
   1.111 +  // This function is called whenever an item gets moved in the menu panel. It
   1.112 +  // adjusts the position of widgets within the panel to prevent "gaps" between
   1.113 +  // wide widgets that could be filled up with single column widgets
   1.114 +  adjustPosition: function(aWidgetId, aMoveForwards) {
   1.115 +    // Make sure that there are n % columns = 0 narrow buttons before the widget.
   1.116 +    let placementIndex = gPanelPlacements.indexOf(aWidgetId);
   1.117 +    let prevSiblingCount = 0;
   1.118 +    let fixedPos = null;
   1.119 +    while (placementIndex--) {
   1.120 +      let thisWidgetId = gPanelPlacements[placementIndex];
   1.121 +      if (gWideWidgets.has(thisWidgetId)) {
   1.122 +        continue;
   1.123 +      }
   1.124 +      let widgetStatus = this.checkWidgetStatus(thisWidgetId);
   1.125 +      if (!widgetStatus) {
   1.126 +        continue;
   1.127 +      }
   1.128 +      if (widgetStatus == "public-only") {
   1.129 +        fixedPos = !fixedPos ? placementIndex : Math.min(fixedPos, placementIndex);
   1.130 +        prevSiblingCount = 0;
   1.131 +      } else {
   1.132 +        prevSiblingCount++;
   1.133 +      }
   1.134 +    }
   1.135 +
   1.136 +    if (fixedPos !== null || prevSiblingCount % CustomizableUI.PANEL_COLUMN_COUNT) {
   1.137 +      let desiredPos = (fixedPos !== null) ? fixedPos : gPanelPlacements.indexOf(aWidgetId);
   1.138 +      let desiredChange = -(prevSiblingCount % CustomizableUI.PANEL_COLUMN_COUNT);
   1.139 +      if (aMoveForwards && fixedPos == null) {
   1.140 +        // +1 because otherwise we'd count ourselves:
   1.141 +        desiredChange = CustomizableUI.PANEL_COLUMN_COUNT + desiredChange + 1;
   1.142 +      }
   1.143 +      desiredPos += desiredChange;
   1.144 +      CustomizableUI.moveWidgetWithinArea(aWidgetId, desiredPos);
   1.145 +    }
   1.146 +  },
   1.147 +
   1.148 +  /*
   1.149 +   * Check whether a widget id is actually known anywhere.
   1.150 +   * @returns false if the widget doesn't exist,
   1.151 +   *          "public-only" if it's not shown in private windows
   1.152 +   *          "real" if it does exist and is shown even in private windows
   1.153 +   */
   1.154 +  checkWidgetStatus: function(aWidgetId) {
   1.155 +    let widgetWrapper = CustomizableUI.getWidget(aWidgetId);
   1.156 +    // This widget might not actually exist:
   1.157 +    if (!widgetWrapper) {
   1.158 +      return false;
   1.159 +    }
   1.160 +    // This widget might still not actually exist:
   1.161 +    if (widgetWrapper.provider == CustomizableUI.PROVIDER_XUL &&
   1.162 +        widgetWrapper.instances.length == 0) {
   1.163 +      return false;
   1.164 +    }
   1.165 +
   1.166 +    // Or it might only be there some of the time:
   1.167 +    if (widgetWrapper.provider == CustomizableUI.PROVIDER_API &&
   1.168 +        widgetWrapper.showInPrivateBrowsing === false) {
   1.169 +      return "public-only";
   1.170 +    }
   1.171 +    return "real";
   1.172 +  },
   1.173 +
   1.174 +  init: function() {
   1.175 +    // Initialize our local placements copy and register the listener
   1.176 +    gPanelPlacements = CustomizableUI.getWidgetIdsInArea(gPanel);
   1.177 +    CustomizableUI.addListener(this);
   1.178 +  },
   1.179 +};

mercurial