michael@0: #ifdef 0 michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: #endif michael@0: michael@0: /** michael@0: * This singleton provides the ability to re-arrange the current grid to michael@0: * indicate the transformation that results from dropping a cell at a certain michael@0: * position. michael@0: */ michael@0: let gDropPreview = { michael@0: /** michael@0: * Rearranges the sites currently contained in the grid when a site would be michael@0: * dropped onto the given cell. michael@0: * @param aCell The drop target cell. michael@0: * @return The re-arranged array of sites. michael@0: */ michael@0: rearrange: function DropPreview_rearrange(aCell) { michael@0: let sites = gGrid.sites; michael@0: michael@0: // Insert the dragged site into the current grid. michael@0: this._insertDraggedSite(sites, aCell); michael@0: michael@0: // After the new site has been inserted we need to correct the positions michael@0: // of all pinned tabs that have been moved around. michael@0: this._repositionPinnedSites(sites, aCell); michael@0: michael@0: return sites; michael@0: }, michael@0: michael@0: /** michael@0: * Inserts the currently dragged site into the given array of sites. michael@0: * @param aSites The array of sites to insert into. michael@0: * @param aCell The drop target cell. michael@0: */ michael@0: _insertDraggedSite: function DropPreview_insertDraggedSite(aSites, aCell) { michael@0: let dropIndex = aCell.index; michael@0: let draggedSite = gDrag.draggedSite; michael@0: michael@0: // We're currently dragging a site. michael@0: if (draggedSite) { michael@0: let dragCell = draggedSite.cell; michael@0: let dragIndex = dragCell.index; michael@0: michael@0: // Move the dragged site into its new position. michael@0: if (dragIndex != dropIndex) { michael@0: aSites.splice(dragIndex, 1); michael@0: aSites.splice(dropIndex, 0, draggedSite); michael@0: } michael@0: // We're handling an external drag item. michael@0: } else { michael@0: aSites.splice(dropIndex, 0, null); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Correct the position of all pinned sites that might have been moved to michael@0: * different positions after the dragged site has been inserted. michael@0: * @param aSites The array of sites containing the dragged site. michael@0: * @param aCell The drop target cell. michael@0: */ michael@0: _repositionPinnedSites: michael@0: function DropPreview_repositionPinnedSites(aSites, aCell) { michael@0: michael@0: // Collect all pinned sites. michael@0: let pinnedSites = this._filterPinnedSites(aSites, aCell); michael@0: michael@0: // Correct pinned site positions. michael@0: pinnedSites.forEach(function (aSite) { michael@0: aSites[aSites.indexOf(aSite)] = aSites[aSite.cell.index]; michael@0: aSites[aSite.cell.index] = aSite; michael@0: }, this); michael@0: michael@0: // There might be a pinned cell that got pushed out of the grid, try to michael@0: // sneak it in by removing a lower-priority cell. michael@0: if (this._hasOverflowedPinnedSite(aSites, aCell)) michael@0: this._repositionOverflowedPinnedSite(aSites, aCell); michael@0: }, michael@0: michael@0: /** michael@0: * Filter pinned sites out of the grid that are still on their old positions michael@0: * and have not moved. michael@0: * @param aSites The array of sites to filter. michael@0: * @param aCell The drop target cell. michael@0: * @return The filtered array of sites. michael@0: */ michael@0: _filterPinnedSites: function DropPreview_filterPinnedSites(aSites, aCell) { michael@0: let draggedSite = gDrag.draggedSite; michael@0: michael@0: // When dropping on a cell that contains a pinned site make sure that all michael@0: // pinned cells surrounding the drop target are moved as well. michael@0: let range = this._getPinnedRange(aCell); michael@0: michael@0: return aSites.filter(function (aSite, aIndex) { michael@0: // The site must be valid, pinned and not the dragged site. michael@0: if (!aSite || aSite == draggedSite || !aSite.isPinned()) michael@0: return false; michael@0: michael@0: let index = aSite.cell.index; michael@0: michael@0: // If it's not in the 'pinned range' it's a valid pinned site. michael@0: return (index > range.end || index < range.start); michael@0: }); michael@0: }, michael@0: michael@0: /** michael@0: * Determines the range of pinned sites surrounding the drop target cell. michael@0: * @param aCell The drop target cell. michael@0: * @return The range of pinned cells. michael@0: */ michael@0: _getPinnedRange: function DropPreview_getPinnedRange(aCell) { michael@0: let dropIndex = aCell.index; michael@0: let range = {start: dropIndex, end: dropIndex}; michael@0: michael@0: // We need a pinned range only when dropping on a pinned site. michael@0: if (aCell.containsPinnedSite()) { michael@0: let links = gPinnedLinks.links; michael@0: michael@0: // Find all previous siblings of the drop target that are pinned as well. michael@0: while (range.start && links[range.start - 1]) michael@0: range.start--; michael@0: michael@0: let maxEnd = links.length - 1; michael@0: michael@0: // Find all next siblings of the drop target that are pinned as well. michael@0: while (range.end < maxEnd && links[range.end + 1]) michael@0: range.end++; michael@0: } michael@0: michael@0: return range; michael@0: }, michael@0: michael@0: /** michael@0: * Checks if the given array of sites contains a pinned site that has michael@0: * been pushed out of the grid. michael@0: * @param aSites The array of sites to check. michael@0: * @param aCell The drop target cell. michael@0: * @return Whether there is an overflowed pinned cell. michael@0: */ michael@0: _hasOverflowedPinnedSite: michael@0: function DropPreview_hasOverflowedPinnedSite(aSites, aCell) { michael@0: michael@0: // If the drop target isn't pinned there's no way a pinned site has been michael@0: // pushed out of the grid so we can just exit here. michael@0: if (!aCell.containsPinnedSite()) michael@0: return false; michael@0: michael@0: let cells = gGrid.cells; michael@0: michael@0: // No cells have been pushed out of the grid, nothing to do here. michael@0: if (aSites.length <= cells.length) michael@0: return false; michael@0: michael@0: let overflowedSite = aSites[cells.length]; michael@0: michael@0: // Nothing to do if the site that got pushed out of the grid is not pinned. michael@0: return (overflowedSite && overflowedSite.isPinned()); michael@0: }, michael@0: michael@0: /** michael@0: * We have a overflowed pinned site that we need to re-position so that it's michael@0: * visible again. We try to find a lower-priority cell (empty or containing michael@0: * an unpinned site) that we can move it to. michael@0: * @param aSites The array of sites. michael@0: * @param aCell The drop target cell. michael@0: */ michael@0: _repositionOverflowedPinnedSite: michael@0: function DropPreview_repositionOverflowedPinnedSite(aSites, aCell) { michael@0: michael@0: // Try to find a lower-priority cell (empty or containing an unpinned site). michael@0: let index = this._indexOfLowerPrioritySite(aSites, aCell); michael@0: michael@0: if (index > -1) { michael@0: let cells = gGrid.cells; michael@0: let dropIndex = aCell.index; michael@0: michael@0: // Move all pinned cells to their new positions to let the overflowed michael@0: // site fit into the grid. michael@0: for (let i = index + 1, lastPosition = index; i < aSites.length; i++) { michael@0: if (i != dropIndex) { michael@0: aSites[lastPosition] = aSites[i]; michael@0: lastPosition = i; michael@0: } michael@0: } michael@0: michael@0: // Finally, remove the overflowed site from its previous position. michael@0: aSites.splice(cells.length, 1); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Finds the index of the last cell that is empty or contains an unpinned michael@0: * site. These are considered to be of a lower priority. michael@0: * @param aSites The array of sites. michael@0: * @param aCell The drop target cell. michael@0: * @return The cell's index. michael@0: */ michael@0: _indexOfLowerPrioritySite: michael@0: function DropPreview_indexOfLowerPrioritySite(aSites, aCell) { michael@0: michael@0: let cells = gGrid.cells; michael@0: let dropIndex = aCell.index; michael@0: michael@0: // Search (beginning with the last site in the grid) for a site that is michael@0: // empty or unpinned (an thus lower-priority) and can be pushed out of the michael@0: // grid instead of the pinned site. michael@0: for (let i = cells.length - 1; i >= 0; i--) { michael@0: // The cell that is our drop target is not a good choice. michael@0: if (i == dropIndex) michael@0: continue; michael@0: michael@0: let site = aSites[i]; michael@0: michael@0: // We can use the cell only if it's empty or the site is un-pinned. michael@0: if (!site || !site.isPinned()) michael@0: return i; michael@0: } michael@0: michael@0: return -1; michael@0: } michael@0: };