loleaflet/src/control/Control.ColumnHeader.js |   77 +++--
 loleaflet/src/control/Control.Header.js       |  377 ++++++++++++++------------
 loleaflet/src/control/Control.RowHeader.js    |   77 +++--
 loleaflet/src/layer/CalcGridLines.js          |   28 -
 loleaflet/src/layer/tile/CalcTileLayer.js     |    3 
 5 files changed, 323 insertions(+), 239 deletions(-)

New commits:
commit 0b50166333219a5e111aa6e5344f940ead6ad628
Author:     Dennis Francis <dennis.fran...@collabora.com>
AuthorDate: Tue Jul 7 15:42:47 2020 +0530
Commit:     Dennis Francis <dennis.fran...@collabora.com>
CommitDate: Wed Jul 8 17:02:07 2020 +0200

    make row/col headers render correctly for split-panes
    
    For this to work, we need sheet-geometry data. GapTickMap is dropped
    with a replacement HeaderInfo class that is easier/less confusing to
    work with split-panes. All indices in HeaderInfo are 0-based and a
    column/row is referred to as an 'element' when things have to be
    generic.
    
    Change-Id: Ibddac8901d48cada554b715af70195ef9b9832e2
    Reviewed-on: https://gerrit.libreoffice.org/c/online/+/98357
    Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoff...@gmail.com>
    Tested-by: Jenkins
    Reviewed-by: Dennis Francis <dennis.fran...@collabora.com>

diff --git a/loleaflet/src/control/Control.ColumnHeader.js 
b/loleaflet/src/control/Control.ColumnHeader.js
index 80559a20d..8a07f1e86 100644
--- a/loleaflet/src/control/Control.ColumnHeader.js
+++ b/loleaflet/src/control/Control.ColumnHeader.js
@@ -11,6 +11,7 @@ L.Control.ColumnHeader = L.Control.Header.extend({
 
        onAdd: function (map) {
                map.on('updatepermission', this._onUpdatePermission, this);
+               map.on('moveend zoomchanged sheetgeometrychanged 
splitposchanged', this._updateCanvas, this);
                this._initialized = false;
        },
 
@@ -159,6 +160,13 @@ L.Control.ColumnHeader = L.Control.Header.extend({
                this._updateColumnHeader();
        },
 
+       _updateCanvas: function () {
+               if (this._headerInfo) {
+                       this._headerInfo.update();
+                       this._redrawHeaders();
+               }
+       },
+
        setScrollPosition: function (e) {
                var position = -e.x;
                this._position = Math.min(0, position);
@@ -186,7 +194,7 @@ L.Control.ColumnHeader = L.Control.Header.extend({
        },
 
        _onUpdateCurrentColumn: function (e) {
-               var x = e.curX;
+               var x = e.curX - 1; // 1-based to 0-based.
                var w = this._twipsToPixels(e.width);
                var slim = w <= 1;
                this.updateCurrent(x, slim);
@@ -201,10 +209,10 @@ L.Control.ColumnHeader = L.Control.Header.extend({
                        return;
 
                var ctx = this._canvasContext;
-               var content = this._colIndexToAlpha(entry.index);
+               var content = this._colIndexToAlpha(entry.index + 1);
                var startOrt = this._canvasHeight - this._headerHeight;
-               var startPar = entry.pos - entry.size - this._startOffset;
-               var endPar = entry.pos - this._startOffset;
+               var startPar = entry.pos - entry.size;
+               var endPar = entry.pos;
                var width = endPar - startPar;
                var height = this._headerHeight;
 
@@ -218,7 +226,6 @@ L.Control.ColumnHeader = L.Control.Header.extend({
                ctx.save();
                var scale = L.getDpiScaleFactor();
                ctx.scale(scale, scale);
-               ctx.translate(this._position + this._startOffset, 0);
                // background gradient
                var selectionBackgroundGradient = null;
                if (isHighlighted) {
@@ -279,14 +286,13 @@ L.Control.ColumnHeader = L.Control.Header.extend({
                var level = group.level;
 
                var startOrt = spacing + (headSize + spacing) * level;
-               var startPar = group.startPos - this._startOffset;
+               var startPar = this._headerInfo.docToHeaderPos(group.startPos);
                var height = group.endPos - group.startPos;
 
                ctx.save();
                var scale = L.getDpiScaleFactor();
                ctx.scale(scale, scale);
 
-               ctx.translate(this._position + this._startOffset, 0);
                // clip mask
                ctx.beginPath();
                ctx.rect(startPar, startOrt, height, headSize);
@@ -357,7 +363,7 @@ L.Control.ColumnHeader = L.Control.Header.extend({
        getHeaderEntryBoundingClientRect: function (index) {
                var entry = this._mouseOverEntry;
                if (index) {
-                       entry = this._tickMap.getGap(index);
+                       entry = this._headerInfo.getColData(index);
                }
 
                if (!entry)
@@ -365,8 +371,8 @@ L.Control.ColumnHeader = L.Control.Header.extend({
 
                var rect = this._canvas.getBoundingClientRect();
 
-               var colStart = entry.pos - entry.size + this._position;
-               var colEnd = entry.pos + this._position;
+               var colStart = entry.pos - entry.size;
+               var colEnd = entry.pos;
 
                var left = rect.left + colStart;
                var right = rect.left + colEnd;
@@ -408,11 +414,12 @@ L.Control.ColumnHeader = L.Control.Header.extend({
                }
 
                var sheetGeometry = this._map._docLayer.sheetGeometry;
-               var columnsGeometry = sheetGeometry ? 
sheetGeometry.getColumnsGeometry() : undefined;
 
-               // create data structure for column widths
-               this._tickMap = new L.Control.Header.GapTickMap(this._map, 
columns, columnsGeometry);
-               this._startOffset = this._tickMap.getStartOffset();
+               if (!this._headerInfo) {
+                       // create data structure for column widths
+                       this._headerInfo = new 
L.Control.Header.HeaderInfo(this._map, true /* isCol */);
+                       this._map._colHdr = this._headerInfo;
+               }
 
                // setup conversion routine
                this.converter = L.Util.bind(converter, context);
@@ -437,13 +444,7 @@ L.Control.ColumnHeader = L.Control.Header.extend({
                        this.resize(this._headerHeight);
                }
 
-               // Initial draw
-               this._tickMap.forEachGap(function(gap) {
-                       this.drawHeaderEntry(gap, false);
-               }.bind(this));
-
-               // draw group controls
-               this.drawOutline();
+               this._redrawHeaders();
 
                this.mouseInit(canvas);
 
@@ -452,6 +453,16 @@ L.Control.ColumnHeader = L.Control.Header.extend({
                }
        },
 
+       _redrawHeaders: function () {
+               this._canvasContext.clearRect(0, 0, this._canvas.width, 
this._canvas.height);
+               this._headerInfo.forEachElement(function(elemData) {
+                       this.drawHeaderEntry(elemData, false);
+               }.bind(this));
+
+               // draw group controls
+               this.drawOutline();
+       },
+
        _colAlphaToNumber: function(alpha) {
                var res = 0;
                var offset = 'A'.charCodeAt();
@@ -482,7 +493,7 @@ L.Control.ColumnHeader = L.Control.Header.extend({
                var command = {
                        Col: {
                                type: 'unsigned short',
-                               value: colNumber - 1
+                               value: colNumber
                        },
                        Modifier: {
                                type: 'unsigned short',
@@ -549,11 +560,18 @@ L.Control.ColumnHeader = L.Control.Header.extend({
        },
 
        _getVertLatLng: function (start, offset, e) {
-               var limit = this._map.mouseEventToContainerPoint({clientX: 
start.x, clientY: start.y});
+               var size = this._map.getSize();
                var drag = this._map.mouseEventToContainerPoint(e);
+               var entryStart = this._dragEntry.pos - this._dragEntry.size;
+               var xdocpos = this._headerInfo.headerToDocPos(Math.max(drag.x, 
entryStart));
+               var ymin = this._map.getPixelBounds().min.y;
+               var ymax = ymin + size.y;
+               if (this._headerInfo.hasSplits()) {
+                       ymin = 0;
+               }
                return [
-                       this._map.containerPointToLatLng(new 
L.Point(Math.max(limit.x, drag.x + offset.x), 0)),
-                       this._map.containerPointToLatLng(new 
L.Point(Math.max(limit.x, drag.x + offset.x), this._map.getSize().y))
+                       this._map.unproject(new L.Point(xdocpos, ymin)),
+                       this._map.unproject(new L.Point(xdocpos, ymax)),
                ];
        },
 
@@ -583,8 +601,9 @@ L.Control.ColumnHeader = L.Control.Header.extend({
                        var width = clickedColumn.size;
                        var column = clickedColumn.index;
 
-                       if (this._tickMap.isZeroSize(clickedColumn.index + 1)) {
-                               column += 1;
+                       var nextCol = 
this._headerInfo.getNextIndex(clickedColumn.index);
+                       if (this._headerInfo.isZeroSize(nextCol)) {
+                               column = nextCol;
                                width = 0;
                        }
 
@@ -596,7 +615,7 @@ L.Control.ColumnHeader = L.Control.Header.extend({
                                        },
                                        Column: {
                                                type: 'unsigned short',
-                                               value: column
+                                               value: column + 1 // core 
expects 1-based index.
                                        }
                                };
 
@@ -619,7 +638,7 @@ L.Control.ColumnHeader = L.Control.Header.extend({
                        var command = {
                                Col: {
                                        type: 'unsigned short',
-                                       value: column - 1
+                                       value: column
                                },
                                Modifier: {
                                        type: 'unsigned short',
diff --git a/loleaflet/src/control/Control.Header.js 
b/loleaflet/src/control/Control.Header.js
index 3ef4bb2e0..8dff63acc 100644
--- a/loleaflet/src/control/Control.Header.js
+++ b/loleaflet/src/control/Control.Header.js
@@ -172,17 +172,17 @@ L.Control.Header = L.Control.extend({
        clearSelection: function () {
                if (this._selection.start === -1 && this._selection.end === -1)
                        return;
-               var start = (this._selection.start < 1) ? 1 : 
this._selection.start;
-               var end = this._selection.end + 1;
+               var start = (this._selection.start < 1) ? 0 : 
this._selection.start;
+               var end = this._headerInfo.getNextIndex(this._selection.end);
 
-               for (var i=start; i<end; i++) {
+               for (var i = start; i < end; i = 
this._headerInfo.getNextIndex(i)) {
                        if (i === this._current) {
                                // after clearing selection, we need to select 
the header entry for the current cursor position,
                                // since we can't be sure that the selection 
clearing is due to click on a cell
                                // different from the one where the cursor is 
already placed
-                               this.select(this._tickMap.getGap(i), true);
+                               this.select(this._headerInfo.getElementData(i), 
true);
                        } else {
-                               this.unselect(this._tickMap.getGap(i));
+                               
this.unselect(this._headerInfo.getElementData(i));
                        }
                }
 
@@ -193,25 +193,28 @@ L.Control.Header = L.Control.extend({
        // selects the new set of rows/cols.
        // Start and end are given in pixels absolute to the document
        updateSelection: function(start, end) {
-               if (!this._tickMap)
+               if (!this._headerInfo)
                        return;
 
+               start = this._headerInfo.docToHeaderPos(start);
+               end = this._headerInfo.docToHeaderPos(end);
+
                var x0 = 0, x1 = 0;
                var itStart = -1, itEnd = -1;
 
                // if the start selection position is above/on the left of the 
first header entry,
                // but the end selection position is below/on the right of it
                // then we set the start selected entry to the first header 
entry.
-               var entry = this._tickMap.getGap(this._tickMap.getMinTickIdx() 
+ 1);
+               var entry = 
this._headerInfo.getElementData(this._headerInfo.getMinIndex());
                if (entry) {
                        x0 = entry.pos - entry.size;
                        if (start < x0 && end > x0) {
-                               itStart = 1;
+                               itStart = 0;
                        }
                }
 
-               this._tickMap.forEachGap((function(entry) {
-                       x0 = entry.start;
+               this._headerInfo.forEachElement((function(entry) {
+                       x0 = entry.pos - entry.size;
                        x1 = entry.pos;
                        if (start < x1 && end > x0) {
                                this.select(entry, false);
@@ -221,7 +224,7 @@ L.Control.Header = L.Control.extend({
                        } else {
                                this.unselect(entry);
                                if (itStart !== -1 && itEnd === -1) {
-                                       itEnd = entry.index - 1;
+                                       itEnd = 
this._headerInfo.getPreviousIndex(entry.index);
                                }
                        }
                }).bind(this));
@@ -229,7 +232,7 @@ L.Control.Header = L.Control.extend({
                // if end is greater than the last fetched header position set 
itEnd to the max possible value
                // without this hack selecting a whole row and then a whole 
column (or viceversa) leads to an incorrect selection
                if (itStart !== -1 && itEnd === -1) {
-                       itEnd = this._tickMap.getMaxTickIdx() - 1;
+                       itEnd = this._headerInfo.getMaxIndex();
                }
 
                this._selection.start = itStart;
@@ -240,24 +243,24 @@ L.Control.Header = L.Control.extend({
        // Called whenever the cell cursor is in a cell corresponding to the 
cursorPos-th
        // column/row.
        updateCurrent: function (cursorPos, slim) {
-               if (!this._tickMap) {return;}
+               if (!this._headerInfo) {return;}
 
                if (cursorPos < 0) {
-                       this.unselect(this._tickMap.getGap(this._current));
+                       
this.unselect(this._headerInfo.getElementData(this._current));
                        this._current = -1;
                        return;
                }
 
-               var prevEntry = cursorPos > 0 ? this._tickMap.getGap(cursorPos 
- 1) : null;
+               var prevEntry = cursorPos > 0 ? 
this._headerInfo.getPreviousIndex(cursorPos) : null;
                var zeroSizeEntry = slim && prevEntry && prevEntry.size === 0;
 
-               var entry = this._tickMap.getGap(cursorPos);
+               var entry = this._headerInfo.getElementData(cursorPos);
                if (this._selection.start === -1 && this._selection.end === -1) 
{
                        // when a whole row (column) is selected the cell 
cursor is moved to the first column (row)
                        // but this action should not cause to select/unselect 
anything, on the contrary we end up
                        // with all column (row) header entries selected but 
the one where the cell cursor was
                        // previously placed
-                       this.unselect(this._tickMap.getGap(this._current));
+                       
this.unselect(this._headerInfo.getElementData(this._current));
                        // no selection when the cell cursor is slim
                        if (entry && !zeroSizeEntry)
                                this.select(entry, true);
@@ -280,22 +283,23 @@ L.Control.Header = L.Control.extend({
        },
 
        _entryAtPoint: function(point) {
-               if (!this._tickMap)
+               if (!this._headerInfo)
                        return false;
 
                var position = this._getParallelPos(point);
-               position = position - this._position;
 
                var that = this;
                var result = null;
-               this._tickMap.forEachGap(function(gap) {
-                       if (position >= gap.start && position < gap.end) {
-                               var resizeAreaStart = Math.max(gap.start, 
gap.end - 3);
-                               if (that.isHeaderSelected(gap.index)) {
-                                       resizeAreaStart = gap.end - 
that._resizeHandleSize;
+               this._headerInfo.forEachElement(function(entry) {
+                       var end = entry.pos;
+                       var start = end - entry.size;
+                       if (position >= start && position < end) {
+                               var resizeAreaStart = Math.max(start, end - 3);
+                               if (that.isHeaderSelected(entry.index)) {
+                                       resizeAreaStart = end - 
that._resizeHandleSize;
                                }
                                var isMouseOverResizeArea = (position > 
resizeAreaStart);
-                               result = {entry: gap, hit: 
isMouseOverResizeArea};
+                               result = {entry: entry, hit: 
isMouseOverResizeArea};
                                return true;
                        }
                });
@@ -325,6 +329,7 @@ L.Control.Header = L.Control.extend({
                this._start = new L.Point(rectangle.left, rectangle.top);
                this._offset = new L.Point(rectangle.right - event.center.x, 
rectangle.bottom - event.center.y);
                this._item = target;
+               this._dragEntry = result.entry;
                this.onDragStart(this._item, this._start, this._offset, 
{clientX: event.center.x, clientY: event.center.y});
        },
 
@@ -337,6 +342,7 @@ L.Control.Header = L.Control.extend({
                L.DomUtil.enableTextSelection();
 
                this.onDragEnd(this._item, this._start, this._offset, {clientX: 
event.center.x, clientY: event.center.y});
+               this._dragEntry = null;
 
                this._mouseOverEntry = null;
                this._item = this._start = this._offset = null;
@@ -402,7 +408,7 @@ L.Control.Header = L.Control.extend({
                        entry = result.entry;
                }
 
-               if (mouseOverIndex && (!this._mouseOverEntry || mouseOverIndex 
!== this._mouseOverEntry.index)) {
+               if (typeof mouseOverIndex === 'number' && 
(!this._mouseOverEntry || mouseOverIndex !== this._mouseOverEntry.index)) {
                        var mouseOverIsCurrent = false;
                        if (this._mouseOverEntry != null) {
                                mouseOverIsCurrent = 
(this._mouseOverEntry.index == this._current);
@@ -456,8 +462,8 @@ L.Control.Header = L.Control.extend({
                if (!group)
                        return false;
 
-               var pos = 
this._getParallelPos(this._mouseEventToCanvasPos(this._canvas, e));
-               pos = pos - this._position;
+               var pos = this._headerInfo.headerToDocPos(
+                       
this._getParallelPos(this._mouseEventToCanvasPos(this._canvas, e)));
                if (group.startPos < pos && pos < group.startPos + 
this._groupHeadSize) {
                        this._updateOutlineState(/*isColumnOutline: */ 
this._isColumn, group);
                        return true;
@@ -474,8 +480,8 @@ L.Control.Header = L.Control.extend({
                if (!group && !group.hidden)
                        return false;
 
-               var pos = 
this._getParallelPos(this._mouseEventToCanvasPos(this._canvas, e));
-               pos = pos - this._position;
+               var pos = this._headerInfo.headerToDocPos(
+                       
this._getParallelPos(this._mouseEventToCanvasPos(this._canvas, e)));
                if (group.startPos + this._groupHeadSize < pos && pos < 
group.endPos) {
                        this._updateOutlineState(/*isColumnOutline: */ 
this._isColumn, group);
                        return true;
@@ -525,6 +531,7 @@ L.Control.Header = L.Control.extend({
                this._start = new L.Point(rect.left, rect.top);
                this._offset = new L.Point(rect.right - e.clientX, rect.bottom 
- e.clientY);
                this._item = target;
+               this._dragEntry = this._mouseOverEntry;
 
                this.onDragStart(this._item, this._start, this._offset, e);
        },
@@ -561,6 +568,7 @@ L.Control.Header = L.Control.extend({
 
                this._item = this._start = this._offset = null;
                this._dragging = false;
+               this._dragEntry = null;
        },
 
        _twipsToPixels: function (twips) {
@@ -771,165 +779,208 @@ L.Control.Header = L.Control.extend({
 L.Control.Header.rowHeaderWidth = undefined;
 L.Control.Header.colHeaderHeight = undefined;
 
-/*
- * GapTickMap is a data structure for handling the dimensions of each
- * column/row in the column/row header.
- *
- * A "tick" is the position of the boundary between two cols/rows, a "gap" is
- * the space between two ticks - the position and width/height of a col/row.
- *
- * Data about ticks is 0-indexed: the first tick (top of row 1 / left of 
column A) is 0.
- *
- * Data about gaps is 1-indexed: The 1st gap (row 1 / column A) is 1, and spans
- * from tick 0 to tick 1.
- *
- * A GapTickMap is fed data coming from a 'viewrowcolumnheaders' UNO message.
- * That contains the position of some of the ticks. The rest of the tick 
positions
- * is extrapolated as follows:
- * - The first two ticks are assumed to be consecutive. This sets the size of
- *   the first gap.
- * - If the position of n-th tick is not explicit, then its position is the 
(n-1)-th tick plus
- *   the size of the last known gap.
- * - When a new tick position is defined, it resets the size of the last known 
gap
- *
- * All inputs received are given in tile pixels, and stored as such.
- * outputs are returned in CSS pixels.
- *
- * **NB.** CSS pixels are scaled (down) from the tile pixels by the a factor
- * from TileLayer - 2x for retina, 1x for non-retina.
- *
- * **NB.** twip to pixel mapping is made non-obvious by the need to ensure that
- * there are no cumulative rounding errors from TWIP heights to pixels. We 
have to
- * match the core here, so we just use pixels.
- */
-L.Control.Header.GapTickMap = L.Class.extend({
-
-       initialize: function (map, ticks, dimensionGeometry) {
-
-               if (dimensionGeometry) {
-                       // Until .uno:ViewRowColumnHeaders is not phased out, 
we need to live with
-                       // GapTickMap datastructure to avoid an invasive 
refactoring.
-                       // L.SheetGeometry and L.SheetDimension datastructures 
can directly provide
-                       // position/size of any row/column intuitively without 
using unnecessary
-                       // terminologies like (1-based) Gap and (0-based) Tick.
-                       var dimrange = dimensionGeometry.getViewElementRange();
-                       var start = Math.max(0, dimrange.start - 2);
-                       var startData = dimensionGeometry.getElementData(start);
-                       var startText = start ? start + 1 : 0;
-                       var endText = Math.min(dimensionGeometry.getMaxIndex(), 
dimrange.end + 2) + 1;
-
-                       this._minTickIdx = startText;
-                       this._maxTickIdx = endText;
-                       this._startOffset = start ? startData.startpos + 
startData.size : 0;
-                       this._tilePixelScale = 1; // We already have everything 
in css px.
-
-                       ticks = start ? [] : [0];
-                       dimensionGeometry.forEachInRange(start,
-                               this._maxTickIdx - 1, function (idx, data) {
-                                       ticks[idx + 1] = data.startpos + 
data.size;
-                               });
-
-                       this._ticks = ticks;
-
-                       return;
+L.Control.Header.HeaderInfo = L.Class.extend({
+
+       initialize: function (map, isCol) {
+               console.assert(map && isCol !== undefined, 'map and isCol 
required');
+               this._map = map;
+               this._isCol = isCol;
+               console.assert(this._map._docLayer.sheetGeometry, 'no sheet 
geometry data-structure found!');
+               var sheetGeom = this._map._docLayer.sheetGeometry;
+               this._dimGeom = this._isCol ? sheetGeom.getColumnsGeometry() : 
sheetGeom.getRowsGeometry();
+               this.update();
+       },
+
+       update: function () {
+               var bounds = this._map.getPixelBounds();
+               var startPx = this._isCol ? bounds.getTopLeft().x : 
bounds.getTopLeft().y;
+               this._docVisStart = startPx;
+               var endPx = this._isCol ? bounds.getBottomRight().x : 
bounds.getBottomRight().y;
+               var startIdx = this._dimGeom.getIndexFromPos(startPx, 
'csspixels');
+               var endIdx = this._dimGeom.getIndexFromPos(endPx - 1, 
'csspixels');
+               this._elements = [];
+
+               var splitPosContext = this._map.getSplitPanesContext();
+
+               this._hasSplits = false;
+               this._splitIndex = 0;
+               var splitPos = 0;
+
+               if (splitPosContext) {
+
+                       splitPos = this._isCol ? 
splitPosContext.getSplitPos().x : splitPosContext.getSplitPos().y;
+                       var splitIndex = this._dimGeom.getIndexFromPos(splitPos 
+ 1, 'csspixels');
+
+                       if (splitIndex) {
+                               // Make sure splitPos is aligned to the cell 
boundary.
+                               splitPos = 
this._dimGeom.getElementData(splitIndex).startpos;
+                               this._splitPos = splitPos;
+                               this._dimGeom.forEachInRange(0, splitIndex - 1,
+                                       function (idx, data) {
+                                               this._elements[idx] = {
+                                                       index: idx,
+                                                       pos: data.startpos + 
data.size, // end position on the header canvas
+                                                       size: data.size,
+                                                       origsize: data.size,
+                                               };
+                                       }.bind(this)
+                               );
+
+                               this._hasSplits = true;
+                               this._splitIndex = splitIndex;
+
+                               var freeStartPos = startPx + splitPos + 1;
+                               var freeStartIndex = 
this._dimGeom.getIndexFromPos(freeStartPos + 1, 'csspixels');
+
+                               startIdx = freeStartIndex;
+                       }
                }
 
-               var gapSize;
-               this._ticks = [];
+               // first free index
+               var dataFirstFree = this._dimGeom.getElementData(startIdx);
+               var firstFreeEnd = dataFirstFree.startpos + dataFirstFree.size 
- startPx;
+               var firstFreeStart = splitPos;
+               var firstFreeSize = Math.max(0, firstFreeEnd - firstFreeStart);
+               this._elements[startIdx] = {
+                       index: startIdx,
+                       pos: firstFreeEnd, // end position on the header canvas
+                       size: firstFreeSize,
+                       origsize: dataFirstFree.size,
+               };
 
-               // Sanitize input
-               var knownTicks = [];
-               for (var i in ticks) {
-                       // The field in the input data struct is called "text" 
but it's the
-                       // column/row index, as a string.
-                       // Idem for "size": it's the tick position in pixels, 
as a string
-                       knownTicks[ parseInt(ticks[i].text) ] = 
parseInt(ticks[i].size);
-               }
+               this._dimGeom.forEachInRange(startIdx + 1,
+                       endIdx, function (idx, data) {
+                               var startpos = data.startpos - startPx;
+                               var endpos = startpos + data.size;
+                               var size = endpos - startpos;
+                               this._elements[idx] = {
+                                       index: idx,
+                                       pos: endpos, // end position on the 
header canvas
+                                       size: size,
+                                       origsize: size,
+                               };
+                       }.bind(this));
 
-               // This *assumes* the input is ordered - i.e. tick indexes only 
grow
-               this._minTickIdx = parseInt(ticks[0].text);
-               this._maxTickIdx = knownTicks.length - 1;
-               this._startOffset = parseInt(ticks[0].size);
-               this._tilePixelScale = map._docLayer._tilePixelScale;
+               this._startIndex = startIdx;
+               this._endIndex = endIdx;
+       },
 
-               for (var idx=this._minTickIdx; idx <= this._maxTickIdx; idx++) {
+       docToHeaderPos: function (docPos) {
 
-                       if (idx in knownTicks) {
-                               this._ticks[idx] = knownTicks[idx];
-                               gapSize = this._ticks[idx] - this._ticks[idx - 
1];
-                       } else {
-                               if (isNaN(gapSize) || gapSize < 0) {
-                                       // This should never happen, unless 
data from the UNO message
-                                       // is not in strictly increasing order, 
or the first two ticks
-                                       // are not consecutive.
-                                       throw new Error('Malformed data for 
column/row sizes.');
-                               }
-                               this._ticks[idx] = this._ticks[idx - 1] + 
gapSize;
-                       }
+               if (!this._hasSplits) {
+                       return docPos - this._docVisStart;
                }
+
+               if (docPos <= this._splitPos) {
+                       return docPos;
+               }
+
+               // max here is to prevent encroachment of the fixed pane-area.
+               return Math.max(docPos - this._docVisStart, this._splitPos);
        },
 
-       // Gets the position of the i-th tick (or `undefined` if the index 
falls outside).
-       getTick: function getTick(i) {
-               // to get CSS pixels we need to adjust for DPI scale
-               // since we render at full native pixel resolution &
-               // account in those units.
-               return this._ticks[i] / this._tilePixelScale;
+       headerToDocPos: function (hdrPos) {
+               if (!this._hasSplits) {
+                       return hdrPos + this._docVisStart;
+               }
+
+               if (hdrPos <= this._splitPos) {
+                       return hdrPos;
+               }
+
+               return hdrPos + this._docVisStart;
        },
 
        getStartOffset: function() {
-               return this._startOffset / this._tilePixelScale;
+               return 0;
        },
 
-       getMinTickIdx: function() {
-               return this._minTickIdx;
+       isZeroSize: function (i) {
+               var elem = this._elements[i];
+               console.assert(elem, 'queried a non existent row/col in the 
header : ' + i);
+               return elem.size === 0;
        },
-       getMaxTickIdx: function() {
-               return this._maxTickIdx;
+
+       hasSplits: function () {
+               return this._hasSplits;
        },
 
-       // Gets the start and size of the i-th gap.
-       // returns an Object of the form {index: i, pos: start, size: 
width/height },
-       // or `undefined` if the index falls outside.
-       getGap: function getGap(i) {
-               var start = this.getTick(i-1);
-               var end = this.getTick(i);
+       // Index after the split.
+       getSplitIndex: function () {
+               return this._splitIndex;
+       },
 
-               if (start === undefined || end === undefined || isNaN(start) || 
isNaN(end)) {
-                       return undefined;
-               }
+       getStartIndex: function () {
+               return this._startIndex;
+       },
 
-               return {
-                       index: i,
-                       start: start,
-                       end: end,
-                       size: end - start,
-                       pos: end,
-               };
+       getEndIndex: function () {
+               return this._endIndex;
        },
 
-       // Returns true when the i-th gap has zero size.
-       isZeroSize: function isZeroSize(i) {
-               return this.getGap(i).size === 0;
+       getMinIndex: function () {
+               return this._hasSplits ? 0 : this._startIndex;
        },
 
-       // Runs the given callback function for each tick
-       // The callback function receives two parameters: the tick index and the
-       // (interpolated) tick position
-       forEachTick: function forEachTick(callback) {
-               for (var idx=this._minTickIdx; idx <= this._maxTickIdx; idx++) {
-                       if (callback(idx, this.getTick(idx)))
-                               break;
+       getMaxIndex: function () {
+               return this._endIndex;
+       },
+
+       getElementData: function (index) {
+               return this._elements[index];
+       },
+
+       getRowData: function (index) {
+               console.assert(!this._isCol, 'this is a column header 
instance!');
+               return this.getElementData(index);
+       },
+
+       getColData: function (index) {
+               console.assert(this._isCol, 'this is a row header instance!');
+               return this.getElementData(index);
+       },
+
+       getPreviousIndex: function (index) {
+
+               var prevIndex;
+               if (this._splitIndex && index === this._startIndex) {
+                       prevIndex = this._splitIndex - 1;
                }
+               else {
+                       prevIndex = index - 1;
+               }
+
+               return prevIndex;
        },
 
-       // Runs the given callback function for each gap
-       // The callback receives one parameter, in the same format as the 
return value
-       // of getGap()
-       forEachGap: function forEachGap(callback) {
-               for (var idx=this._minTickIdx; idx < this._maxTickIdx; idx++) {
-                       if (callback(this.getGap(idx+1)))
-                               break;
+       getNextIndex: function (index) {
+
+               var nextIndex;
+               if (this._splitIndex && index === (this._splitIndex - 1)) {
+                       nextIndex = this._startIndex;
+               }
+               else {
+                       nextIndex = index + 1;
+               }
+
+               return nextIndex;
+       },
+
+       forEachElement: function (callback) {
+               var idx;
+               if (this._hasSplits) {
+                       for (idx = 0; idx < this._splitIndex; ++idx) {
+                               console.assert(this._elements[idx], 
'forEachElement failed');
+                               if (callback(this._elements[idx])) {
+                                       return;
+                               }
+                       }
+               }
+               for (idx = this._startIndex; idx <= this._endIndex; ++idx) {
+                       console.assert(this._elements[idx], 'forEachElement 
failed');
+                       if (callback(this._elements[idx])) {
+                               return;
+                       }
                }
        },
 
diff --git a/loleaflet/src/control/Control.RowHeader.js 
b/loleaflet/src/control/Control.RowHeader.js
index 820f2205b..2e55fc4b7 100644
--- a/loleaflet/src/control/Control.RowHeader.js
+++ b/loleaflet/src/control/Control.RowHeader.js
@@ -11,6 +11,7 @@ L.Control.RowHeader = L.Control.Header.extend({
 
        onAdd: function (map) {
                map.on('updatepermission', this._onUpdatePermission, this);
+               map.on('moveend zoomchanged sheetgeometrychanged 
splitposchanged', this._updateCanvas, this);
                this._initialized = false;
        },
 
@@ -152,6 +153,13 @@ L.Control.RowHeader = L.Control.Header.extend({
                this._map.sendUnoCommand('.uno:ShowRow');
        },
 
+       _updateCanvas: function () {
+               if (this._headerInfo) {
+                       this._headerInfo.update();
+                       this._redrawHeaders();
+               }
+       },
+
        setScrollPosition: function (e) {
                var position = -e.y;
                this._position = Math.min(0, position);
@@ -179,7 +187,7 @@ L.Control.RowHeader = L.Control.Header.extend({
        },
 
        _onUpdateCurrentRow: function (e) {
-               var y = e.curY;
+               var y = e.curY - 1; // 1-based to 0-based.
                var h = this._twipsToPixels(e.height);
                var slim = h <= 1;
                this.updateCurrent(y, slim);
@@ -194,10 +202,10 @@ L.Control.RowHeader = L.Control.Header.extend({
                        return;
 
                var ctx = this._canvasContext;
-               var content = entry.index;
+               var content = entry.index + 1;
                var startOrt = this._canvasWidth - this._headerWidth;
-               var startPar = entry.pos - entry.size - this._startOffset;
-               var endPar = entry.pos - this._startOffset;
+               var startPar = entry.pos - entry.size;
+               var endPar = entry.pos;
                var height = endPar - startPar;
                var width = this._headerWidth;
 
@@ -211,7 +219,6 @@ L.Control.RowHeader = L.Control.Header.extend({
                ctx.save();
                var scale = L.getDpiScaleFactor();
                ctx.scale(scale, scale);
-               ctx.translate(0, this._position + this._startOffset);
                // background gradient
                var selectionBackgroundGradient = null;
                if (isHighlighted) {
@@ -268,14 +275,13 @@ L.Control.RowHeader = L.Control.Header.extend({
                var level = group.level;
 
                var startOrt = spacing + (headSize + spacing) * level;
-               var startPar = group.startPos - this._startOffset;
+               var startPar = this._headerInfo.docToHeaderPos(group.startPos);
                var height = group.endPos - group.startPos;
 
                ctx.save();
                var scale = L.getDpiScaleFactor();
                ctx.scale(scale, scale);
 
-               ctx.translate(0, this._position + this._startOffset);
                // clip mask
                ctx.beginPath();
                ctx.rect(startOrt, startPar, headSize, height);
@@ -347,15 +353,15 @@ L.Control.RowHeader = L.Control.Header.extend({
                var entry = this._mouseOverEntry;
 
                if (index)
-                       entry = this._tickMap.getGap(index);
+                       entry = this._headerInfo.getRowData(index);
 
                if (!entry)
                        return;
 
                var rect = this._canvas.getBoundingClientRect();
 
-               var rowStart = entry.pos - entry.size + this._position;
-               var rowEnd = entry.pos + this._position;
+               var rowStart = entry.pos - entry.size;
+               var rowEnd = entry.pos;
 
                var left = rect.left;
                var right = rect.right;
@@ -397,11 +403,12 @@ L.Control.RowHeader = L.Control.Header.extend({
                }
 
                var sheetGeometry = this._map._docLayer.sheetGeometry;
-               var rowsGeometry = sheetGeometry ? 
sheetGeometry.getRowsGeometry() : undefined;
 
-               // create data structure for row heights
-               this._tickMap = new L.Control.Header.GapTickMap(this._map, 
rows, rowsGeometry);
-               this._startOffset = this._tickMap.getStartOffset();
+               if (!this._headerInfo) {
+                       // create data structure for row heights
+                       this._headerInfo = new 
L.Control.Header.HeaderInfo(this._map, false /* isCol */);
+                       this._map._rowHdr = this._headerInfo;
+               }
 
                // setup conversion routine
                this.converter = L.Util.bind(converter, context);
@@ -426,13 +433,7 @@ L.Control.RowHeader = L.Control.Header.extend({
                        this.resize(this._headerWidth);
                }
 
-               // Initial draw
-               this._tickMap.forEachGap(function(gap) {
-                       this.drawHeaderEntry(gap, false);
-               }.bind(this));
-
-               // draw group controls
-               this.drawOutline();
+               this._redrawHeaders();
 
                this.mouseInit(canvas);
 
@@ -441,11 +442,21 @@ L.Control.RowHeader = L.Control.Header.extend({
                }
        },
 
+       _redrawHeaders: function () {
+               this._canvasContext.clearRect(0, 0, this._canvas.width, 
this._canvas.height);
+               this._headerInfo.forEachElement(function(elemData) {
+                       this.drawHeaderEntry(elemData, false);
+               }.bind(this));
+
+               // draw group controls
+               this.drawOutline();
+       },
+
        _selectRow: function(row, modifier) {
                var command = {
                        Row: {
                                type: 'long',
-                               value: row - 1
+                               value: row
                        },
                        Modifier: {
                                type: 'unsigned short',
@@ -505,11 +516,18 @@ L.Control.RowHeader = L.Control.Header.extend({
        },
 
        _getHorzLatLng: function (start, offset, e) {
-               var limit = this._map.mouseEventToContainerPoint({clientX: 
start.x, clientY: start.y});
+               var size = this._map.getSize();
                var drag = this._map.mouseEventToContainerPoint(e);
+               var entryStart = this._dragEntry.pos - this._dragEntry.size;
+               var ydocpos = this._headerInfo.headerToDocPos(Math.max(drag.y, 
entryStart));
+               var xmin = this._map.getPixelBounds().min.x;
+               var xmax = xmin + size.x;
+               if (this._headerInfo.hasSplits()) {
+                       xmin = 0;
+               }
                return [
-                       this._map.containerPointToLatLng(new L.Point(0, 
Math.max(limit.y, drag.y + offset.y))),
-                       this._map.containerPointToLatLng(new 
L.Point(this._map.getSize().x, Math.max(limit.y, drag.y + offset.y)))
+                       this._map.unproject(new L.Point(xmin, ydocpos)),
+                       this._map.unproject(new L.Point(xmax, ydocpos)),
                ];
        },
 
@@ -539,8 +557,9 @@ L.Control.RowHeader = L.Control.Header.extend({
                        var height = clickedRow.size;
                        var row = clickedRow.index;
 
-                       if (this._tickMap.isZeroSize(clickedRow.index + 1)) {
-                               row += 1;
+                       var nextRow = 
this._headerInfo.getNextIndex(clickedRow.index);
+                       if (this._headerInfo.isZeroSize(nextRow)) {
+                               row = nextRow;
                                height = 0;
                        }
 
@@ -552,7 +571,7 @@ L.Control.RowHeader = L.Control.Header.extend({
                                        },
                                        Row: {
                                                type: 'long',
-                                               value: row
+                                               value: row + 1 // core expects 
1-based index.
                                        }
                                };
 
@@ -574,7 +593,7 @@ L.Control.RowHeader = L.Control.Header.extend({
                        var command = {
                                Row: {
                                        type: 'long',
-                                       value: row - 1
+                                       value: row
                                },
                                Modifier: {
                                        type: 'unsigned short',
diff --git a/loleaflet/src/layer/CalcGridLines.js 
b/loleaflet/src/layer/CalcGridLines.js
index 37492b606..fce802101 100644
--- a/loleaflet/src/layer/CalcGridLines.js
+++ b/loleaflet/src/layer/CalcGridLines.js
@@ -72,23 +72,17 @@ L.CalcGridLines = L.LayerGroup.extend({
        // Redraw col/row lines whenever new information about them is 
available.
        // One websocket message might have info about cols, rows, or both
        onUpdate: function onUpdate(ev) {
-               var ticks;
+               var headerInfo, pos;
 
-               // Aux stuff to scale twips from the websocket message
-               // into map coordinate units
+               // Aux stuff to convert css pixels to map coordinate units
                var pixelToMapUnitRatio = 
this._map.options.crs.scale(this._map.getZoom());
 
-               var colDataInEvent = ev.data && ev.data.columns && 
ev.data.columns.length;
-               var rowDataInEvent = ev.data && ev.data.rows && 
ev.data.rows.length;
-
-               if (colDataInEvent || ev.updatecolumns) {
-                       var columnsData = colDataInEvent ? ev.data.columns : 
undefined;
-                       var columnsGeometry = colDataInEvent ? undefined : 
this._map._docLayer.sheetGeometry.getColumnsGeometry();
-                       ticks = new L.Control.Header.GapTickMap(this._map, 
columnsData, columnsGeometry);
+               if (ev.updatecolumns) {
+                       headerInfo = new L.Control.Header.HeaderInfo(this._map, 
true /* isCol */);
                        this._colLines.clearLayers();
 
-                       ticks.forEachTick(function(idx, pos) {
-                               pos /= pixelToMapUnitRatio;
+                       headerInfo.forEachElement(function(columnData) {
+                               pos = headerInfo.headerToDocPos(columnData.pos) 
/ pixelToMapUnitRatio;
                                this._colLines.addLayer(
                                        L.polyline([[[ L.Util.MIN_SAFE_INTEGER, 
pos ],[ L.Util.MAX_SAFE_INTEGER, pos ]]],
                                                this.options
@@ -97,14 +91,12 @@ L.CalcGridLines = L.LayerGroup.extend({
                        }.bind(this));
                }
 
-               if (rowDataInEvent || ev.updaterows) {
-                       var rowsData = rowDataInEvent ? ev.data.rows : 
undefined;
-                       var rowsGeometry = rowDataInEvent ? undefined : 
this._map._docLayer.sheetGeometry.getRowsGeometry();
-                       ticks = new L.Control.Header.GapTickMap(this._map, 
rowsData, rowsGeometry);
+               if (ev.updaterows) {
+                       headerInfo = new L.Control.Header.HeaderInfo(this._map, 
false /* isCol */);
                        this._rowLines.clearLayers();
 
-                       ticks.forEachTick(function(idx, pos) {
-                               pos /= pixelToMapUnitRatio;
+                       headerInfo.forEachElement(function(rowData) {
+                               pos = headerInfo.headerToDocPos(rowData.pos) / 
pixelToMapUnitRatio;
                                this._rowLines.addLayer(
                                        // Note that y-coordinates are 
inverted: Leaflet's CRS.Simple assumes
                                        // down = negative latlngs, whereas 
loolkit assumes down = positive twips
diff --git a/loleaflet/src/layer/tile/CalcTileLayer.js 
b/loleaflet/src/layer/tile/CalcTileLayer.js
index 4ff7b51ba..9d711b48f 100644
--- a/loleaflet/src/layer/tile/CalcTileLayer.js
+++ b/loleaflet/src/layer/tile/CalcTileLayer.js
@@ -449,6 +449,7 @@ L.CalcTileLayer = L.TileLayer.extend({
                }
                this._restrictDocumentSize();
                this._replayPrintTwipsMsgs();
+               this._map.fire('zoomchanged');
                this.refreshViewData();
                this._map._socket.sendMessage('commandvalues 
command=.uno:ViewAnnotationsPosition');
        },
@@ -719,6 +720,8 @@ L.CalcTileLayer = L.TileLayer.extend({
                        this._pixelsToTwips(this._map.getSize()));
                this._updateHeadersGridLines(undefined, true /* updateCols */,
                        true /* updateRows */);
+
+               this._map.fire('sheetgeometrychanged');
        },
 
        _onCommandValuesMsg: function (textMsg) {
_______________________________________________
Libreoffice-commits mailing list
libreoffice-comm...@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits

Reply via email to