Rebased ref, commits from common ancestor:
commit 258ea819be684e7de102fd8b3c0bc9fe4eb01b91
Author:     Dennis Francis <dennis.fran...@collabora.com>
AuthorDate: Sat May 16 18:36:04 2020 +0530
Commit:     Dennis Francis <dennis.fran...@collabora.com>
CommitDate: Sun May 17 17:34:22 2020 +0530

    Initialize outlines ds on empty outline
    
    Change-Id: If845991272a05f026fad7819d39ff4b23622abae

diff --git a/loleaflet/src/layer/tile/CalcTileLayer.js 
b/loleaflet/src/layer/tile/CalcTileLayer.js
index 272db0cb9..096ba66c4 100644
--- a/loleaflet/src/layer/tile/CalcTileLayer.js
+++ b/loleaflet/src/layer/tile/CalcTileLayer.js
@@ -1469,6 +1469,7 @@ L.DimensionOutlines = L.Class.extend({
                var levels = encoding.split(' ');
                if (levels.length < 2) {
                        // No outline.
+                       this._outlines = [];
                        return true;
                }
 
commit 65c255d8722bdb1dc1da068de2d9de51730f5749
Author:     Dennis Francis <dennis.fran...@collabora.com>
AuthorDate: Sat May 16 18:07:56 2020 +0530
Commit:     Dennis Francis <dennis.fran...@collabora.com>
CommitDate: Sun May 17 17:34:22 2020 +0530

    call refreshViewData() on setpart message...
    
    if the part is not hidden. The member _selectedPart is already set
    to the new part in Parts.js's setPart(), as a result the code inside
    the if was never getting executed. There is no need to call
    map.setPart() as this was also done in Parts.js setPart(), and finally
    there are no handler for 'setpart' event as of now, so lets remove the
    fire() call too. All of this was not a problem when the
    '.uno:ViewRowColumnHeader' data source was used, because that data
    was getting requested unintentionally as part of related scroll events
    during a sheet switch.
    
    Change-Id: I3ea3916ba738d9616e822659fc64903eda8f99cf

diff --git a/loleaflet/src/layer/tile/CalcTileLayer.js 
b/loleaflet/src/layer/tile/CalcTileLayer.js
index 2b29e734b..272db0cb9 100644
--- a/loleaflet/src/layer/tile/CalcTileLayer.js
+++ b/loleaflet/src/layer/tile/CalcTileLayer.js
@@ -364,9 +364,7 @@ L.CalcTileLayer = L.TileLayer.extend({
 
        _onSetPartMsg: function (textMsg) {
                var part = parseInt(textMsg.match(/\d+/g)[0]);
-               if (part !== this._selectedPart && !this.isHiddenPart(part)) {
-                       this._map.setPart(part, true);
-                       this._map.fire('setpart', {selectedPart: 
this._selectedPart});
+               if (!this.isHiddenPart(part)) {
                        this.refreshViewData(undefined, true /* 
sheetGeometryChanged */);
                }
        },
commit ac8f72f6cc14b89a5bb7cb2b5b2a670e5e96e1b1
Author:     Dennis Francis <dennis.fran...@collabora.com>
AuthorDate: Sat May 16 16:41:29 2020 +0530
Commit:     Dennis Francis <dennis.fran...@collabora.com>
CommitDate: Sun May 17 17:34:22 2020 +0530

    Add ability to get first match in binarySearch
    
    (more details in the comments)
    This can help in a corner case (very improbable though) when we query
    for the exact end-position of a span with trailing empty span.  Lets do
    the right thing, even if that does happen only extremely rarely.
    
    Change-Id: Ib1370811c257e6ef3d52d29caf2963641bad8e40

diff --git a/loleaflet/src/layer/tile/CalcTileLayer.js 
b/loleaflet/src/layer/tile/CalcTileLayer.js
index cfadbb2be..2b29e734b 100644
--- a/loleaflet/src/layer/tile/CalcTileLayer.js
+++ b/loleaflet/src/layer/tile/CalcTileLayer.js
@@ -1300,7 +1300,9 @@ L.SpanList = L.Class.extend({
                                }
                                return (testValue < valueStart) ? -1 :
                                        (valueEnd < testValue) ? 1 : 0;
-                       });
+                       }, true /* find the first match in case of duplicates 
*/);
+               // About the last argument: duplicates can happen, for instance 
if the
+               // custom field represents positions, and there are spans with 
zero sizes (hidden/filtered).
        }
 
 });
@@ -1582,6 +1584,9 @@ L.DimensionOutlines = L.Class.extend({
 // Of course, this assumes that the array is sorted (w.r.t to the semantics of
 // the directionProvider when it is provided).
 // It returns the index of the match if successful else returns -1.
+// 'firstMatch' if true, some additional work is done to ensure that the index 
of
+// the first match (from the 0 index of the array) is returned in case there 
are
+// duplicates.
 //
 // directionProvider will be provided the following parameters :
 // (key, previousArrayElement, currentArrayElement, nextArrayElement)
@@ -1592,19 +1597,21 @@ L.DimensionOutlines = L.Class.extend({
 //   1: to try searching upper half,
 //  -1: to try searching lower half
 
-function binarySearch(array, key, directionProvider) {
+function binarySearch(array, key, directionProvider, firstMatch) {
 
        if (!Array.isArray(array) || !array.length) {
                return -1;
        }
 
        if (typeof directionProvider != 'function') {
-               directionProvider = function (key, testvalue) {
+               directionProvider = function (key, prevvalue, testvalue) {
                        return (key === testvalue) ? 0 :
                                (key < testvalue) ? -1 : 1;
                };
        }
 
+       firstMatch = (firstMatch === true);
+
        var start = 0;
        var end = array.length - 1;
 
@@ -1616,7 +1623,12 @@ function binarySearch(array, key, directionProvider) {
 
        var endDir = directionProvider(key, array[end - 1], array[end]);
        if (endDir >= 0) {
-               return endDir ? -1 : end;
+
+               if (endDir === 1) {
+                       return -1;
+               }
+
+               return firstMatch ? _findFirstMatch(array, key, 
directionProvider, end) : end;
        }
 
        var mid = -1;
@@ -1637,5 +1649,22 @@ function binarySearch(array, key, directionProvider) {
                }
        }
 
-       return (start > end) ? -1 : mid;
+       return (start > end) ? -1 :
+               firstMatch ? _findFirstMatch(array, key, directionProvider, 
mid) : mid;
+}
+
+// Helper function for binarySearch().
+function _findFirstMatch(array, key, directionProvider, randomMatchingIndex) {
+
+       if (randomMatchingIndex === 0) {
+               return 0;
+       }
+
+       var index = randomMatchingIndex - 1;
+       while (index >= 0 && directionProvider(key,
+               array[index - 1], array[index], array[index + 1]) == 0) {
+               --index;
+       }
+
+       return index + 1;
 }
commit 8c477e2856ebb20631d830d6a90013a949075634
Author:     Dennis Francis <dennis.fran...@collabora.com>
AuthorDate: Sat May 16 15:09:51 2020 +0530
Commit:     Dennis Francis <dennis.fran...@collabora.com>
CommitDate: Sun May 17 17:34:22 2020 +0530

    Make the newly added interfaces more robust
    
    against wrong argument counts/types.
    
    Change-Id: Ibfed2ba4f3e907ef8a038a3b13b3081cc6248c20

diff --git a/loleaflet/src/layer/tile/CalcTileLayer.js 
b/loleaflet/src/layer/tile/CalcTileLayer.js
index 830c9084b..cfadbb2be 100644
--- a/loleaflet/src/layer/tile/CalcTileLayer.js
+++ b/loleaflet/src/layer/tile/CalcTileLayer.js
@@ -1193,6 +1193,10 @@ L.SpanList = L.Class.extend({
 
        addCustomDataForEachSpan: function (getCustomDataCallback) {
 
+               if (typeof getCustomDataCallback != 'function') {
+                       return;
+               }
+
                var prevIndex = -1;
                this._spanlist.forEach(function (span) {
                        span.data = getCustomDataCallback(
@@ -1203,6 +1207,11 @@ L.SpanList = L.Class.extend({
        },
 
        getSpanDataByIndex: function (index) {
+
+               if (typeof index != 'number') {
+                       return undefined;
+               }
+
                var spanid = this._searchByIndex(index);
                if (spanid == -1) {
                        return undefined;
@@ -1212,6 +1221,11 @@ L.SpanList = L.Class.extend({
        },
 
        getSpanDataByCustomDataField: function (value, fieldName) {
+
+               if (typeof value != 'number' || typeof fieldName != 'string' || 
!fieldName) {
+                       return undefined;
+               }
+
                var spanid = this._searchByCustomDataField(value, fieldName);
                if (spanid == -1) {
                        return undefined;
@@ -1222,7 +1236,8 @@ L.SpanList = L.Class.extend({
 
        forEachSpanInRange: function (start, end, callback) {
 
-               if (start > end) {
+               if (typeof start != 'number' || typeof end != 'number' ||
+                       typeof callback != 'function' || start > end) {
                        return;
                }
 
@@ -1279,6 +1294,10 @@ L.SpanList = L.Class.extend({
                                var valueStart = prevSpan ?
                                        prevSpan.data[fieldName] + 1 : 0;
                                var valueEnd = curSpan.data[fieldName];
+                               if (valueStart === undefined || valueEnd === 
undefined) {
+                                       // fieldName not present in the 'data' 
property.
+                                       return -1;
+                               }
                                return (testValue < valueStart) ? -1 :
                                        (valueEnd < testValue) ? 1 : 0;
                        });
@@ -1501,7 +1520,7 @@ L.DimensionOutlines = L.Class.extend({
        // 'callback' is called with these parameters : (levelIdx, groupIdx, 
groupStart, groupEnd, groupHidden).
        forEachGroupInRange: function (start, end, callback) {
 
-               if (start === undefined || end === undefined || callback === 
undefined) {
+               if (typeof start != 'number' || typeof end != 'number' || 
typeof callback != 'function') {
                        return;
                }
 
@@ -1575,11 +1594,11 @@ L.DimensionOutlines = L.Class.extend({
 
 function binarySearch(array, key, directionProvider) {
 
-       if (array === undefined || !array.length) {
+       if (!Array.isArray(array) || !array.length) {
                return -1;
        }
 
-       if (directionProvider === undefined) {
+       if (typeof directionProvider != 'function') {
                directionProvider = function (key, testvalue) {
                        return (key === testvalue) ? 0 :
                                (key < testvalue) ? -1 : 1;
commit 7bafaf13697e91d1614b95c7432958f5cc585886
Author:     Dennis Francis <dennis.fran...@collabora.com>
AuthorDate: Sat May 16 14:12:10 2020 +0530
Commit:     Dennis Francis <dennis.fran...@collabora.com>
CommitDate: Sun May 17 17:34:21 2020 +0530

    Reuse binarySearch routine in L.SpanList search functions
    
    Change-Id: I2f5411ef4da6b236978c08e3ea03ee05f9c8dabc

diff --git a/loleaflet/src/layer/tile/CalcTileLayer.js 
b/loleaflet/src/layer/tile/CalcTileLayer.js
index 361dbe120..830c9084b 100644
--- a/loleaflet/src/layer/tile/CalcTileLayer.js
+++ b/loleaflet/src/layer/tile/CalcTileLayer.js
@@ -1259,61 +1259,29 @@ L.SpanList = L.Class.extend({
 
        _searchByIndex: function (index) {
 
-               if (index < 0 || index > this._spanlist[this._spanlist.length - 
1].index) {
-                       return -1;
-               }
-
-               var start = 0;
-               var end = this._spanlist.length - 1;
-               var mid = -1;
-               while (start <= end) {
-                       mid = Math.round((start + end) / 2);
-                       var spanstart = mid ? this._spanlist[mid - 1].index + 1 
: 0;
-                       var spanend = this._spanlist[mid].index;
-                       if (spanstart <= index && index <= spanend) {
-                               break;
-                       }
-
-                       if (index < spanstart) {
-                               end = mid - 1;
-                       }
-                       else { // spanend < index
-                               start = mid + 1;
-                       }
-               }
-
-               return mid;
+               return binarySearch(this._spanlist, index,
+                       function directionProvider(testIndex, prevSpan, 
curSpan) {
+                               var spanStart = prevSpan ?
+                                       prevSpan.index + 1 : 0;
+                               var spanEnd = curSpan.index;
+                               return (testIndex < spanStart) ? -1 :
+                                       (spanEnd < testIndex) ? 1 : 0;
+                       });
        },
 
        _searchByCustomDataField: function (value, fieldName) {
 
-               // All custom searchable data values are assumed to start from 
0 at the start of first span.
-               var maxValue = this._spanlist[this._spanlist.length - 
1].data[fieldName];
-               if (value < 0 || value > maxValue) {
-                       return -1;
-               }
+               // All custom searchable data values are assumed to start
+               // from 0 at the start of first span and are in non-decreasing 
order.
 
-               var start = 0;
-               var end = this._spanlist.length - 1;
-               var mid = -1;
-               while (start <= end) {
-                       mid = Math.round((start + end) / 2);
-                       var valuestart = mid ? this._spanlist[mid - 
1].data[fieldName] + 1 : 0;
-                       var valueend = this._spanlist[mid].data[fieldName];
-                       if (valuestart <= value && value <= valueend) {
-                               break;
-                       }
-
-                       if (value < valuestart) {
-                               end = mid - 1;
-                       }
-                       else { // valueend < value
-                               start = mid + 1;
-                       }
-               }
-
-               // may fail for custom data ?
-               return (start <= end) ? mid : -1;
+               return binarySearch(this._spanlist, value,
+                       function directionProvider(testValue, prevSpan, 
curSpan) {
+                               var valueStart = prevSpan ?
+                                       prevSpan.data[fieldName] + 1 : 0;
+                               var valueEnd = curSpan.data[fieldName];
+                               return (testValue < valueStart) ? -1 :
+                                       (valueEnd < testValue) ? 1 : 0;
+                       });
        }
 
 });
commit 80c380ae028ded7622db1f2dbe555555a5d6f7dc
Author:     Dennis Francis <dennis.fran...@collabora.com>
AuthorDate: Sat May 16 11:14:17 2020 +0530
Commit:     Dennis Francis <dennis.fran...@collabora.com>
CommitDate: Sun May 17 17:34:21 2020 +0530

    Restore the scope of 'offset' tampering code
    
    as it was before 317afcecb4
    
    This 'adjustment' was just meant for refreshViewData() or whatever it
    was called before, to indicate that both column/row headers/gridlines
    should be updated, no matter what the actual offset is (probably only
    meant for zoom changes?). The offset passed to refreshViewData is only
    going to be used as a boolean flag.
    
    This patch fixes the row/col headers getting a off-by-one pixel when
    changing zooms with the new data-source (.uno:SheetGeometryData). If
    using the older source (.uno:ViewRowColumnHeader), this bug is hidden
    because of the delay for fetching the JSON everytime before painting the
    headers.
    
    TODO: Refactor all calls of refreshViewData to get rid of the 'offset'
    and this adjustment code and only send the boolean flags to
    refreshViewData().
    
    Change-Id: I4c30e8f06a6a2d58b9a9a89e283d7a214d00b99c

diff --git a/loleaflet/src/control/Control.Scroll.js 
b/loleaflet/src/control/Control.Scroll.js
index 36eed31fe..0cde2b4c9 100644
--- a/loleaflet/src/control/Control.Scroll.js
+++ b/loleaflet/src/control/Control.Scroll.js
@@ -259,15 +259,21 @@ L.Control.Scroll = L.Control.extend({
                // used on window resize
                // also when dragging
                var offset = new L.Point(e.x - this._prevScrollX, e.y - 
this._prevScrollY);
-               if (offset.x === 0) {
-                       offset.x = 1;
-               }
-               if (offset.y === 0) {
-                       offset.y = 1;
-               }
 
                this._map.fire('scrolloffset', offset);
                if (e.updateHeaders && this._map._docLayer._docType === 
'spreadsheet') {
+                       // This adjustment was just meant for refreshViewData()
+                       // to indicate that both column/row headers/gridlines
+                       // should be updated, no matter what the actual offset
+                       // is (unsure why).
+                       // TODO: Get rid of the 'offset' adjustment and
+                       // only send boolean flags to refreshViewData().
+                       if (offset.x === 0) {
+                               offset.x = 1;
+                       }
+                       if (offset.y === 0) {
+                               offset.y = 1;
+                       }
                        this._map._docLayer.refreshViewData({x: e.x, y: e.y, 
offset: offset});
                }
 
commit e512bb601378c43bb66124e2c282e54fa6a376db
Author:     Dennis Francis <dennis.fran...@collabora.com>
AuthorDate: Fri May 15 23:12:16 2020 +0530
Commit:     Dennis Francis <dennis.fran...@collabora.com>
CommitDate: Sun May 17 17:34:21 2020 +0530

    enforce bound-checks on setViewArea()
    
    Change-Id: Ic129181095c301ce27421bac5d3f1f94d1932248

diff --git a/loleaflet/src/layer/tile/CalcTileLayer.js 
b/loleaflet/src/layer/tile/CalcTileLayer.js
index d7218b458..361dbe120 100644
--- a/loleaflet/src/layer/tile/CalcTileLayer.js
+++ b/loleaflet/src/layer/tile/CalcTileLayer.js
@@ -1040,6 +1040,10 @@ L.SheetDimension = L.Class.extend({
        // computes element index from tile-twips position.
        _getIndexFromTileTwipsPos: function (pos) {
                var span = this._visibleSizes.getSpanDataByCustomDataField(pos, 
'postiletwips');
+               if (span === undefined) {
+                       // enforce limits.
+                       return (pos >= 0) ? this._maxIndex : 0;
+               }
                var elementCount = span.end - span.start + 1;
                var posStart = ((span.data.posdevpx - span.data.sizedev * 
elementCount) /
                        this._devPixelsPerCssPixel * this._twipsPerCSSPixel);
commit 4d7d0ae9632d50347a63f6dd94af0930ea3c4085
Author:     Dennis Francis <dennis.fran...@collabora.com>
AuthorDate: Fri May 15 22:43:19 2020 +0530
Commit:     Dennis Francis <dennis.fran...@collabora.com>
CommitDate: Sun May 17 17:34:21 2020 +0530

    call refreshViewData after 'scrolloffset' is fired
    
    otherwise the header controls won't have the right position info
    when refreshViewData causes an ~immediate header/gridline rendering
    (.uno:SheetGeometryData source). This was not a problem in case of
    .uno:ViewRowColumnHeader source, because of the roundtrip delay for
    getting the msg from core.
    
    Change-Id: I48298dbfb8d62acc64adbfd662a5304b856d702a

diff --git a/loleaflet/src/control/Control.Scroll.js 
b/loleaflet/src/control/Control.Scroll.js
index cb323e1c7..36eed31fe 100644
--- a/loleaflet/src/control/Control.Scroll.js
+++ b/loleaflet/src/control/Control.Scroll.js
@@ -114,11 +114,10 @@ L.Control.Scroll = L.Control.extend({
                        return;
                }
 
-               this._map._docLayer.refreshViewData({ x: newLeft, y: newTop, 
offset: offset});
-
                this._prevScrollY = newTop;
                this._prevScrollX = newLeft;
                this._map.fire('scrolloffset', offset);
+               this._map._docLayer.refreshViewData({ x: newLeft, y: newTop, 
offset: offset});
                this._map.scroll(offset.x, offset.y);
        },
 
@@ -266,10 +265,12 @@ L.Control.Scroll = L.Control.extend({
                if (offset.y === 0) {
                        offset.y = 1;
                }
+
+               this._map.fire('scrolloffset', offset);
                if (e.updateHeaders && this._map._docLayer._docType === 
'spreadsheet') {
                        this._map._docLayer.refreshViewData({x: e.x, y: e.y, 
offset: offset});
                }
-               this._map.fire('scrolloffset', offset);
+
                this._ignoreScroll = null;
                $('.scroll-container').mCustomScrollbar('stop');
                this._prevScrollY = e.y;
commit 8cec83121c83bdb6928afe4cd4c8a3ca3580cff2
Author:     Dennis Francis <dennis.fran...@collabora.com>
AuthorDate: Fri May 15 22:05:44 2020 +0530
Commit:     Dennis Francis <dennis.fran...@collabora.com>
CommitDate: Sun May 17 17:34:21 2020 +0530

    Don't ask core for sheet-geometry data for invalidateheader msgs
    
    We may need to have a dedicated sheetgeometrychanged msg for geometry
    changes like change of col/row sizes, hidden/filtered, groups/outline
    states.
    
    Change-Id: I45a8038546c66797aed4b58f11c6450cbe6e2965

diff --git a/loleaflet/src/layer/tile/CalcTileLayer.js 
b/loleaflet/src/layer/tile/CalcTileLayer.js
index 37a08755e..d7218b458 100644
--- a/loleaflet/src/layer/tile/CalcTileLayer.js
+++ b/loleaflet/src/layer/tile/CalcTileLayer.js
@@ -269,15 +269,15 @@ L.CalcTileLayer = L.TileLayer.extend({
                        }
                } else if (textMsg.startsWith('invalidateheader: column')) {
                        this.refreshViewData({x: 
this._map._getTopLeftPoint().x, y: 0,
-                               offset: {x: undefined, y: 0}}, true /* 
sheetGeometryChanged */);
+                               offset: {x: undefined, y: 0}});
                        this._map._socket.sendMessage('commandvalues 
command=.uno:ViewAnnotationsPosition');
                } else if (textMsg.startsWith('invalidateheader: row')) {
                        this.refreshViewData({x: 0, y: 
this._map._getTopLeftPoint().y,
-                               offset: {x: 0, y: undefined}}, true /* 
sheetGeometryChanged */);
+                               offset: {x: 0, y: undefined}});
                        this._map._socket.sendMessage('commandvalues 
command=.uno:ViewAnnotationsPosition');
                } else if (textMsg.startsWith('invalidateheader: all')) {
                        this.refreshViewData({x: 
this._map._getTopLeftPoint().x, y: this._map._getTopLeftPoint().y,
-                               offset: {x: undefined, y: undefined}}, true /* 
sheetGeometryChanged */);
+                               offset: {x: undefined, y: undefined}});
                        this._map._socket.sendMessage('commandvalues 
command=.uno:ViewAnnotationsPosition');
                } else {
                        L.TileLayer.prototype._onMessage.call(this, textMsg, 
img);
commit 45671770435bec356555d089478b9f49ecd71d4f
Author:     Dennis Francis <dennis.fran...@collabora.com>
AuthorDate: Fri May 15 08:12:03 2020 +0530
Commit:     Dennis Francis <dennis.fran...@collabora.com>
CommitDate: Sun May 17 17:34:21 2020 +0530

    use SheetGeometry data to draw headers/gridlines if enabled
    
    Change-Id: If146512a50c24f5fd81f6df7e0a3746f70bf21f9

diff --git a/loleaflet/src/control/Control.ColumnHeader.js 
b/loleaflet/src/control/Control.ColumnHeader.js
index a2846f78e..254a4df41 100644
--- a/loleaflet/src/control/Control.ColumnHeader.js
+++ b/loleaflet/src/control/Control.ColumnHeader.js
@@ -193,7 +193,7 @@ L.Control.ColumnHeader = L.Control.Header.extend({
        },
 
        _updateColumnHeader: function () {
-               this._map._docLayer.requestViewRowColumnData({x: 
this._map._getTopLeftPoint().x, y: 0, offset: {x: undefined, y: 0}});
+               this._map._docLayer.refreshViewData({x: 
this._map._getTopLeftPoint().x, y: 0, offset: {x: undefined, y: 0}});
        },
 
        drawHeaderEntry: function (entry, isOver, isHighlighted, isCurrent) {
@@ -376,8 +376,10 @@ L.Control.ColumnHeader = L.Control.Header.extend({
        },
 
        viewRowColumnHeaders: function (e) {
-               if (e.data.columns && e.data.columns.length > 0) {
-                       this.fillColumns(e.data.columns, e.data.columnGroups, 
e.converter, e.context);
+               var dataInEvent = (e.data && e.data.columns && 
e.data.columns.length > 0);
+               if (dataInEvent || e.updatecolumns) {
+                       dataInEvent ? this.fillColumns(e.data.columns, 
e.data.columnGroups, e.converter, e.context) :
+                               this.fillColumns(undefined, undefined, 
e.converter, e.context);
                        this._onUpdateCurrentColumn(e.cursor);
                        if (e.selection && e.selection.hasSelection) {
                                this._onUpdateSelection(e.selection);
@@ -389,7 +391,7 @@ L.Control.ColumnHeader = L.Control.Header.extend({
        },
 
        fillColumns: function (columns, colGroups, converter, context) {
-               if (columns.length < 2)
+               if (columns && columns.length < 2)
                        return;
 
                var canvas = this._canvas;
@@ -413,21 +415,28 @@ L.Control.ColumnHeader = L.Control.Header.extend({
                        this._lastMouseOverIndex = undefined;
                }
 
+               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);
+               this._tickMap = new L.Control.Header.GapTickMap(this._map, 
columns, columnsGeometry);
                this._startOffset = this._tickMap.getStartOffset();
 
                // setup conversion routine
                this.converter = L.Util.bind(converter, context);
 
                // create group array
-               this._groupLevels = parseInt(columns[0].groupLevels);
+               this._groupLevels = columns ? parseInt(columns[0].groupLevels):
+                       sheetGeometry.getColumnGroupLevels();
                this._groups = this._groupLevels ? new Array(this._groupLevels) 
: null;
 
                // collect group controls data
                if (colGroups !== undefined && this._groups) {
                        this._collectGroupsData(colGroups);
                }
+               else if (sheetGeometry) {
+                       
this._collectGroupsData(sheetGeometry.getColumnGroupsDataInView());
+               }
 
                if (this._groups) {
                        this.resize(this._computeOutlineWidth() + 
this._borderWidth + this._headerHeight);
diff --git a/loleaflet/src/control/Control.Header.js 
b/loleaflet/src/control/Control.Header.js
index 5aeac06da..3ef4bb2e0 100644
--- a/loleaflet/src/control/Control.Header.js
+++ b/loleaflet/src/control/Control.Header.js
@@ -804,7 +804,35 @@ L.Control.Header.colHeaderHeight = undefined;
  */
 L.Control.Header.GapTickMap = L.Class.extend({
 
-       initialize: function (map, ticks) {
+       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;
+               }
 
                var gapSize;
                this._ticks = [];
diff --git a/loleaflet/src/control/Control.RowHeader.js 
b/loleaflet/src/control/Control.RowHeader.js
index afbba3c45..820f2205b 100644
--- a/loleaflet/src/control/Control.RowHeader.js
+++ b/loleaflet/src/control/Control.RowHeader.js
@@ -186,7 +186,7 @@ L.Control.RowHeader = L.Control.Header.extend({
        },
 
        _updateRowHeader: function () {
-               this._map._docLayer.requestViewRowColumnData({x: 0, y: 
this._map._getTopLeftPoint().y, offset: {x: 0, y: undefined}});
+               this._map._docLayer.refreshViewData({x: 0, y: 
this._map._getTopLeftPoint().y, offset: {x: 0, y: undefined}});
        },
 
        drawHeaderEntry: function (entry, isOver, isHighlighted, isCurrent) {
@@ -365,8 +365,10 @@ L.Control.RowHeader = L.Control.Header.extend({
        },
 
        viewRowColumnHeaders: function (e) {
-               if (e.data.rows && e.data.rows.length) {
-                       this.fillRows(e.data.rows, e.data.rowGroups, 
e.converter, e.context);
+               var dataInEvent = (e.data && e.data.rows && e.data.rows.length);
+               if (dataInEvent || e.updaterows) {
+                       dataInEvent ? this.fillRows(e.data.rows, 
e.data.rowGroups, e.converter, e.context) :
+                               this.fillRows(undefined, undefined, 
e.converter, e.context);
                        this._onUpdateCurrentRow(e.cursor);
                        if (e.selection && e.selection.hasSelection) {
                                this._onUpdateSelection(e.selection);
@@ -378,7 +380,7 @@ L.Control.RowHeader = L.Control.Header.extend({
        },
 
        fillRows: function (rows, rowGroups, converter, context) {
-               if (rows.length < 2)
+               if (rows && rows.length < 2)
                        return;
 
                var canvas = this._canvas;
@@ -394,21 +396,28 @@ L.Control.RowHeader = L.Control.Header.extend({
                        this._lastMouseOverIndex = undefined;
                }
 
+               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);
+               this._tickMap = new L.Control.Header.GapTickMap(this._map, 
rows, rowsGeometry);
                this._startOffset = this._tickMap.getStartOffset();
 
                // setup conversion routine
                this.converter = L.Util.bind(converter, context);
 
                // create group array
-               this._groupLevels = parseInt(rows[0].groupLevels);
+               this._groupLevels = rows ? parseInt(rows[0].groupLevels) :
+                       sheetGeometry.getRowGroupLevels();
                this._groups = this._groupLevels ? new Array(this._groupLevels) 
: null;
 
                // collect group controls data
                if (rowGroups !== undefined && this._groups) {
                        this._collectGroupsData(rowGroups);
                }
+               else if (sheetGeometry) {
+                       
this._collectGroupsData(sheetGeometry.getRowGroupsDataInView());
+               }
 
                if (this._groups) {
                        this.resize(this._computeOutlineWidth() + 
this._borderWidth + this._headerWidth);
diff --git a/loleaflet/src/control/Control.Scroll.js 
b/loleaflet/src/control/Control.Scroll.js
index e3f9cf4dc..cb323e1c7 100644
--- a/loleaflet/src/control/Control.Scroll.js
+++ b/loleaflet/src/control/Control.Scroll.js
@@ -114,7 +114,7 @@ L.Control.Scroll = L.Control.extend({
                        return;
                }
 
-               this._map._docLayer.requestViewRowColumnData({ x: newLeft, y: 
newTop, offset: offset});
+               this._map._docLayer.refreshViewData({ x: newLeft, y: newTop, 
offset: offset});
 
                this._prevScrollY = newTop;
                this._prevScrollX = newLeft;
@@ -267,7 +267,7 @@ L.Control.Scroll = L.Control.extend({
                        offset.y = 1;
                }
                if (e.updateHeaders && this._map._docLayer._docType === 
'spreadsheet') {
-                       this._map._docLayer.requestViewRowColumnData({x: e.x, 
y: e.y, offset: offset});
+                       this._map._docLayer.refreshViewData({x: e.x, y: e.y, 
offset: offset});
                }
                this._map.fire('scrolloffset', offset);
                this._ignoreScroll = null;
diff --git a/loleaflet/src/layer/CalcGridLines.js 
b/loleaflet/src/layer/CalcGridLines.js
index 4b4e58d3a..37492b606 100644
--- a/loleaflet/src/layer/CalcGridLines.js
+++ b/loleaflet/src/layer/CalcGridLines.js
@@ -78,8 +78,13 @@ L.CalcGridLines = L.LayerGroup.extend({
                // into map coordinate units
                var pixelToMapUnitRatio = 
this._map.options.crs.scale(this._map.getZoom());
 
-               if (ev.data.columns && ev.data.columns.length) {
-                       ticks = new L.Control.Header.GapTickMap(this._map, 
ev.data.columns);
+               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);
                        this._colLines.clearLayers();
 
                        ticks.forEachTick(function(idx, pos) {
@@ -92,8 +97,10 @@ L.CalcGridLines = L.LayerGroup.extend({
                        }.bind(this));
                }
 
-               if (ev.data.rows && ev.data.rows.length) {
-                       ticks = new L.Control.Header.GapTickMap(this._map, 
ev.data.rows);
+               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);
                        this._rowLines.clearLayers();
 
                        ticks.forEachTick(function(idx, pos) {
diff --git a/loleaflet/src/layer/tile/CalcTileLayer.js 
b/loleaflet/src/layer/tile/CalcTileLayer.js
index 6ed8ae4b4..37a08755e 100644
--- a/loleaflet/src/layer/tile/CalcTileLayer.js
+++ b/loleaflet/src/layer/tile/CalcTileLayer.js
@@ -5,6 +5,10 @@
 
 /* global */
 L.CalcTileLayer = L.TileLayer.extend({
+       options: {
+               sheetGeometryDataEnabled: true
+       },
+
        STD_EXTRA_WIDTH: 113, /* 2mm extra for optimal width,
                                                          * 0.1986cm with TeX 
points,
                                                          * 0.1993cm with PS 
points. */
@@ -264,13 +268,16 @@ L.CalcTileLayer = L.TileLayer.extend({
                                }
                        }
                } else if (textMsg.startsWith('invalidateheader: column')) {
-                       this.requestViewRowColumnData({x: 
this._map._getTopLeftPoint().x, y: 0, offset: {x: undefined, y: 0}});
+                       this.refreshViewData({x: 
this._map._getTopLeftPoint().x, y: 0,
+                               offset: {x: undefined, y: 0}}, true /* 
sheetGeometryChanged */);
                        this._map._socket.sendMessage('commandvalues 
command=.uno:ViewAnnotationsPosition');
                } else if (textMsg.startsWith('invalidateheader: row')) {
-                       this.requestViewRowColumnData({x: 0, y: 
this._map._getTopLeftPoint().y, offset: {x: 0, y: undefined}});
+                       this.refreshViewData({x: 0, y: 
this._map._getTopLeftPoint().y,
+                               offset: {x: 0, y: undefined}}, true /* 
sheetGeometryChanged */);
                        this._map._socket.sendMessage('commandvalues 
command=.uno:ViewAnnotationsPosition');
                } else if (textMsg.startsWith('invalidateheader: all')) {
-                       this.requestViewRowColumnData({x: 
this._map._getTopLeftPoint().x, y: this._map._getTopLeftPoint().y, offset: {x: 
undefined, y: undefined}});
+                       this.refreshViewData({x: 
this._map._getTopLeftPoint().x, y: this._map._getTopLeftPoint().y,
+                               offset: {x: undefined, y: undefined}}, true /* 
sheetGeometryChanged */);
                        this._map._socket.sendMessage('commandvalues 
command=.uno:ViewAnnotationsPosition');
                } else {
                        L.TileLayer.prototype._onMessage.call(this, textMsg, 
img);
@@ -360,15 +367,17 @@ L.CalcTileLayer = L.TileLayer.extend({
                if (part !== this._selectedPart && !this.isHiddenPart(part)) {
                        this._map.setPart(part, true);
                        this._map.fire('setpart', {selectedPart: 
this._selectedPart});
-                       // TODO: test it!
-                       this.requestViewRowColumnData();
+                       this.refreshViewData(undefined, true /* 
sheetGeometryChanged */);
                }
        },
 
        _onZoomRowColumns: function () {
                this._sendClientZoom();
-               // TODO: test it!
-               this.requestViewRowColumnData();
+               if (this.sheetGeometry) {
+                       
this.sheetGeometry.setTileGeometryData(this._tileWidthTwips, 
this._tileHeightTwips,
+                               this._tileSize, this._tilePixelScale);
+               }
+               this.refreshViewData();
                this._map._socket.sendMessage('commandvalues 
command=.uno:ViewAnnotationsPosition');
        },
 
@@ -454,8 +463,13 @@ L.CalcTileLayer = L.TileLayer.extend({
                }
        },
 
-       // This send .uno:ViewRowColumnHeaders command to core with the new 
view coordinates.
-       requestViewRowColumnData: function (coordinatesData) {
+       // This initiates a selective repainting of row/col headers and
+       // gridlines based on the settings of coordinatesData.offset. This
+       // should be called whenever the view area changes (scrolling, panning,
+       // zooming, cursor moving out of view-area etc.).  Depending on the
+       // active sheet geometry data-source, it may ask core to send current
+       // view area's data or the global data on geometry changes.
+       refreshViewData: function (coordinatesData, sheetGeometryChanged) {
 
                // There are places that call this function with no arguments 
to indicate that the
                // command arguments should be the current map area coordinates.
@@ -475,32 +489,57 @@ L.CalcTileLayer = L.TileLayer.extend({
                        topLeftPoint.y = this._map._getTopLeftPoint().y;
                }
 
+               var updateRows = true;
+               var updateCols = true;
+
                if (offset.x === 0) {
-                       topLeftPoint.x = -1;
-                       sizePx.x = 0;
+                       updateCols = false;
+                       if (!this.options.sheetGeometryDataEnabled) {
+                               topLeftPoint.x = -1;
+                               sizePx.x = 0;
+                       }
                }
                if (offset.y === 0) {
-                       topLeftPoint.y = -1;
-                       sizePx.y = 0;
+                       updateRows = false;
+                       if (!this.options.sheetGeometryDataEnabled) {
+                               topLeftPoint.y = -1;
+                               sizePx.y = 0;
+                       }
                }
 
                var pos = this._pixelsToTwips(topLeftPoint);
                var size = this._pixelsToTwips(sizePx);
-               var payload = 'commandvalues 
command=.uno:ViewRowColumnHeaders?x=' + Math.round(pos.x) + '&y=' + 
Math.round(pos.y) +
-                       '&width=' + Math.round(size.x) + '&height=' + 
Math.round(size.y);
 
-               if (coordinatesData.outline) {
-                       payload += '&columnOutline=' + 
coordinatesData.outline.column + '&groupLevel=' + coordinatesData.outline.level
-                               + '&groupIndex=' + 
coordinatesData.outline.index + '&groupHidden=' + 
coordinatesData.outline.hidden;
+               if (!this.options.sheetGeometryDataEnabled) {
+                       this.requestViewRowColumnData(pos, size);
+                       return;
+               }
+
+               if (sheetGeometryChanged || !this.sheetGeometry) {
+                       this.requestSheetGeometryData(
+                               {columns: updateCols, rows: updateRows});
+                       return;
                }
 
+               this.sheetGeometry.setViewArea(pos, size);
+               this._updateHeadersGridLines(undefined, updateCols, updateRows);
+       },
+
+       // This send .uno:ViewRowColumnHeaders command to core with the new 
view coordinates (tile-twips).
+       requestViewRowColumnData: function (pos, size) {
+
+               var payload = 'commandvalues 
command=.uno:ViewRowColumnHeaders?x=' + Math.round(pos.x) + '&y=' + 
Math.round(pos.y) +
+                       '&width=' + Math.round(size.x) + '&height=' + 
Math.round(size.y);
+
                this._map._socket.sendMessage(payload);
        },
 
        // sends the .uno:SheetGeometryData command optionally with arguments.
-       requestSheetGeomtryData: function (flags) {
+       requestSheetGeometryData: function (flags) {
                var unoCmd = '.uno:SheetGeometryData';
-               var haveArgs = (typeof flags == 'object' && (flags.columns === 
true || flags.rows === true));
+               var haveArgs = (typeof flags == 'object' &&
+                       (flags.columns === true || flags.rows === true) &&
+                       (flags.columns !== flags.rows));
                var payload = 'commandvalues command=' + unoCmd;
 
                if (haveArgs) {
@@ -533,9 +572,15 @@ L.CalcTileLayer = L.TileLayer.extend({
                this._map._socket.sendMessage(payload);
        },
 
-       _handleViewRowColumnHeadersMsg: function (jsonMsgObj) {
+       // Sends a notification to the row/col header and gridline controls that
+       // they need repainting.
+       // viewAreaData is the parsed .uno:ViewRowColumnHeaders JSON if that 
source is used.
+       // else it should be undefined.
+       _updateHeadersGridLines: function (viewAreaData, updateCols, 
updateRows) {
                this._map.fire('viewrowcolumnheaders', {
-                       data: jsonMsgObj,
+                       data: viewAreaData,
+                       updaterows: updateRows,
+                       updatecolumns: updateCols,
                        cursor: this._getCursorPosSize(),
                        selection: this._getSelectionHeaderData(),
                        converter: this._twipsToPixels,
@@ -544,8 +589,17 @@ L.CalcTileLayer = L.TileLayer.extend({
        },
 
        _handleSheetGeometryDataMsg: function (jsonMsgObj) {
-               // TODO: use the L.SheetGeometry datastructure
-               this._map.sheetGeomData = jsonMsgObj;
+               if (!this.sheetGeometry) {
+                       this.sheetGeometry = new L.SheetGeometry(jsonMsgObj,
+                               this._tileWidthTwips, this._tileHeightTwips,
+                               this._tileSize, this._tilePixelScale);
+               }
+
+               this.sheetGeometry.update(jsonMsgObj);
+               
this.sheetGeometry.setViewArea(this._pixelsToTwips(this._map._getTopLeftPoint()),
+                       this._pixelsToTwips(this._map.getSize()));
+               this._updateHeadersGridLines(undefined, true /* updateCols */,
+                       true /* updateRows */);
        },
 
        _onCommandValuesMsg: function (textMsg) {
@@ -560,7 +614,7 @@ L.CalcTileLayer = L.TileLayer.extend({
 
                var comment;
                if (values.commandName === '.uno:ViewRowColumnHeaders') {
-                       this._handleViewRowColumnHeadersMsg(values);
+                       this._updateHeadersGridLines(values);
 
                } else if (values.commandName === '.uno:SheetGeometryData') {
                        this._handleSheetGeometryDataMsg(values);
@@ -697,6 +751,14 @@ L.SheetGeometry = L.Class.extend({
                return true;
        },
 
+       getColumnsGeometry: function () {
+               return this._columns;
+       },
+
+       getRowsGeometry: function () {
+               return this._rows;
+       },
+
        // returns an object with keys 'start' and 'end' indicating the
        // column range in the current view area.
        getViewColumnRange: function () {
diff --git a/loleaflet/src/map/Map.js b/loleaflet/src/map/Map.js
index 8e05b5544..18dbe5b57 100644
--- a/loleaflet/src/map/Map.js
+++ b/loleaflet/src/map/Map.js
@@ -324,7 +324,7 @@ L.Map = L.Evented.extend({
                this._socket.sendMessage('commandvalues 
command=.uno:LanguageStatus');
                this._socket.sendMessage('commandvalues 
command=.uno:ViewAnnotations');
                if (this._docLayer._docType === 'spreadsheet') {
-                       this._docLayer.requestViewRowColumnData();
+                       this._docLayer.refreshViewData();
                }
                this._docLayer._getToolbarCommandsValues();
        },
commit 020e6ea6d3bc7c8de80f0a29e366d014a37d91c7
Author:     Dennis Francis <dennis.fran...@collabora.com>
AuthorDate: Thu May 14 23:11:07 2020 +0530
Commit:     Dennis Francis <dennis.fran...@collabora.com>
CommitDate: Sun May 17 17:34:21 2020 +0530

    Round down when computing row/col index from tile-twips position
    
    And lets not unnecessarily extend the cellrange in the view as the
    computation is accurate.
    
    Change-Id: I62de80ce42430c62a399d4e39bafab7896217bf1

diff --git a/loleaflet/src/layer/tile/CalcTileLayer.js 
b/loleaflet/src/layer/tile/CalcTileLayer.js
index 974c1daae..6ed8ae4b4 100644
--- a/loleaflet/src/layer/tile/CalcTileLayer.js
+++ b/loleaflet/src/layer/tile/CalcTileLayer.js
@@ -975,6 +975,7 @@ L.SheetDimension = L.Class.extend({
                });
        },
 
+       // computes element index from tile-twips position.
        _getIndexFromTileTwipsPos: function (pos) {
                var span = this._visibleSizes.getSpanDataByCustomDataField(pos, 
'postiletwips');
                var elementCount = span.end - span.start + 1;
@@ -982,16 +983,17 @@ L.SheetDimension = L.Class.extend({
                        this._devPixelsPerCssPixel * this._twipsPerCSSPixel);
                var posEnd = span.data.postiletwips;
                var sizeOne = (posEnd - posStart) / elementCount;
-               var relativeIndex = Math.round((pos - posStart) / sizeOne);
+
+               // always round down as relativeIndex is zero-based.
+               var relativeIndex = Math.floor((pos - posStart) / sizeOne);
 
                return span.start + relativeIndex;
        },
 
        setViewLimits: function (startPosTileTwips, endPosTileTwips) {
 
-               // Extend the range a bit, to compensate for rounding errors.
-               this._viewStartIndex = Math.max(0, 
this._getIndexFromTileTwipsPos(startPosTileTwips) - 2);
-               this._viewEndIndex = Math.min(this._maxIndex, 
this._getIndexFromTileTwipsPos(endPosTileTwips) + 2);
+               this._viewStartIndex = Math.max(0, 
this._getIndexFromTileTwipsPos(startPosTileTwips));
+               this._viewEndIndex = Math.min(this._maxIndex, 
this._getIndexFromTileTwipsPos(endPosTileTwips));
        },
 
        getViewElementRange: function () {
commit 039ee6c27e64ccfd0265d27b725535b5b130de63
Author:     Dennis Francis <dennis.fran...@collabora.com>
AuthorDate: Thu May 14 22:49:30 2020 +0530
Commit:     Dennis Francis <dennis.fran...@collabora.com>
CommitDate: Sun May 17 17:34:21 2020 +0530

    add table outline query interfaces
    
    Change-Id: Ibd26592e321af1dca5ee7ff369e58b1f51dc480b

diff --git a/loleaflet/src/layer/tile/CalcTileLayer.js 
b/loleaflet/src/layer/tile/CalcTileLayer.js
index b3b7c7037..974c1daae 100644
--- a/loleaflet/src/layer/tile/CalcTileLayer.js
+++ b/loleaflet/src/layer/tile/CalcTileLayer.js
@@ -841,7 +841,7 @@ L.SheetDimension = L.Class.extend({
                this._sizes = new L.SpanList();
                this._hidden = new L.BoolSpanList();
                this._filtered = new L.BoolSpanList();
-               this._groups = new L.DimensionOutlines();
+               this._outlines = new L.DimensionOutlines();
 
                // This is used to store the span-list of sizes
                // with hidden/filtered elements set to zero size.
@@ -876,7 +876,7 @@ L.SheetDimension = L.Class.extend({
                }
 
                if (jsonObject.hasOwnProperty('groups')) {
-                       thisLoadOK = this._groups.load(jsonObject.groups);
+                       thisLoadOK = this._outlines.load(jsonObject.groups);
                        loadsOK = loadsOK && thisLoadOK;
                }
 
@@ -939,25 +939,27 @@ L.SheetDimension = L.Class.extend({
                });
        },
 
-       getElementData: function (index) {
+       // returns the element pos/size in css pixels by default.
+       getElementData: function (index, useDevicePixels) {
                var span = this._visibleSizes.getSpanDataByIndex(index);
                if (span === undefined) {
                        return undefined;
                }
 
-               return this._getElementDataFromSpanByIndex(index, span);
+               return this._getElementDataFromSpanByIndex(index, span, 
useDevicePixels);
        },
 
-       _getElementDataFromSpanByIndex: function (index, span) {
+       // returns element pos/size in css pixels by default.
+       _getElementDataFromSpanByIndex: function (index, span, useDevicePixels) 
{
                if (span === undefined || index < span.start || span.end < 
index) {
                        return undefined;
                }
 
                var numSizes = span.end - index + 1;
-               // all in css pixels.
+               var pixelScale = useDevicePixels ? this._devPixelsPerCssPixel : 
1;
                return {
-                       startpos: (span.data.posdevpx - span.data.sizedev * 
numSizes) / this._devPixelsPerCssPixel,
-                       size: span.data.sizedev / this._devPixelsPerCssPixel
+                       startpos: (span.data.posdevpx - span.data.sizedev * 
numSizes) / pixelScale,
+                       size: span.data.sizedev / pixelScale
                };
        },
 
@@ -1432,16 +1434,19 @@ L.DimensionOutlines = L.Class.extend({
 
                                var olineEntry = {
                                        start: parseInt(entrySplits[0]),
-                                       size: parseInt(entrySplits[1]),
+                                       end: parseInt(entrySplits[1]), // this 
is size.
                                        hidden: parseInt(entrySplits[2]),
                                        visible: parseInt(entrySplits[3])
                                };
 
-                               if (isNaN(olineEntry.start) || 
isNaN(olineEntry.size) ||
+                               if (isNaN(olineEntry.start) || 
isNaN(olineEntry.end) ||
                                        isNaN(olineEntry.hidden) || 
isNaN(olineEntry.visible)) {
                                        return false;
                                }
 
+                               // correct the 'end' attribute.
+                               olineEntry.end += (olineEntry.start - 1);
+
                                collections.push(olineEntry);
                        }
 
@@ -1450,5 +1455,132 @@ L.DimensionOutlines = L.Class.extend({
 
                this._outlines = outlines;
                return true;
+       },
+
+       getLevels: function () {
+               return this._outlines.length;
+       },
+
+       // Calls 'callback' for all groups in all levels that have an 
intersection with the inclusive element range [start, end].
+       // 'callback' is called with these parameters : (levelIdx, groupIdx, 
groupStart, groupEnd, groupHidden).
+       forEachGroupInRange: function (start, end, callback) {
+
+               if (start === undefined || end === undefined || callback === 
undefined) {
+                       return;
+               }
+
+               if (!this._outlines.length || start > end) {
+                       return;
+               }
+
+               // Search direction provider for binarySearch().
+               // Here we want to find the first group after or intersects 
elementIdx.
+               // return value : 0 for match, -1 for "try previous entries", 
+1 for "try next entries".
+               var directionProvider = function (elementIdx, prevGroup, 
curGroup/*, nextGroup*/) {
+
+                       var direction = (elementIdx < curGroup.start) ? -1 :
+                               (curGroup.end < elementIdx) ? 1 : 0;
+
+                       if (direction >= 0) {
+                               return direction;
+                       }
+
+                       // If curGroup is the first one, or elementidx is after 
prevGroup's end, then it is a match.
+                       if (!prevGroup || (prevGroup.end < elementIdx)) {
+                               return 0;
+                       }
+
+                       return -1;
+               };
+
+               for (var levelIdx = this._outlines.length - 1; levelIdx >= 0; 
--levelIdx) {
+
+                       var groupsInLevel = this._outlines[levelIdx];
+                       // Find the first group after or that intersects 
'start'.
+                       var startGroupIdx = binarySearch(groupsInLevel, start, 
directionProvider);
+                       if (startGroupIdx == -1) {
+                               // All groups at this level are before 'start'.
+                               continue;
+                       }
+
+                       var startGroup = groupsInLevel[startGroupIdx];
+                       if (end < startGroup.start) {
+                               // No group at this level intersects the range 
[start, end].
+                               continue;
+                       }
+
+                       for (var groupIdx = startGroupIdx; groupIdx < 
groupsInLevel.length; ++groupIdx) {
+                               var group = groupsInLevel[groupIdx];
+                               if (end < group.start) {
+                                       continue;
+                               }
+
+                               callback(levelIdx, groupIdx, group.start,
+                                       group.end, group.hidden);
+                       }
+               }
        }
 });
+
+
+// Does binary search on array for key, possibly using a custom direction 
provider.
+// Of course, this assumes that the array is sorted (w.r.t to the semantics of
+// the directionProvider when it is provided).
+// It returns the index of the match if successful else returns -1.
+//
+// directionProvider will be provided the following parameters :
+// (key, previousArrayElement, currentArrayElement, nextArrayElement)
+// previousArrayElement and nextArrayElement can be undefined when
+// currentArrayElement is the first or the last element of the array
+// respectively. This function should return:
+//   0: for a match(to stop search),
+//   1: to try searching upper half,
+//  -1: to try searching lower half
+
+function binarySearch(array, key, directionProvider) {
+
+       if (array === undefined || !array.length) {
+               return -1;
+       }
+
+       if (directionProvider === undefined) {
+               directionProvider = function (key, testvalue) {
+                       return (key === testvalue) ? 0 :
+                               (key < testvalue) ? -1 : 1;
+               };
+       }
+
+       var start = 0;
+       var end = array.length - 1;
+
+       // Bound checks and early exit.
+       var startDir = directionProvider(key, undefined, array[0], array[1]);
+       if (startDir <= 0) {
+               return startDir;
+       }
+
+       var endDir = directionProvider(key, array[end - 1], array[end]);
+       if (endDir >= 0) {
+               return endDir ? -1 : end;
+       }
+
+       var mid = -1;
+       while (start <= end) {
+               mid = Math.round((start + end) / 2);
+               var direction = directionProvider(key, array[mid-1],
+                       array[mid], array[mid+1]);
+
+               if (direction == 0) {
+                       break;
+               }
+
+               if (direction == -1) {
+                       end = mid - 1;
+               }
+               else {
+                       start = mid + 1;
+               }
+       }
+
+       return (start > end) ? -1 : mid;
+}
commit f57358f8a6b25561b53071ac1889f9d721283127
Author:     Dennis Francis <dennis.fran...@collabora.com>
AuthorDate: Tue May 12 10:52:50 2020 +0530
Commit:     Dennis Francis <dennis.fran...@collabora.com>
CommitDate: Sun May 17 17:34:21 2020 +0530

    introduce .uno:SheetGeometryData requester/handler
    
    Change-Id: Iaeef462346f22996daddb583c459a5f67aa6c290

diff --git a/loleaflet/src/layer/tile/CalcTileLayer.js 
b/loleaflet/src/layer/tile/CalcTileLayer.js
index b0773251d..b3b7c7037 100644
--- a/loleaflet/src/layer/tile/CalcTileLayer.js
+++ b/loleaflet/src/layer/tile/CalcTileLayer.js
@@ -497,6 +497,42 @@ L.CalcTileLayer = L.TileLayer.extend({
                this._map._socket.sendMessage(payload);
        },
 
+       // sends the .uno:SheetGeometryData command optionally with arguments.
+       requestSheetGeomtryData: function (flags) {
+               var unoCmd = '.uno:SheetGeometryData';
+               var haveArgs = (typeof flags == 'object' && (flags.columns === 
true || flags.rows === true));
+               var payload = 'commandvalues command=' + unoCmd;
+
+               if (haveArgs) {
+                       var argList = [];
+                       if (flags.columns === true) {
+                               argList.push('columns=1');
+                       }
+                       if (flags.rows === true) {
+                               argList.push('rows=1');
+                       }
+
+                       var dataTypeFlagNames = ['sizes', 'hidden', 'filtered', 
'groups'];
+                       var dataTypesPresent = false;
+                       dataTypeFlagNames.forEach(function (name) {
+                               if (flags[name] === true) {
+                                       argList.push(name + '=1');
+                                       dataTypesPresent = true;
+                               }
+                       });
+
+                       if (!dataTypesPresent) {
+                               dataTypeFlagNames.forEach(function (name) {
+                                       argList.push(name + '=1');
+                               });
+                       }
+
+                       payload += '?' + argList.join('&');
+               }
+
+               this._map._socket.sendMessage(payload);
+       },
+
        _handleViewRowColumnHeadersMsg: function (jsonMsgObj) {
                this._map.fire('viewrowcolumnheaders', {
                        data: jsonMsgObj,
@@ -507,6 +543,11 @@ L.CalcTileLayer = L.TileLayer.extend({
                });
        },
 
+       _handleSheetGeometryDataMsg: function (jsonMsgObj) {
+               // TODO: use the L.SheetGeometry datastructure
+               this._map.sheetGeomData = jsonMsgObj;
+       },
+
        _onCommandValuesMsg: function (textMsg) {
                var jsonIdx = textMsg.indexOf('{');
                if (jsonIdx === -1)
@@ -521,6 +562,9 @@ L.CalcTileLayer = L.TileLayer.extend({
                if (values.commandName === '.uno:ViewRowColumnHeaders') {
                        this._handleViewRowColumnHeadersMsg(values);
 
+               } else if (values.commandName === '.uno:SheetGeometryData') {
+                       this._handleSheetGeometryDataMsg(values);
+
                } else if (values.comments) {
                        this.clearAnnotations();
                        for (var index in values.comments) {
commit db0af6af59731c070e41b051d7bd2f7dbcbcf9a0
Author:     Dennis Francis <dennis.fran...@collabora.com>
AuthorDate: Sat May 9 20:34:37 2020 +0530
Commit:     Dennis Francis <dennis.fran...@collabora.com>
CommitDate: Sun May 17 17:34:21 2020 +0530

    add sheet-geometry datastructures
    
    to parse, store the .uno:SheetGeometryData JSON efficiently although it
    is optimized for fast querying. L.SheetGeometry is the external class
    that exposes all necessary sheet query interfaces.
    
    Change-Id: I24df8d85734a6cdf9c393fd2c3c5ed4de0ea29f3

diff --git a/loleaflet/src/layer/tile/CalcTileLayer.js 
b/loleaflet/src/layer/tile/CalcTileLayer.js
index 06ffa2bef..b0773251d 100644
--- a/loleaflet/src/layer/tile/CalcTileLayer.js
+++ b/loleaflet/src/layer/tile/CalcTileLayer.js
@@ -567,3 +567,844 @@ L.CalcTileLayer = L.TileLayer.extend({
                this._onUpdateCurrentHeader();
        }
 });
+
+
+// TODO: Move these somewhere more appropriate.
+
+// Sheet geometry data
+L.SheetGeometry = L.Class.extend({
+
+       // sheetGeomJSON is expected to be the parsed JSON message from core
+       // in response to client command '.uno:SheetGeometryData' with
+       // all flags (ie 'columns', 'rows', 'sizes', 'hidden', 'filtered',
+       // 'groups') enabled.
+       initialize: function (sheetGeomJSON, tileWidthTwips, tileHeightTwips,
+               tileSizeCSSPixels, dpiScale) {
+
+               if (typeof sheetGeomJSON !== 'object' ||
+                       typeof tileWidthTwips !== 'number' ||
+                       typeof tileHeightTwips !== 'number' ||
+                       typeof tileSizeCSSPixels !== 'number' ||
+                       typeof dpiScale !== 'number') {
+                       console.error('Incorrect constructor argument types or 
missing required arguments');
+                       return;
+               }
+
+               this._columns = new L.SheetDimension();
+               this._rows = new L.SheetDimension();
+               this._unoCommand = '.uno:SheetGeometryData';
+
+               // Set various unit conversion info early on because on 
update() call below, these info are needed.
+               this.setTileGeometryData(tileWidthTwips, tileHeightTwips, 
tileSizeCSSPixels,
+                       dpiScale, false /* update position info ?*/);
+
+               this.update(sheetGeomJSON, /* checkCompleteness */ true);
+       },
+
+       update: function (sheetGeomJSON, checkCompleteness) {
+
+               if (!this._testValidity(sheetGeomJSON, checkCompleteness)) {
+                       return false;
+               }
+
+               var updateOK = true;
+               if (sheetGeomJSON.columns) {
+                       if (!this._columns.update(sheetGeomJSON.columns)) {
+                               console.error(this._unoCommand + ': columns 
update failed.');
+                               updateOK = false;
+                       }
+               }
+
+               if (sheetGeomJSON.rows) {
+                       if (!this._rows.update(sheetGeomJSON.rows)) {
+                               console.error(this._unoCommand + ': rows update 
failed.');
+                               updateOK = false;
+                       }
+               }
+
+               this._columns.setMaxIndex(+sheetGeomJSON.maxtiledcolumn);
+               this._rows.setMaxIndex(+sheetGeomJSON.maxtiledrow);
+
+               return updateOK;
+       },
+
+       setTileGeometryData: function (tileWidthTwips, tileHeightTwips, 
tileSizeCSSPixels,
+               dpiScale, updatePositions) {
+
+               this._columns.setTileGeometryData(tileWidthTwips, 
tileSizeCSSPixels, dpiScale, updatePositions);
+               this._rows.setTileGeometryData(tileHeightTwips, 
tileSizeCSSPixels, dpiScale, updatePositions);
+       },
+
+       setViewArea: function (topLeftTwipsPoint, sizeTwips) {
+
+               if (!(topLeftTwipsPoint instanceof L.Point) || !(sizeTwips 
instanceof L.Point)) {
+                       console.error('invalid argument types');
+                       return false;
+               }
+
+               var left   = topLeftTwipsPoint.x;
+               var top    = topLeftTwipsPoint.y;
+               var right  = left + sizeTwips.x;
+               var bottom = top + sizeTwips.y;
+
+               this._columns.setViewLimits(left, right);
+               this._rows.setViewLimits(top, bottom);
+
+               return true;
+       },
+
+       // returns an object with keys 'start' and 'end' indicating the
+       // column range in the current view area.
+       getViewColumnRange: function () {
+               return this._columns.getViewElementRange();
+       },
+
+       // returns an object with keys 'start' and 'end' indicating the
+       // row range in the current view area.
+       getViewRowRange: function () {
+               return this._rows.getViewElementRange();
+       },
+
+       getViewCellRange: function () {
+               return {
+                       columnrange: this.getViewColumnRange(),
+                       rowrange: this.getViewRowRange()
+               };
+       },
+
+       // Returns an object with the following fields:
+       // columnIndex should be zero based.
+       // 'startpos' (start position of the column in css pixels), 'size' 
(column size in css pixels).
+       // Note: All these fields are computed by assuming zero sizes for 
hidden/filtered columns.
+       getColumnData: function (columnIndex) {
+               return this._columns.getElementData(columnIndex);
+       },
+
+       // Returns an object with the following fields:
+       // rowIndex should be zero based.
+       // 'startpos' (start position of the row in css pixels), 'size' (row 
size in css pixels).
+       // Note: All these fields are computed by assuming zero sizes for 
hidden/filtered rows.
+       getRowData: function (rowIndex) {
+               return this._rows.getElementData(rowIndex);
+       },
+
+       // Runs the callback for every column in the inclusive range 
[columnStart, columnEnd].
+       // callback is expected to have a signature of (column, columnData)
+       // where 'column' will contain the column index(zero based) and 
'columnData' will be an object with
+       // the same fields as returned by getColumnData().
+       forEachColumnInRange: function (columnStart, columnEnd, callback) {
+               this._columns.forEachInRange(columnStart, columnEnd, callback);
+       },
+
+       // Runs the callback for every row in the inclusive range [rowStart, 
rowEnd].
+       // callback is expected to have a signature of (row, rowData)
+       // where 'row' will contain the row index(zero based) and 'rowData' 
will be an object with
+       // the same fields as returned by getRowData().
+       forEachRowInRange: function (rowStart, rowEnd, callback) {
+               this._rows.forEachInRange(rowStart, rowEnd, callback);
+       },
+
+       getColumnGroupLevels: function () {
+               return this._columns.getGroupLevels();
+       },
+
+       getRowGroupLevels: function () {
+               return this._rows.getGroupLevels();
+       },
+
+       getColumnGroupsDataInView: function () {
+               return this._columns.getGroupsDataInView();
+       },
+
+       getRowGroupsDataInView: function () {
+               return this._rows.getGroupsDataInView();
+       },
+
+       _testValidity: function (sheetGeomJSON, checkCompleteness) {
+
+               if (!sheetGeomJSON.hasOwnProperty('commandName')) {
+                       console.error(this._unoCommand + ' response has no 
property named "commandName".');
+                       return false;
+               }
+
+               if (sheetGeomJSON.commandName !== this._unoCommand) {
+                       console.error('JSON response has wrong commandName: ' +
+                               sheetGeomJSON.commandName + ' expected: ' +
+                               this._unoCommand);
+                       return false;
+               }
+
+               if (typeof sheetGeomJSON.maxtiledcolumn !== 'string' ||
+                       !/^\d+$/.test(sheetGeomJSON.maxtiledcolumn)) {
+                       console.error('JSON is missing/unreadable 
maxtiledcolumn property');
+                       return false;
+               }
+
+               if (typeof sheetGeomJSON.maxtiledrow !== 'string' ||
+                       !/^\d+$/.test(sheetGeomJSON.maxtiledrow)) {
+                       console.error('JSON is missing/unreadable maxtiledrow 
property');
+                       return false;
+               }
+
+               if (checkCompleteness) {
+
+                       if (!sheetGeomJSON.hasOwnProperty('rows') ||
+                               !sheetGeomJSON.hasOwnProperty('columns')) {
+
+                               console.error(this._unoCommand + ' response is 
incomplete.');
+                               return false;
+                       }
+
+                       if (typeof sheetGeomJSON.rows !== 'object' ||
+                               typeof sheetGeomJSON.columns !== 'object') {
+
+                               console.error(this._unoCommand + ' response has 
invalid rows/columns children.');
+                               return false;
+                       }
+
+                       var expectedFields = ['sizes', 'hidden', 'filtered'];
+                       for (var idx = 0; idx < expectedFields.length; idx++) {
+
+                               var fieldName = expectedFields[idx];
+                               var encodingForCols = 
sheetGeomJSON.columns[fieldName];
+                               var encodingForRows = 
sheetGeomJSON.rows[fieldName];
+
+                               // Don't accept empty string or any other types.
+                               if (typeof encodingForRows !== 'string' || 
!encodingForRows) {
+                                       console.error(this._unoCommand + ' 
response has invalid value for rows.' +
+                                               fieldName);
+                                       return false;
+                               }
+
+                               // Don't accept empty string or any other types.
+                               if (typeof encodingForCols !== 'string' || 
!encodingForCols) {
+                                       console.error(this._unoCommand + ' 
response has invalid value for columns.' +
+                                               fieldName);
+                                       return false;
+                               }
+                       }
+               }
+
+               return true;
+       }
+});
+
+// Used to represent/query geometry data about either rows or columns.
+L.SheetDimension = L.Class.extend({
+
+       initialize: function () {
+
+               this._sizes = new L.SpanList();
+               this._hidden = new L.BoolSpanList();
+               this._filtered = new L.BoolSpanList();
+               this._groups = new L.DimensionOutlines();
+
+               // This is used to store the span-list of sizes
+               // with hidden/filtered elements set to zero size.
+               // This needs to be updated whenever
+               // this._sizes/this._hidden/this._filtered are modified.
+               this._visibleSizes = undefined;
+       },
+
+       update: function (jsonObject) {
+
+               if (typeof jsonObject !== 'object') {
+                       return false;
+               }
+
+               var regenerateVisibleSizes = false;
+               var loadsOK = true;
+               if (jsonObject.hasOwnProperty('sizes')) {
+                       loadsOK = this._sizes.load(jsonObject.sizes);
+                       regenerateVisibleSizes = true;
+               }
+
+               if (jsonObject.hasOwnProperty('hidden')) {
+                       var thisLoadOK = this._hidden.load(jsonObject.hidden);
+                       loadsOK = loadsOK && thisLoadOK;
+                       regenerateVisibleSizes = true;
+               }
+
+               if (jsonObject.hasOwnProperty('filtered')) {
+                       thisLoadOK = this._filtered.load(jsonObject.filtered);
+                       loadsOK = loadsOK && thisLoadOK;
+                       regenerateVisibleSizes = true;
+               }
+
+               if (jsonObject.hasOwnProperty('groups')) {
+                       thisLoadOK = this._groups.load(jsonObject.groups);
+                       loadsOK = loadsOK && thisLoadOK;
+               }
+
+               if (loadsOK && regenerateVisibleSizes) {
+                       this._updateVisible();
+               }
+
+               return loadsOK;
+       },
+
+       setMaxIndex: function (maxIndex) {
+               this._maxIndex = maxIndex;
+       },
+
+       setTileGeometryData: function (tileSizeTwips, tileSizeCSSPixels, 
dpiScale, updatePositions) {
+               if (updatePositions === undefined) {
+                       updatePositions = true;
+               }
+
+               this._twipsPerCSSPixel = tileSizeTwips / tileSizeCSSPixels;
+               this._devPixelsPerCssPixel = dpiScale;
+
+               if (updatePositions) {
+                       // We need to compute positions data for every zoom 
change.
+                       this._updatePositions();
+               }
+       },
+
+       _updateVisible: function () {
+
+               var invisibleSpanList = this._hidden.union(this._filtered); // 
this._hidden is not modified.
+               this._visibleSizes = 
this._sizes.applyZeroValues(invisibleSpanList); // this._sizes is not modified.
+               this._updatePositions();
+       },
+
+       _updatePositions: function() {
+
+               var posDevPx = 0; // position in device pixels.
+               var dimensionObj = this;
+               this._visibleSizes.addCustomDataForEachSpan(function (
+                       index,
+                       size, /* size in twips of one element in the span */
+                       spanLength /* #elements in the span */) {
+
+                       // Important: rounding needs to be done in device 
pixels exactly like the core.
+                       var sizeDevPxOne = Math.floor(size / 
dimensionObj._twipsPerCSSPixel * dimensionObj._devPixelsPerCssPixel);
+                       posDevPx += (sizeDevPxOne * spanLength);
+                       var posCssPx = posDevPx / 
dimensionObj._devPixelsPerCssPixel;
+                       // position in device-pixel aligned twips.
+                       var posTileTwips = Math.floor(posCssPx * 
dimensionObj._twipsPerCSSPixel);
+
+                       var customData = {
+                               sizedev: sizeDevPxOne,
+                               posdevpx: posDevPx,
+                               poscsspx: posCssPx,
+                               postiletwips: posTileTwips
+                       };
+
+                       return customData;
+               });
+       },
+
+       getElementData: function (index) {
+               var span = this._visibleSizes.getSpanDataByIndex(index);
+               if (span === undefined) {
+                       return undefined;
+               }
+
+               return this._getElementDataFromSpanByIndex(index, span);
+       },
+
+       _getElementDataFromSpanByIndex: function (index, span) {
+               if (span === undefined || index < span.start || span.end < 
index) {
+                       return undefined;
+               }
+
+               var numSizes = span.end - index + 1;
+               // all in css pixels.
+               return {
+                       startpos: (span.data.posdevpx - span.data.sizedev * 
numSizes) / this._devPixelsPerCssPixel,
+                       size: span.data.sizedev / this._devPixelsPerCssPixel
+               };
+       },
+
+       forEachInRange: function (start, end, callback) {
+
+               var dimensionObj = this;
+               this._visibleSizes.forEachSpanInRange(start, end, function 
(span) {
+                       var first = Math.max(span.start, start);
+                       var last = Math.min(span.end, end);
+                       for (var index = first; index <= last; ++index) {
+                               callback(index, 
dimensionObj._getElementDataFromSpanByIndex(index, span));
+                       }
+               });
+       },
+
+       _getIndexFromTileTwipsPos: function (pos) {
+               var span = this._visibleSizes.getSpanDataByCustomDataField(pos, 
'postiletwips');
+               var elementCount = span.end - span.start + 1;
+               var posStart = ((span.data.posdevpx - span.data.sizedev * 
elementCount) /
+                       this._devPixelsPerCssPixel * this._twipsPerCSSPixel);
+               var posEnd = span.data.postiletwips;
+               var sizeOne = (posEnd - posStart) / elementCount;
+               var relativeIndex = Math.round((pos - posStart) / sizeOne);
+
+               return span.start + relativeIndex;
+       },
+
+       setViewLimits: function (startPosTileTwips, endPosTileTwips) {
+
+               // Extend the range a bit, to compensate for rounding errors.
+               this._viewStartIndex = Math.max(0, 
this._getIndexFromTileTwipsPos(startPosTileTwips) - 2);
+               this._viewEndIndex = Math.min(this._maxIndex, 
this._getIndexFromTileTwipsPos(endPosTileTwips) + 2);
+       },
+
+       getViewElementRange: function () {
+               return {
+                       start: this._viewStartIndex,
+                       end: this._viewEndIndex
+               };
+       },
+
+       getGroupLevels: function () {
+               return this._outlines.getLevels();
+       },
+
+       getGroupsDataInView: function () {
+               var groupsData = [];
+               var levels = this._outlines.getLevels();
+               if (!levels) {
+                       return groupsData;
+               }
+
+               var dimensionObj = this;
+               this._outlines.forEachGroupInRange(this._viewStartIndex, 
this._viewEndIndex,
+                       function (levelIdx, groupIdx, start, end, hidden) {
+
+                               var startElementData = 
dimensionObj.getElementData(start, true /* device pixels */);
+                               var endElementData = 
dimensionObj.getElementData(end, true /* device pixels */);
+                               groupsData.push({
+                                       level: (levelIdx + 1).toString(),
+                                       index: groupIdx.toString(),
+                                       startPos: 
startElementData.startpos.toString(),
+                                       endPos: (endElementData.startpos + 
endElementData.size).toString(),
+                                       hidden: hidden ? '1' : '0'
+                               });
+                       });
+
+               return groupsData;
+       },
+
+       getMaxIndex: function () {
+               return this._maxIndex;
+       }
+});
+
+L.SpanList = L.Class.extend({
+
+       initialize: function (encoding) {
+
+               // spans are objects with keys: 'index' and 'value'.
+               // 'index' holds the last element of the span.
+               // Optionally custom data of a span can be added
+               // under the key 'data' via addCustomDataForEachSpan.
+               this._spanlist = [];
+               if (typeof encoding !== 'string') {
+                       return;
+               }
+
+               this.load(encoding);
+       },
+
+       load: function (encoding) {
+
+               if (typeof encoding !== 'string') {
+                       return false;
+               }
+
+               var result = parseSpanListEncoding(encoding, false /* boolean 
value ? */);
+               if (result === undefined) {
+                       return false;
+               }
+
+               this._spanlist = result.spanlist;
+               return true;
+       },
+
+       // Runs in O(#spans in 'this' + #spans in 'other')
+       applyZeroValues: function (other) {
+
+               if (!(other instanceof L.BoolSpanList)) {
+                       return undefined;
+               }
+
+               // Ensure both spanlists have the same total range.
+               if (this._spanlist[this._spanlist.length - 1].index !== 
other._spanlist[other._spanlist.length - 1]) {
+                       return undefined;
+               }
+
+               var maxElement = this._spanlist[this._spanlist.length - 
1].index;
+               var result = new L.SpanList();
+
+               var thisIdx = 0;
+               var otherIdx = 0;
+               var zeroBit = other._startBit;
+               var resultValue = zeroBit ? 0 : this._spanlist[thisIdx].value;
+
+               while (thisIdx < this._spanlist.length && otherIdx < 
other._spanlist.length) {
+
+                       // end elements of the current spans of 'this' and 
'other'.
+                       var thisElement = this._spanlist[thisIdx].index;
+                       var otherElement = other._spanlist[otherIdx];
+
+                       var lastElement = otherElement;
+                       if (thisElement < otherElement) {
+                               lastElement = thisElement;
+                               ++thisIdx;
+                       }
+                       else if (otherElement < thisElement) {
+                               zeroBit = !zeroBit;
+                               ++otherIdx;
+                       }
+                       else { // both elements are equal.
+                               zeroBit = !zeroBit;
+                               ++thisIdx;
+                               ++otherIdx;
+                       }
+
+                       var nextResultValue = resultValue;
+                       if (thisIdx < this._spanlist.length) {
+                               nextResultValue = zeroBit ? 0 : 
this._spanlist[thisIdx].value;
+                       }
+
+                       if (resultValue != nextResultValue || lastElement >= 
maxElement) {
+                               // In the result spanlist a new span start from 
lastElement+1
+                               // or reached the maximum possible element.
+                               result._spanlist.push({index: lastElement, 
value: resultValue});
+                               resultValue = nextResultValue;
+                       }
+               }
+
+               return result;
+       },
+
+       addCustomDataForEachSpan: function (getCustomDataCallback) {
+
+               var prevIndex = -1;
+               this._spanlist.forEach(function (span) {
+                       span.data = getCustomDataCallback(
+                               span.index, span.value,
+                               span.index - prevIndex);
+                       prevIndex = span.index;
+               });
+       },
+
+       getSpanDataByIndex: function (index) {
+               var spanid = this._searchByIndex(index);
+               if (spanid == -1) {
+                       return undefined;
+               }
+
+               return this._getSpanData(spanid);
+       },
+
+       getSpanDataByCustomDataField: function (value, fieldName) {
+               var spanid = this._searchByCustomDataField(value, fieldName);
+               if (spanid == -1) {
+                       return undefined;
+               }
+
+               return this._getSpanData(spanid);
+       },
+
+       forEachSpanInRange: function (start, end, callback) {
+
+               if (start > end) {
+                       return;
+               }
+
+               var startId = this._searchByIndex(start);
+               var endId = this._searchByIndex(end);
+
+               if (startId == -1 || endId == -1) {
+                       return;
+               }
+
+               for (var id = startId; id <= endId; ++id) {
+                       callback(this._getSpanData(id));
+               }
+       },
+
+       _getSpanData: function (spanid) {
+
+               var span = this._spanlist[spanid];
+               var dataClone = undefined;
+               if (span.data) {
+                       dataClone = {};
+                       Object.keys(span.data).forEach(function (key) {
+                               dataClone[key] = span.data[key];
+                       });
+               }
+
+               return {
+                       start: spanid ? this._spanlist[spanid - 1].index + 1 : 
0,
+                       end: span.index,
+                       size: span.value,
+                       data: dataClone
+               };
+       },
+
+       _searchByIndex: function (index) {
+
+               if (index < 0 || index > this._spanlist[this._spanlist.length - 
1].index) {
+                       return -1;
+               }
+
+               var start = 0;
+               var end = this._spanlist.length - 1;
+               var mid = -1;
+               while (start <= end) {
+                       mid = Math.round((start + end) / 2);
+                       var spanstart = mid ? this._spanlist[mid - 1].index + 1 
: 0;
+                       var spanend = this._spanlist[mid].index;
+                       if (spanstart <= index && index <= spanend) {
+                               break;
+                       }
+
+                       if (index < spanstart) {
+                               end = mid - 1;
+                       }
+                       else { // spanend < index
+                               start = mid + 1;
+                       }
+               }
+
+               return mid;
+       },
+
+       _searchByCustomDataField: function (value, fieldName) {
+
+               // All custom searchable data values are assumed to start from 
0 at the start of first span.
+               var maxValue = this._spanlist[this._spanlist.length - 
1].data[fieldName];
+               if (value < 0 || value > maxValue) {
+                       return -1;
+               }
+
+               var start = 0;
+               var end = this._spanlist.length - 1;
+               var mid = -1;
+               while (start <= end) {
+                       mid = Math.round((start + end) / 2);
+                       var valuestart = mid ? this._spanlist[mid - 
1].data[fieldName] + 1 : 0;
+                       var valueend = this._spanlist[mid].data[fieldName];
+                       if (valuestart <= value && value <= valueend) {
+                               break;
+                       }
+
+                       if (value < valuestart) {
+                               end = mid - 1;
+                       }
+                       else { // valueend < value
+                               start = mid + 1;
+                       }
+               }
+
+               // may fail for custom data ?
+               return (start <= end) ? mid : -1;
+       }
+
+});
+
+L.BoolSpanList = L.SpanList.extend({
+
+       load: function (encoding) {
+
+               if (typeof encoding !== 'string') {
+                       return false;
+               }
+
+               var result = parseSpanListEncoding(encoding, true /* boolean 
value ? */);
+               if (result === undefined) {
+                       return false;
+               }
+
+               this._spanlist = result.spanlist;
+               this._startBit = result.startBit;
+               return true;
+       },
+
+       // Runs in O(#spans in 'this' + #spans in 'other')
+       union: function (other) {
+
+               if (!(other instanceof L.BoolSpanList)) {
+                       return undefined;
+               }
+
+               // Ensure both spanlists have the same total range.
+               if (this._spanlist[this._spanlist.length - 1] !== 
other._spanlist[other._spanlist.length - 1]) {
+                       return undefined;
+               }
+
+               var maxElement = this._spanlist[this._spanlist.length - 1];
+
+               var result = new L.BoolSpanList();
+               var thisBit = this._startBit;
+               var otherBit = other._startBit;
+               var resultBit = thisBit || otherBit;
+               result._startBit = resultBit;
+
+               var thisIdx = 0;
+               var otherIdx = 0;
+
+               while (thisIdx < this._spanlist.length && otherIdx < 
other._spanlist.length) {
+
+                       // end elements of the current spans of 'this' and 
'other'.
+                       var thisElement = this._spanlist[thisIdx];
+                       var otherElement = other._spanlist[otherIdx];
+
+                       var lastElement = otherElement;
+                       if (thisElement < otherElement) {
+                               lastElement = thisElement;
+                               thisBit = !thisBit;
+                               ++thisIdx;
+                       }
+                       else if (otherElement < thisElement) {
+                               otherBit = !otherBit;
+                               ++otherIdx;
+                       }
+                       else { // both elements are equal.
+                               thisBit = !thisBit;
+                               otherBit = !otherBit;
+                               ++thisIdx;
+                               ++otherIdx;
+                       }
+
+                       var nextResultBit = (thisBit || otherBit);
+                       if (resultBit != nextResultBit || lastElement >= 
maxElement) {
+                               // In the result spanlist a new span start from 
lastElement+1
+                               // or reached the maximum possible element.
+                               result._spanlist.push(lastElement);
+                               resultBit = nextResultBit;
+                       }
+               }
+
+               return result;
+       }
+});
+
+function parseSpanListEncoding(encoding, booleanValue) {
+
+       var spanlist = [];
+       var splits = encoding.split(' ');
+       if (splits.length < 2) {
+               return undefined;
+       }
+
+       var startBit = false;
+       if (booleanValue) {
+               var parts = splits[0].split(':');
+               if (parts.length != 2) {
+                       return undefined;
+               }
+               startBit = parseInt(parts[0]);
+               var first = parseInt(parts[1]);
+               if (isNaN(startBit) || isNaN(first)) {
+                       return undefined;
+               }
+               spanlist.push(first);
+       }
+
+       startBit = Boolean(startBit);
+
+       for (var idx = 0; idx < splits.length - 1; ++idx) {
+
+               if (booleanValue) {
+                       if (!idx) {
+                               continue;
+                       }
+
+                       var entry = parseInt(splits[idx]);
+                       if (isNaN(entry)) {
+                               return undefined;
+                       }
+
+                       spanlist.push(entry);
+                       continue;
+               }
+
+               var spanParts = splits[idx].split(':');
+               if (spanParts.length != 2) {
+                       return undefined;
+               }
+
+               var span = {
+                       index: parseInt(spanParts[1]),
+                       value: parseInt(spanParts[0])
+               };
+
+               if (isNaN(span.index) || isNaN(span.value)) {
+                       return undefined;
+               }
+
+               spanlist.push(span);
+       }
+
+       var result = {spanlist: spanlist};
+
+       if (booleanValue) {
+               result.startBit = startBit;
+       }
+
+       return result;
+}
+
+L.DimensionOutlines = L.Class.extend({
+
+       initialize: function (encoding) {
+
+               this._outlines = [];
+               if (typeof encoding !== 'string') {
+                       return;
+               }
+
+               this.load(encoding);
+       },
+
+       load: function (encoding) {
+
+               if (typeof encoding !== 'string') {
+                       return false;
+               }
+
+               var levels = encoding.split(' ');
+               if (levels.length < 2) {
+                       // No outline.
+                       return true;
+               }
+
+               var outlines = [];
+
+               for (var levelIdx = 0; levelIdx < levels.length - 1; 
++levelIdx) {
+                       var collectionSplits = levels[levelIdx].split(',');
+                       var collections = [];
+                       if (collectionSplits.length < 2) {
+                               return false;
+                       }
+
+                       for (var collIdx = 0; collIdx < collectionSplits.length 
- 1; ++collIdx) {
+                               var entrySplits = 
collectionSplits[collIdx].split(':');
+                               if (entrySplits.length < 4) {
+                                       return false;
+                               }
+
+                               var olineEntry = {
+                                       start: parseInt(entrySplits[0]),
+                                       size: parseInt(entrySplits[1]),
+                                       hidden: parseInt(entrySplits[2]),
+                                       visible: parseInt(entrySplits[3])
+                               };
+
+                               if (isNaN(olineEntry.start) || 
isNaN(olineEntry.size) ||
+                                       isNaN(olineEntry.hidden) || 
isNaN(olineEntry.visible)) {
+                                       return false;
+                               }
+
+                               collections.push(olineEntry);
+                       }
+
+                       outlines.push(collections);
+               }
+
+               this._outlines = outlines;
+               return true;
+       }
+});
commit 24270f2efa1ad0f4ffd1137e42d0539b918266b7
Author:     Dennis Francis <dennis.fran...@collabora.com>
AuthorDate: Mon May 11 21:31:25 2020 +0530
Commit:     Dennis Francis <dennis.fran...@collabora.com>
CommitDate: Sun May 17 17:34:21 2020 +0530

    move the 'updaterowcolumnheaders' event handling code
    
    from L.Control.Scroll to a new method requestViewRowColumnData() under
    L.CalcTileLayer which is arguably a more appropriate place for it and
    change all the places that calls map.fire() to emit
    'updaterowcolumnheaders' to call the new method directly.
    
    This helps to improve the code readability a bit by being more explicit
    and also avoid an unnecessary indirection while code grepping.
    
    This also makes it much easier to introduce the change in data source
    from .uno:ViewRowColumnHeaders to .uno:SheetGeometryData by avoiding
    lots of abrupt changes in one go.
    
    Change-Id: Ia42d7586f06e28a5715fac278967a445089308af

diff --git a/loleaflet/src/control/Control.ColumnHeader.js 
b/loleaflet/src/control/Control.ColumnHeader.js
index f9d3feedb..a2846f78e 100644
--- a/loleaflet/src/control/Control.ColumnHeader.js
+++ b/loleaflet/src/control/Control.ColumnHeader.js
@@ -193,7 +193,7 @@ L.Control.ColumnHeader = L.Control.Header.extend({
        },
 
        _updateColumnHeader: function () {
-               this._map.fire('updaterowcolumnheaders', {x: 
this._map._getTopLeftPoint().x, y: 0, offset: {x: undefined, y: 0}});
+               this._map._docLayer.requestViewRowColumnData({x: 
this._map._getTopLeftPoint().x, y: 0, offset: {x: undefined, y: 0}});
        },
 
        drawHeaderEntry: function (entry, isOver, isHighlighted, isCurrent) {
diff --git a/loleaflet/src/control/Control.RowHeader.js 
b/loleaflet/src/control/Control.RowHeader.js
index 0bf1fcb38..afbba3c45 100644
--- a/loleaflet/src/control/Control.RowHeader.js
+++ b/loleaflet/src/control/Control.RowHeader.js
@@ -186,7 +186,7 @@ L.Control.RowHeader = L.Control.Header.extend({
        },
 
        _updateRowHeader: function () {
-               this._map.fire('updaterowcolumnheaders', {x: 0, y: 
this._map._getTopLeftPoint().y, offset: {x: 0, y: undefined}});
+               this._map._docLayer.requestViewRowColumnData({x: 0, y: 
this._map._getTopLeftPoint().y, offset: {x: 0, y: undefined}});
        },
 
        drawHeaderEntry: function (entry, isOver, isHighlighted, isCurrent) {
diff --git a/loleaflet/src/control/Control.Scroll.js 
b/loleaflet/src/control/Control.Scroll.js
index 650f69ce4..e3f9cf4dc 100644
--- a/loleaflet/src/control/Control.Scroll.js
+++ b/loleaflet/src/control/Control.Scroll.js
@@ -23,7 +23,6 @@ L.Control.Scroll = L.Control.extend({
                map.on('handleautoscroll', this._onHandleAutoScroll, this);
                map.on('docsize', this._onUpdateSize, this);
                map.on('updatescrolloffset', this._onUpdateScrollOffset, this);
-               map.on('updaterowcolumnheaders', 
this._onUpdateRowColumnHeaders, this);
 
                var control = this;
                var autoHideTimeout = null;
@@ -115,7 +114,7 @@ L.Control.Scroll = L.Control.extend({
                        return;
                }
 
-               this._onUpdateRowColumnHeaders({ x: newLeft, y: newTop, offset: 
offset});
+               this._map._docLayer.requestViewRowColumnData({ x: newLeft, y: 
newTop, offset: offset});
 
                this._prevScrollY = newTop;
                this._prevScrollX = newLeft;
@@ -268,7 +267,7 @@ L.Control.Scroll = L.Control.extend({
                        offset.y = 1;
                }
                if (e.updateHeaders && this._map._docLayer._docType === 
'spreadsheet') {
-                       this._onUpdateRowColumnHeaders({x: e.x, y: e.y, offset: 
offset});
+                       this._map._docLayer.requestViewRowColumnData({x: e.x, 
y: e.y, offset: offset});
                }
                this._map.fire('scrolloffset', offset);
                this._ignoreScroll = null;
@@ -276,41 +275,6 @@ L.Control.Scroll = L.Control.extend({
                this._prevScrollY = e.y;
                this._prevScrollX = e.x;
                $('.scroll-container').mCustomScrollbar('scrollTo', [e.y, e.x], 
{callbacks: false, timeout:0});
-       },
-
-       _onUpdateRowColumnHeaders: function(e) {
-               var offset = e.offset || {};
-
-               var topLeftPoint = new L.Point(e.x, e.y);
-               var sizePx = this._map.getSize();
-
-               if (topLeftPoint.x === undefined) {
-                       topLeftPoint.x = this._map._getTopLeftPoint().x;
-               }
-               if (topLeftPoint.y === undefined) {
-                       topLeftPoint.y = this._map._getTopLeftPoint().y;
-               }
-
-               if (offset.x === 0) {
-                       topLeftPoint.x = -1;
-                       sizePx.x = 0;
-               }
-               if (offset.y === 0) {
-                       topLeftPoint.y = -1;
-                       sizePx.y = 0;
-               }
-
-               var pos = this._map._docLayer._pixelsToTwips(topLeftPoint);
-               var size = this._map._docLayer._pixelsToTwips(sizePx);
-               var payload = 'commandvalues 
command=.uno:ViewRowColumnHeaders?x=' + Math.round(pos.x) + '&y=' + 
Math.round(pos.y) +
-                       '&width=' + Math.round(size.x) + '&height=' + 
Math.round(size.y);
-
-               if (e.outline) {
-                       payload += '&columnOutline=' + e.outline.column + 
'&groupLevel=' + e.outline.level
-                               + '&groupIndex=' + e.outline.index + 
'&groupHidden=' + e.outline.hidden;
-               }
-
-               this._map._socket.sendMessage(payload);
        }
 });
 
diff --git a/loleaflet/src/layer/tile/CalcTileLayer.js 
b/loleaflet/src/layer/tile/CalcTileLayer.js
index 323fcc711..06ffa2bef 100644
--- a/loleaflet/src/layer/tile/CalcTileLayer.js
+++ b/loleaflet/src/layer/tile/CalcTileLayer.js
@@ -264,13 +264,13 @@ L.CalcTileLayer = L.TileLayer.extend({
                                }
                        }
                } else if (textMsg.startsWith('invalidateheader: column')) {
-                       this._map.fire('updaterowcolumnheaders', {x: 
this._map._getTopLeftPoint().x, y: 0, offset: {x: undefined, y: 0}});
+                       this.requestViewRowColumnData({x: 
this._map._getTopLeftPoint().x, y: 0, offset: {x: undefined, y: 0}});
                        this._map._socket.sendMessage('commandvalues 
command=.uno:ViewAnnotationsPosition');
                } else if (textMsg.startsWith('invalidateheader: row')) {
-                       this._map.fire('updaterowcolumnheaders', {x: 0, y: 
this._map._getTopLeftPoint().y, offset: {x: 0, y: undefined}});
+                       this.requestViewRowColumnData({x: 0, y: 
this._map._getTopLeftPoint().y, offset: {x: 0, y: undefined}});
                        this._map._socket.sendMessage('commandvalues 
command=.uno:ViewAnnotationsPosition');
                } else if (textMsg.startsWith('invalidateheader: all')) {
-                       this._map.fire('updaterowcolumnheaders', {x: 
this._map._getTopLeftPoint().x, y: this._map._getTopLeftPoint().y, offset: {x: 
undefined, y: undefined}});
+                       this.requestViewRowColumnData({x: 
this._map._getTopLeftPoint().x, y: this._map._getTopLeftPoint().y, offset: {x: 
undefined, y: undefined}});
                        this._map._socket.sendMessage('commandvalues 
command=.uno:ViewAnnotationsPosition');
                } else {
                        L.TileLayer.prototype._onMessage.call(this, textMsg, 
img);
@@ -361,14 +361,14 @@ L.CalcTileLayer = L.TileLayer.extend({
                        this._map.setPart(part, true);
                        this._map.fire('setpart', {selectedPart: 
this._selectedPart});
                        // TODO: test it!
-                       this._map.fire('updaterowcolumnheaders');
+                       this.requestViewRowColumnData();
                }
        },
 
        _onZoomRowColumns: function () {
                this._sendClientZoom();
                // TODO: test it!
-               this._map.fire('updaterowcolumnheaders');
+               this.requestViewRowColumnData();
                this._map._socket.sendMessage('commandvalues 
command=.uno:ViewAnnotationsPosition');
        },
 
@@ -454,6 +454,49 @@ L.CalcTileLayer = L.TileLayer.extend({
                }
        },
 
+       // This send .uno:ViewRowColumnHeaders command to core with the new 
view coordinates.
+       requestViewRowColumnData: function (coordinatesData) {
+
+               // There are places that call this function with no arguments 
to indicate that the
+               // command arguments should be the current map area coordinates.
+               if (typeof coordinatesData != 'object') {
+                       coordinatesData = {};
+               }
+
+               var offset = coordinatesData.offset || {};
+
+               var topLeftPoint = new L.Point(coordinatesData.x, 
coordinatesData.y);
+               var sizePx = this._map.getSize();
+
+               if (topLeftPoint.x === undefined) {
+                       topLeftPoint.x = this._map._getTopLeftPoint().x;
+               }
+               if (topLeftPoint.y === undefined) {
+                       topLeftPoint.y = this._map._getTopLeftPoint().y;
+               }
+
+               if (offset.x === 0) {
+                       topLeftPoint.x = -1;
+                       sizePx.x = 0;
+               }
+               if (offset.y === 0) {
+                       topLeftPoint.y = -1;
+                       sizePx.y = 0;
+               }
+
+               var pos = this._pixelsToTwips(topLeftPoint);
+               var size = this._pixelsToTwips(sizePx);
+               var payload = 'commandvalues 
command=.uno:ViewRowColumnHeaders?x=' + Math.round(pos.x) + '&y=' + 
Math.round(pos.y) +
+                       '&width=' + Math.round(size.x) + '&height=' + 
Math.round(size.y);
+
+               if (coordinatesData.outline) {
+                       payload += '&columnOutline=' + 
coordinatesData.outline.column + '&groupLevel=' + coordinatesData.outline.level
+                               + '&groupIndex=' + 
coordinatesData.outline.index + '&groupHidden=' + 
coordinatesData.outline.hidden;
+               }
+
+               this._map._socket.sendMessage(payload);
+       },
+
        _handleViewRowColumnHeadersMsg: function (jsonMsgObj) {
                this._map.fire('viewrowcolumnheaders', {
                        data: jsonMsgObj,
diff --git a/loleaflet/src/map/Map.js b/loleaflet/src/map/Map.js
index 670e625c7..8e05b5544 100644
--- a/loleaflet/src/map/Map.js
+++ b/loleaflet/src/map/Map.js
@@ -323,7 +323,9 @@ L.Map = L.Evented.extend({
                // TODO: remove duplicated init code
                this._socket.sendMessage('commandvalues 
command=.uno:LanguageStatus');
                this._socket.sendMessage('commandvalues 
command=.uno:ViewAnnotations');
-               this.fire('updaterowcolumnheaders');
+               if (this._docLayer._docType === 'spreadsheet') {
+                       this._docLayer.requestViewRowColumnData();
+               }
                this._docLayer._getToolbarCommandsValues();
        },
 
commit e6ce819b0c465a5d5a79ea8a61a5a3dc90012a6d
Author:     Dennis Francis <dennis.fran...@collabora.com>
AuthorDate: Mon May 11 15:17:49 2020 +0530
Commit:     Dennis Francis <dennis.fran...@collabora.com>
CommitDate: Sun May 17 17:34:21 2020 +0530

    Move the ViewRowColumnHeaders handling code into its own method
    
    _handleViewRowColumnHeadersMsg()
    
    Change-Id: Ib0b83941dcc9bc0bed42269fafbc61fcd7877bc6

diff --git a/loleaflet/src/layer/tile/CalcTileLayer.js 
b/loleaflet/src/layer/tile/CalcTileLayer.js
index 684d559c1..323fcc711 100644
--- a/loleaflet/src/layer/tile/CalcTileLayer.js
+++ b/loleaflet/src/layer/tile/CalcTileLayer.js
@@ -454,6 +454,16 @@ L.CalcTileLayer = L.TileLayer.extend({
                }
        },
 
+       _handleViewRowColumnHeadersMsg: function (jsonMsgObj) {
+               this._map.fire('viewrowcolumnheaders', {
+                       data: jsonMsgObj,
+                       cursor: this._getCursorPosSize(),
+                       selection: this._getSelectionHeaderData(),
+                       converter: this._twipsToPixels,
+                       context: this
+               });
+       },
+
        _onCommandValuesMsg: function (textMsg) {
                var jsonIdx = textMsg.indexOf('{');
                if (jsonIdx === -1)
@@ -466,13 +476,7 @@ L.CalcTileLayer = L.TileLayer.extend({
 
                var comment;
                if (values.commandName === '.uno:ViewRowColumnHeaders') {
-                       this._map.fire('viewrowcolumnheaders', {
-                               data: values,
-                               cursor: this._getCursorPosSize(),
-                               selection: this._getSelectionHeaderData(),
-                               converter: this._twipsToPixels,
-                               context: this
-                       });
+                       this._handleViewRowColumnHeadersMsg(values);
 
                } else if (values.comments) {
                        this.clearAnnotations();
commit 4ab346b6b36427cd66c6e1f2f77aa57c83277424
Author:     Dennis Francis <dennis.fran...@collabora.com>
AuthorDate: Sun May 10 01:57:01 2020 +0530
Commit:     Dennis Francis <dennis.fran...@collabora.com>
CommitDate: Sun May 17 17:34:21 2020 +0530

    Do not rely on js ordering of multi event execution
    
    On getting a .uno:ViewRowColumnHeaders message, the order of header
    painting should be the headers elements first, then the cursor
    indication on the header, then the selection area indication on the
    header if any. More importantly none of these painting will be correct
    if the data in the tickMap member of both headers is stale.
    
    As of now all three of these are executed by three different events.
    Lets avoid depending on the implicit ordering of execution of these and
    do these synchronously as part of the main event
    ('viewrowcolumnheaders') handler.
    
    Change-Id: I4da29ba893c408af45159073e4389481b2eaecc7

diff --git a/loleaflet/src/control/Control.ColumnHeader.js 
b/loleaflet/src/control/Control.ColumnHeader.js
index 8c049fd93..f9d3feedb 100644
--- a/loleaflet/src/control/Control.ColumnHeader.js
+++ b/loleaflet/src/control/Control.ColumnHeader.js
@@ -378,6 +378,13 @@ L.Control.ColumnHeader = L.Control.Header.extend({
        viewRowColumnHeaders: function (e) {
                if (e.data.columns && e.data.columns.length > 0) {
                        this.fillColumns(e.data.columns, e.data.columnGroups, 
e.converter, e.context);
+                       this._onUpdateCurrentColumn(e.cursor);
+                       if (e.selection && e.selection.hasSelection) {
+                               this._onUpdateSelection(e.selection);
+                       }
+                       else {
+                               this._onClearSelection();
+                       }
                }
        },
 
diff --git a/loleaflet/src/control/Control.RowHeader.js 
b/loleaflet/src/control/Control.RowHeader.js
index d1f475723..0bf1fcb38 100644
--- a/loleaflet/src/control/Control.RowHeader.js
+++ b/loleaflet/src/control/Control.RowHeader.js
@@ -367,6 +367,13 @@ L.Control.RowHeader = L.Control.Header.extend({
        viewRowColumnHeaders: function (e) {
                if (e.data.rows && e.data.rows.length) {
                        this.fillRows(e.data.rows, e.data.rowGroups, 
e.converter, e.context);
+                       this._onUpdateCurrentRow(e.cursor);
+                       if (e.selection && e.selection.hasSelection) {
+                               this._onUpdateSelection(e.selection);
+                       }
+                       else {
+                               this._onClearSelection();
+                       }
                }
        },
 
diff --git a/loleaflet/src/layer/tile/CalcTileLayer.js 
b/loleaflet/src/layer/tile/CalcTileLayer.js
index 1e82f2598..684d559c1 100644
--- a/loleaflet/src/layer/tile/CalcTileLayer.js
+++ b/loleaflet/src/layer/tile/CalcTileLayer.js
@@ -373,6 +373,10 @@ L.CalcTileLayer = L.TileLayer.extend({
        },
 
        _onUpdateCurrentHeader: function() {
+               this._map.fire('updatecurrentheader', this._getCursorPosSize());
+       },
+
+       _getCursorPosSize: function () {
                var x = -1, y = -1;
                if (this._cellCursorXY) {
                        x = this._cellCursorXY.x + 1;
@@ -382,20 +386,30 @@ L.CalcTileLayer = L.TileLayer.extend({
                if (this._cellCursor && 
!this._isEmptyRectangle(this._cellCursor)) {
                        size = this._cellCursorTwips.getSize();
                }
-               this._map.fire('updatecurrentheader', {curX: x, curY: y, width: 
size.x, height: size.y});
+
+               return { curX: x, curY: y, width: size.x, height: size.y };
        },
 
        _onUpdateSelectionHeader: function () {
+               var selectionHeaderData = this._getSelectionHeaderData();
+               if (selectionHeaderData.hasSelection) {
+                       this._map.fire('updateselectionheader', 
selectionHeaderData);
+                       return;
+               }
+
+               this._map.fire('clearselectionheader');
+       },
+
+       _getSelectionHeaderData: function() {
                var layers = this._selections.getLayers();
                var layer = layers.pop();
                if (layers.length === 0 && layer && layer.getLatLngs().length 
=== 1) {
                        var start = 
this._latLngToTwips(layer.getBounds().getNorthWest()).add([1, 1]);
                        var end = 
this._latLngToTwips(layer.getBounds().getSouthEast()).subtract([1, 1]);
-                       this._map.fire('updateselectionheader', {start: start, 
end: end});
-               }
-               else {
-                       this._map.fire('clearselectionheader');
+                       return { hasSelection: true, start: start, end: end };
                }
+
+               return { hasSelection: false };
        },
 
        _onStatusMsg: function (textMsg) {
@@ -452,14 +466,14 @@ L.CalcTileLayer = L.TileLayer.extend({
 
                var comment;
                if (values.commandName === '.uno:ViewRowColumnHeaders') {
-//                     console.log('view row column headers: ' + 
JSON.stringify(values));
                        this._map.fire('viewrowcolumnheaders', {
                                data: values,
+                               cursor: this._getCursorPosSize(),
+                               selection: this._getSelectionHeaderData(),
                                converter: this._twipsToPixels,
                                context: this
                        });
-                       this._onUpdateCurrentHeader();
-                       this._onUpdateSelectionHeader();
+
                } else if (values.comments) {
                        this.clearAnnotations();
                        for (var index in values.comments) {
_______________________________________________
Libreoffice-commits mailing list
libreoffice-comm...@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits

Reply via email to