loleaflet/src/control/Control.ColumnHeader.js |  181 +++++++++-------
 loleaflet/src/control/Control.Header.js       |  294 +++++++++++++++++++++-----
 loleaflet/src/control/Control.RowHeader.js    |  166 ++++++++------
 3 files changed, 435 insertions(+), 206 deletions(-)

New commits:
commit e839abf5376713fb26bf8dc47fb0d31efcabb727
Author: Marco Cecchetti <marco.cecche...@collabora.com>
Date:   Thu Nov 9 18:58:09 2017 +0100

    loleaflet: sc: handle header data subdiveded in ranges
    
    Change-Id: I54b8bc80af91414d8a804e54a478a2eb452510e3
    Reviewed-on: https://gerrit.libreoffice.org/45417
    Reviewed-by: Jan Holesovsky <ke...@collabora.com>
    Tested-by: Jan Holesovsky <ke...@collabora.com>

diff --git a/loleaflet/src/control/Control.ColumnHeader.js 
b/loleaflet/src/control/Control.ColumnHeader.js
index 27765ec7..264fea4d 100644
--- a/loleaflet/src/control/Control.ColumnHeader.js
+++ b/loleaflet/src/control/Control.ColumnHeader.js
@@ -57,8 +57,7 @@ L.Control.ColumnHeader = L.Control.Header.extend({
                                        callback: function(key, options) {
                                                var index = 
colHeaderObj._lastMouseOverIndex;
                                                if (index) {
-                                                       var colAlpha = 
colHeaderObj._data[index].text;
-                                                       
colHeaderObj.insertColumn.call(colHeaderObj, colAlpha);
+                                                       
colHeaderObj.insertColumn.call(colHeaderObj, index);
                                                }
                                        }
                                },
@@ -67,8 +66,7 @@ L.Control.ColumnHeader = L.Control.Header.extend({
                                        callback: function(key, options) {
                                                var index = 
colHeaderObj._lastMouseOverIndex;
                                                if (index) {
-                                                       var colAlpha = 
colHeaderObj._data[index].text;
-                                                       
colHeaderObj.deleteColumn.call(colHeaderObj, colAlpha);
+                                                       
colHeaderObj.deleteColumn.call(colHeaderObj, index);
                                                }
                                        }
                                },
@@ -77,8 +75,7 @@ L.Control.ColumnHeader = L.Control.Header.extend({
                                        callback: function(key, options) {
                                                var index = 
colHeaderObj._lastMouseOverIndex;
                                                if (index) {
-                                                       var colAlpha = 
colHeaderObj._data[index].text;
-                                                       
colHeaderObj.optimalWidth.call(colHeaderObj, colAlpha);
+                                                       
colHeaderObj.optimalWidth.call(colHeaderObj, index);
                                                }
                                        }
                                },
@@ -87,8 +84,7 @@ L.Control.ColumnHeader = L.Control.Header.extend({
                                        callback: function(key, options) {
                                                var index = 
colHeaderObj._lastMouseOverIndex;
                                                if (index) {
-                                                       var colAlpha = 
colHeaderObj._data[index].text;
-                                                       
colHeaderObj.hideColumn.call(colHeaderObj, colAlpha);
+                                                       
colHeaderObj.hideColumn.call(colHeaderObj, index);
                                                }
                                        }
                                },
@@ -97,8 +93,7 @@ L.Control.ColumnHeader = L.Control.Header.extend({
                                        callback: function(key, options) {
                                                var index = 
colHeaderObj._lastMouseOverIndex;
                                                if (index) {
-                                                       var colAlpha = 
colHeaderObj._data[index].text;
-                                                       
colHeaderObj.showColumn.call(colHeaderObj, colAlpha);
+                                                       
colHeaderObj.showColumn.call(colHeaderObj, index);
                                                }
                                        }
                                }
@@ -107,50 +102,50 @@ L.Control.ColumnHeader = L.Control.Header.extend({
                });
        },
 
-       optimalWidth: function(colAlpha) {
+       optimalWidth: function(index) {
                if (!this._dialog) {
                        this._dialog = 
L.control.metricInput(this._onDialogResult, this,
                                                             
this._map._docLayer.twipsToHMM(this._map._docLayer.STD_EXTRA_WIDTH),
                                                             {title: _('Optimal 
Column Width')});
                }
                if (this._map._docLayer._selections.getLayers().length === 0) {
-                       this._selectColumn(colAlpha, 0);
+                       this._selectColumn(index, 0);
                }
                this._dialog.addTo(this._map);
                this._map.enable(false);
                this._dialog.show();
        },
 
-       insertColumn: function(colAlpha) {
+       insertColumn: function(index) {
                // First select the corresponding column because
                // .uno:InsertColumn doesn't accept any column number
                // as argument and just inserts before the selected column
                if (this._map._docLayer._selections.getLayers().length === 0) {
-                       this._selectColumn(colAlpha, 0);
+                       this._selectColumn(index, 0);
                }
                this._map.sendUnoCommand('.uno:InsertColumns');
                this._updateColumnHeader();
        },
 
-       deleteColumn: function(colAlpha) {
+       deleteColumn: function(index) {
                if (this._map._docLayer._selections.getLayers().length === 0) {
-                       this._selectColumn(colAlpha, 0);
+                       this._selectColumn(index, 0);
                }
                this._map.sendUnoCommand('.uno:DeleteColumns');
                this._updateColumnHeader();
        },
 
-       hideColumn: function(colAlpha) {
+       hideColumn: function(index) {
                if (this._map._docLayer._selections.getLayers().length === 0) {
-                       this._selectColumn(colAlpha, 0);
+                       this._selectColumn(index, 0);
                }
                this._map.sendUnoCommand('.uno:HideColumn');
                this._updateColumnHeader();
        },
 
-       showColumn: function(colAlpha) {
+       showColumn: function(index) {
                if (this._map._docLayer._selections.getLayers().length === 0) {
-                       this._selectColumn(colAlpha, 0);
+                       this._selectColumn(index, 0);
                }
                this._map.sendUnoCommand('.uno:ShowColumn');
                this._updateColumnHeader();
@@ -171,50 +166,44 @@ L.Control.ColumnHeader = L.Control.Header.extend({
        },
 
        _onUpdateSelection: function (e) {
-               var data = this._data;
-               if (!data)
-                       return;
                var start = e.start.x;
                var end = e.end.x;
-               var twips;
                if (start !== -1) {
-                       twips = new L.Point(start, start);
-                       start = Math.round(data.converter.call(data.context, 
twips).x);
+                       start = this._twipsToPixels(start);
                }
                if (end !== -1) {
-                       twips = new L.Point(end, end);
-                       end = Math.round(data.converter.call(data.context, 
twips).x);
+                       end = this._twipsToPixels(end);
                }
-               this.updateSelection(data, start, end);
+               this.updateSelection(this._data, start, end);
        },
 
        _onUpdateCurrentColumn: function (e) {
-               var data = this._data;
-               if (!data)
-                       return;
                var x = e.x;
                if (x !== -1) {
-                       var twips = new L.Point(x, x);
-                       x = Math.round(data.converter.call(data.context, 
twips).x);
+                       x = this._twipsToPixels(x);
                }
-               this.updateCurrent(data, x);
+               this.updateCurrent(this._data, x);
        },
 
        _updateColumnHeader: function () {
                this._map.fire('updaterowcolumnheaders', {x: 
this._map._getTopLeftPoint().x, y: 0, offset: {x: undefined, y: 0}});
        },
 
-       drawHeaderEntry: function (index, isOver) {
-               if (!index || index <= 0 || index >= this._data.length)
+       drawHeaderEntry: function (entry, isOver, isHighlighted) {
+               if (!entry)
                        return;
 
                var ctx = this._canvasContext;
-               var content = this._data[index].text;
-               var start = this._data[index - 1].pos - this._leftOffset;
-               var end = this._data[index].pos - this._leftOffset;
+               var content = this._colIndexToAlpha(entry.index + 
this._leftmostColumn);
+               var start = entry.pos - entry.size - this._leftOffset;
+               var end = entry.pos - this._leftOffset;
                var width = end - start;
                var height = this._headerCanvas.height;
-               var isHighlighted = this._data[index].selected;
+
+               if (isHighlighted !== true && isHighlighted !== false) {
+                       isHighlighted = this.isHighlighted(entry.index);
+               }
+
 
                if (width <= 0)
                        return;
@@ -249,22 +238,23 @@ L.Control.ColumnHeader = L.Control.Header.extend({
        },
 
        getHeaderEntryBoundingClientRect: function (index) {
-               if (!index)
-                       index = this._mouseOverIndex; // use last mouse over 
position
+               var entry = this._mouseOverEntry;
+               if (index)
+                       entry = this._data.get(index);
 
-               if (!index || !this._data[index])
+               if (!entry)
                        return;
 
                var rect = this._headerCanvas.getBoundingClientRect();
 
-               var colStart = this._data[index - 1].pos + this._position;
-               var colEnd = this._data[index].pos + this._position;
+               var colStart = entry.pos - entry.size + this._position;
+               var colEnd = entry.pos + this._position;
 
                var left = rect.left + colStart;
                var right = rect.left + colEnd;
                var top = rect.top;
                var bottom = rect.bottom;
-               return { left: left, right: right, top: top, bottom: bottom };
+               return {left: left, right: right, top: top, bottom: bottom};
        },
 
        viewRowColumnHeaders: function (e) {
@@ -274,31 +264,57 @@ L.Control.ColumnHeader = L.Control.Header.extend({
        },
 
        fillColumns: function (columns, converter, context) {
-               var iterator, twip, width;
+               if (columns.length < 2)
+                       return;
 
-               this._data = new Array(columns.length);
-               this._data.converter = converter;
-               this._data.context = context;
+               var entry, index, iterator, pos, width;
 
                var canvas = this._headerCanvas;
                canvas.width = 
parseInt(L.DomUtil.getStyle(this._headersContainer, 'width'));
                canvas.height = 
parseInt(L.DomUtil.getStyle(this._headersContainer, 'height'));
-
                this._canvasContext.clearRect(0, 0, canvas.width, 
canvas.height);
 
-               var leftmostOffset = new L.Point(columns[0].size, 
columns[0].size);
+               // update first header index and reset no more valid variables
                this._leftmostColumn = parseInt(columns[0].text);
-               this._leftOffset = Math.round(converter.call(context, 
leftmostOffset).x);
+               this._current = -1; // no more valid
+               this._selection.start = this._selection.end = -1; // no more 
valid
+               this._mouseOverEntry = null;
+               this._lastMouseOverIndex = undefined;
+
+               // create header data handler instance
+               this._data = new L.Control.Header.DataImpl();
+
+               // setup conversion routine
+               this.converter = L.Util.bind(converter, context);
+               this._data.converter = L.Util.bind(this._twipsToPixels, this);
+
+               var startOffsetTw = parseInt(columns[0].size);
+               this._leftOffset = this._twipsToPixels(startOffsetTw);
+
+               this._data.pushBack(0, {pos: startOffsetTw, size: 0});
+               var prevPos = startOffsetTw;
+               var nextIndex = parseInt(columns[1].text);
+               var last = columns.length - 1;
+               for (iterator = 1; iterator < last; iterator++) {
+                       index = nextIndex;
+                       pos = parseInt(columns[iterator].size);
+                       nextIndex = parseInt(columns[iterator+1].text);
+                       width = pos - prevPos;
+                       prevPos = Math.round(pos + width * (nextIndex - index - 
1));
+                       index = index - this._leftmostColumn;
+                       entry = {pos: pos, size: width};
+                       this._data.pushBack(index, entry);
+               }
 
-               this._data[0] = { pos: this._leftOffset, text: '', selected: 
false };
+               // setup last header entry
+               pos = parseInt(columns[last].size);
+               this._data.pushBack(nextIndex - this._leftmostColumn, {pos: 
pos, size: pos - prevPos});
 
-               for (iterator = 1; iterator < columns.length; iterator++) {
-                       twip = new L.Point(columns[iterator].size, 
columns[iterator].size);
-                       this._data[iterator] = { pos: 
Math.round(converter.call(context, twip).x), text: columns[iterator].text, 
selected: false };
-                       width = this._data[iterator].pos - this._data[iterator 
- 1].pos;
-                       if (width > 0) {
-                               this.drawHeaderEntry(iterator, false);
-                       }
+               // draw header
+               entry = this._data.getFirst();
+               while (entry) {
+                       this.drawHeaderEntry(entry, false);
+                       entry = this._data.getNext();
                }
 
                this.mouseInit(canvas);
@@ -320,13 +336,26 @@ L.Control.ColumnHeader = L.Control.Header.extend({
                return res;
        },
 
-       _selectColumn: function(colAlpha, modifier) {
-               var colNumber = this._colAlphaToNumber(colAlpha);
+       _colIndexToAlpha: function(columnNumber) {
+               var offset = 'A'.charCodeAt();
+               var dividend = columnNumber;
+               var columnName = '';
+               var modulo;
+
+               while (dividend > 0) {
+                       modulo = (dividend - 1) % 26;
+                       columnName = String.fromCharCode(offset + modulo) + 
columnName;
+                       dividend = Math.floor((dividend - modulo) / 26);
+               }
+
+               return columnName;
+       },
 
+       _selectColumn: function(colNumber, modifier) {
                var command = {
                        Col: {
                                type: 'unsigned short',
-                               value: parseInt(colNumber - 1)
+                               value: colNumber - 1
                        },
                        Modifier: {
                                type: 'unsigned short',
@@ -338,10 +367,10 @@ L.Control.ColumnHeader = L.Control.Header.extend({
        },
 
        _onHeaderClick: function (e) {
-               if (!this._mouseOverIndex)
+               if (!this._mouseOverEntry)
                        return;
 
-               var colAlpha = this._data[this._mouseOverIndex].text;
+               var col = this._mouseOverEntry.index + this._leftmostColumn;
 
                var modifier = 0;
                if (e.shiftKey) {
@@ -351,7 +380,7 @@ L.Control.ColumnHeader = L.Control.Header.extend({
                        modifier += this._map.keyboard.keyModifier.ctrl;
                }
 
-               this._selectColumn(colAlpha, modifier);
+               this._selectColumn(col, modifier);
        },
 
        _onCornerHeaderClick: function() {
@@ -403,14 +432,14 @@ L.Control.ColumnHeader = L.Control.Header.extend({
                var end = new L.Point(e.clientX + offset.x, e.clientY);
                var distance = 
this._map._docLayer._pixelsToTwips(end.subtract(start));
 
-               if (this._mouseOverIndex) {
-                       var clickedColumn = this._data[this._mouseOverIndex];
-                       var width = clickedColumn.pos - 
this._data[this._mouseOverIndex - 1];
-                       var column = this._mouseOverIndex + 
this._leftmostColumn;
+               var clickedColumn = this._mouseOverEntry;
+               if (clickedColumn) {
+                       var width = clickedColumn.size;
+                       var column = clickedColumn.index + this._leftmostColumn;
 
-                       if (this._data[this._mouseOverIndex + 1]
-                               && this._data[this._mouseOverIndex + 1].pos === 
clickedColumn.pos) {
+                       if (this._data.isZeroSize(clickedColumn.index + 1)) {
                                column += 1;
+                               width = 0;
                        }
 
                        if (width !== distance.x) {
@@ -436,11 +465,11 @@ L.Control.ColumnHeader = L.Control.Header.extend({
        onDragClick: function (item, clicks, e) {
                this._map.removeLayer(this._vertLine);
 
-               if (!this._mouseOverIndex)
+               if (!this._mouseOverEntry)
                        return;
 
                if (clicks === 2) {
-                       var column = this._mouseOverIndex + 
this._leftmostColumn;
+                       var column = this._mouseOverEntry.index + 
this._leftmostColumn;
                        var command = {
                                Col: {
                                        type: 'unsigned short',
diff --git a/loleaflet/src/control/Control.Header.js 
b/loleaflet/src/control/Control.Header.js
index a94663a2..d7689ee0 100644
--- a/loleaflet/src/control/Control.Header.js
+++ b/loleaflet/src/control/Control.Header.js
@@ -8,11 +8,13 @@ L.Control.Header = L.Control.extend({
        },
 
        initialize: function () {
+               this.converter = null;
+
                this._headerCanvas = null;
                this._clicks = 0;
                this._current = -1;
                this._selection = {start: -1, end: -1};
-               this._mouseOverIndex = undefined;
+               this._mouseOverEntry = null;
                this._lastMouseOverIndex = undefined;
                this._hitResizeArea = false;
 
@@ -77,65 +79,82 @@ L.Control.Header = L.Control.extend({
                L.DomEvent.on(element, 'mousedown', this._onMouseDown, this);
        },
 
-       select: function (data, index) {
-               if (!data[index])
-                       return;
-               data[index].selected = true;
-               this.drawHeaderEntry(index, false);
+       select: function (entry) {
+               this.drawHeaderEntry(entry, /*isOver=*/false, 
/*isHighlighted=*/true);
        },
 
-       unselect: function (data, index) {
-               if (!data[index])
-                       return;
-               data[index].selected = false;
-               this.drawHeaderEntry(index, false);
+       unselect: function (entry) {
+               this.drawHeaderEntry(entry, /*isOver=*/false, 
/*isHighlighted=*/false);
+       },
+
+       isHighlighted: function (index) {
+               if (this._selection.start === -1 && this._selection.end === -1) 
{
+                       return index === this._current;
+               }
+               return (this._selection.start <= index && index <= 
this._selection.end);
        },
 
        clearSelection: function (data) {
                if (this._selection.start === -1 && this._selection.end === -1)
                        return;
-               var start = (this._selection.start === -1) ? 0 : 
this._selection.start;
+               var start = (this._selection.start < 1) ? 1 : 
this._selection.start;
                var end = this._selection.end + 1;
-               for (var iterator = start; iterator < end; iterator++) {
-                       this.unselect(data, iterator);
+
+               var entry = data.getAt(start);
+
+               while (entry && entry.index < end) {
+                       this.unselect(entry);
+                       entry = data.getNext(start);
                }
 
                this._selection.start = this._selection.end = -1;
                // after clearing selection, we need to select the header entry 
for the current cursor position,
                // since we can't be sure that the selection clearing is due to 
click on a cell
                // different from the one where the cursor is already placed
-               this.select(data, this._current);
+               this.select(data.get(this._current));
        },
 
        updateSelection: function(data, start, end) {
-               if (!data)
+               if (!data || data.isEmpty())
                        return;
 
                var x0 = 0, x1 = 0;
                var itStart = -1, itEnd = -1;
                var selected = false;
-               var iterator = 0;
-               for (var len = data.length; iterator < len; iterator++) {
-                       x0 = (iterator > 0 ? data[iterator - 1].pos : 0);
-                       x1 = data[iterator].pos;
-                       // 'start < x1' not '<=' or we get highlighted also the 
`start-row - 1` and `start-column - 1` headers
+
+               // if the start selection position is above/on the left of the 
first header entry,
+               // but the end selection position is below/on the right of it
+               // then we set the start selected entry to the first header 
entry.
+               var entry = data.getFirst();
+               if (entry) {
+                       x0 = entry.pos - entry.size;
+                       if (start < x0 && end > x0) {
+                               selected = true;
+                               itStart = 1;
+                       }
+               }
+
+               while (entry) {
+                       x0 = entry.pos - entry.size;
+                       x1 = entry.pos;
                        if (x0 <= start && start < x1) {
                                selected = true;
-                               itStart = iterator;
+                               itStart = entry.index;
                        }
                        if (selected) {
-                               this.select(data, iterator);
+                               this.select(entry);
                        }
                        if (x0 <= end && end <= x1) {
-                               itEnd = iterator;
+                               itEnd = entry.index;
                                break;
                        }
+                       entry = data.getNext();
                }
 
                // if end is greater than the last fetched header position set 
itEnd to the max possible value
                // without this hack selecting a whole row and then a whole 
column (or viceversa) leads to an incorrect selection
                if (itStart !== -1 && itEnd === -1) {
-                       itEnd = data.length - 1;
+                       itEnd = data.getLength() - 1;
                }
 
                // we need to unselect the row (column) header entry for the 
current cell cursor position
@@ -143,17 +162,22 @@ L.Control.Header = L.Control.extend({
                // does not start by clicking on a cell
                if (this._current !== -1 && itStart !== -1 && itEnd !== -1) {
                        if (this._current < itStart || this._current > itEnd) {
-                               this.unselect(data, this._current);
+                               this.unselect(data.get(this._current));
                        }
                }
+
                if (this._selection.start !== -1 && itStart !== -1 && itStart > 
this._selection.start) {
-                       for (iterator = this._selection.start; iterator < 
itStart; iterator++) {
-                               this.unselect(data, iterator);
+                       entry = data.getAt(this._selection.start);
+                       while (entry && entry.index < itStart) {
+                               this.unselect(entry);
+                               entry = data.getNext();
                        }
                }
                if (this._selection.end !== -1 && itEnd !== -1 && itEnd < 
this._selection.end) {
-                       for (iterator = itEnd + 1; iterator <= 
this._selection.end; iterator++) {
-                               this.unselect(data, iterator);
+                       entry = data.getAt(itEnd + 1);
+                       while (entry && entry.index <= this._selection.end) {
+                               this.unselect(entry);
+                               entry = data.getNext();
                        }
                }
                this._selection.start = itStart;
@@ -161,30 +185,33 @@ L.Control.Header = L.Control.extend({
        },
 
        updateCurrent: function (data, start) {
-               if (!data)
+               if (!data || data.isEmpty())
                        return;
+
                if (start < 0) {
-                       this.unselect(data, this._current);
+                       this.unselect(data.get(this._current));
                        this._current = -1;
                        return;
                }
 
                var x0 = 0, x1 = 0;
-               for (var iterator = 1, len = data.length; iterator < len; 
iterator++) {
-                       x0 = (iterator > 0 ? data[iterator - 1].pos : 0);
-                       x1 = data[iterator].pos;
+               var entry = data.getFirst();
+               while (entry) {
+                       x0 = entry.pos - entry.size;
+                       x1 = entry.pos;
                        if (x0 <= start && start < x1) {
                                // when a whole row (column) is selected the 
cell cursor is moved to the first column (row)
                                // but this action should not cause to 
select/unselect anything, on the contrary we end up
                                // with all column (row) header entries 
selected but the one where the cell cursor was
                                // previously placed
                                if (this._selection.start === -1 && 
this._selection.end === -1) {
-                                       this.unselect(data, this._current);
-                                       this.select(data, iterator);
+                                       this.unselect(data.get(this._current));
+                                       this.select(entry);
                                }
-                               this._current = iterator;
+                               this._current = entry.index;
                                break;
                        }
+                       entry = data.getNext();
                }
        },
 
@@ -197,10 +224,10 @@ L.Control.Header = L.Control.extend({
        },
 
        _onMouseOut: function (e) {
-               if (this._mouseOverIndex) {
-                       this.drawHeaderEntry(this._mouseOverIndex, false);
-                       this._lastMouseOverIndex = this._mouseOverIndex; // 
used by context menu
-                       this._mouseOverIndex = undefined;
+               if (this._mouseOverEntry) {
+                       this.drawHeaderEntry(this._mouseOverEntry, false);
+                       this._lastMouseOverIndex = this._mouseOverEntry.index; 
// used by context menu
+                       this._mouseOverEntry = null;
                }
                this._hitResizeArea = false;
                L.DomUtil.setStyle(this._headerCanvas, 'cursor', this._cursor);
@@ -217,25 +244,24 @@ L.Control.Header = L.Control.extend({
                var pos = 
this._getPos(this._mouseEventToCanvasPos(this._headerCanvas, e));
                pos = pos - this._position;
 
-               var mouseOverIndex = this._mouseOverIndex;
-               for (var iterator = 1; iterator < this._data.length; 
++iterator) {
-                       var start = this._data[iterator - 1].pos;
-                       var end = this._data[iterator].pos;
+               var mouseOverIndex = this._mouseOverEntry ? 
this._mouseOverEntry.index : undefined;
+               var entry = this._data.getFirst();
+               while (entry) {
+                       var start = entry.pos - entry.size;
+                       var end = entry.pos;
                        if (pos > start && pos <= end) {
-                               mouseOverIndex = iterator;
+                               mouseOverIndex = entry.index;
                                var resizeAreaStart = Math.max(start, end - 3);
                                isMouseOverResizeArea = (pos > resizeAreaStart);
                                break;
                        }
+                       entry = this._data.getNext();
                }
 
-               if (mouseOverIndex !== this._mouseOverIndex) {
-                       if (this._mouseOverIndex) {
-                               this.drawHeaderEntry(this._mouseOverIndex, 
false);
-                       }
-                       if (mouseOverIndex) {
-                               this.drawHeaderEntry(mouseOverIndex, true);
-                       }
+               if (mouseOverIndex && (!this._mouseOverEntry || mouseOverIndex 
!== this._mouseOverEntry.index)) {
+                       this.drawHeaderEntry(this._mouseOverEntry, false);
+                       this.drawHeaderEntry(entry, true);
+                       this._mouseOverEntry = entry;
                }
 
                if (isMouseOverResizeArea !== this._hitResizeArea) {
@@ -249,8 +275,6 @@ L.Control.Header = L.Control.extend({
                        L.DomUtil.setStyle(this._headerCanvas, 'cursor', 
cursor);
                        this._hitResizeArea = isMouseOverResizeArea;
                }
-
-               this._mouseOverIndex = mouseOverIndex;
        },
 
        _onMouseDown: function (e) {
@@ -311,6 +335,13 @@ L.Control.Header = L.Control.extend({
                this._dragging = false;
        },
 
+       _twipsToPixels: function (twips) {
+               if (!this.converter)
+                       return 0;
+               var point = new L.Point(twips, twips);
+               return Math.round(this._getPos(this.converter(point)));
+       },
+
        onDragStart: function () {},
        onDragMove: function () {},
        onDragEnd: function () {},
@@ -319,3 +350,154 @@ L.Control.Header = L.Control.extend({
        drawHeaderEntry: function () {},
        _getPos: function () {}
 });
+
+(function () {
+
+       L.Control.Header.DataImpl = L.Class.extend({
+               initialize: function () {
+                       this.converter = null;
+
+                       this._currentIndex = undefined;
+                       this._currentRange = undefined;
+                       this._dataMap = {};
+                       this._indexes = [];
+                       this._endIndex = -1;
+                       this._skipZeroSize = true;
+               },
+
+               _get: function (index, setCurrentIndex) {
+                       if (index < 1 || index > this._endIndex)
+                               return null;
+
+                       var range = this._getFirstIndexLessOrEqual(index);
+                       if (range !== undefined) {
+                               if (setCurrentIndex) {
+                                       this._currentRange = range;
+                                       this._currentIndex = index;
+                               }
+                               return this._computeEntry(this._indexes[range], 
index);
+                       }
+               },
+
+               get: function (index) {
+                       return this._get(index, false);
+               },
+
+               getAt: function (index) {
+                       return this._get(index, true);
+               },
+
+               getFirst: function () {
+                       this._currentRange = 0;
+                       this._currentIndex = this._indexes[this._currentRange];
+                       return this.getNext();
+               },
+
+               getNext: function () {
+                       if (this._currentIndex === undefined || 
this._currentRange === undefined)
+                               return null; // you need to call getFirst on 
initial step
+
+                       this._currentIndex += 1;
+                       if (this._currentIndex >= this._endIndex) {
+                               // we iterated over all entries, reset 
everything
+                               this._currentIndex = undefined;
+                               this._currentRange = undefined;
+                               this._skipZeroSize = false;
+                               return null;
+                       }
+
+                       if (this._indexes[this._currentRange+1] === 
this._currentIndex) {
+                               // new range
+                               this._currentRange += 1;
+
+                               if (this._skipZeroSize) {
+                                       var index, i, len = 
this._indexes.length;
+                                       for (i = this._currentRange; i < len; 
++i) {
+                                               index = this._indexes[i];
+                                               if (this._dataMap[index].size > 
0) {
+                                                       break;
+                                               }
+                                       }
+                                       this._currentRange = i;
+                                       this._currentIndex = index;
+                               }
+                       }
+
+                       var startIndex = this._indexes[this._currentRange];
+                       return this._computeEntry(startIndex, 
this._currentIndex);
+               },
+
+               pushBack: function (index, value) {
+                       if (index <= this._endIndex)
+                               return;
+                       this._dataMap[index] = value;
+                       this._indexes.push(index);
+                       this._endIndex = index;
+               },
+
+               isZeroSize: function (index) {
+                       if (!(index > 0 && index < this._endIndex)) {
+                               return true;
+                       }
+
+                       var range = this._getFirstIndexLessOrEqual(index);
+                       return this._dataMap[this._indexes[range]].size === 0;
+               },
+
+               getLength: function () {
+                       return this._endIndex;
+               },
+
+               isEmpty: function () {
+                       return  this._indexes.length === 0;
+               },
+
+               _binaryIndexOf: function (collection, searchElement) {
+                       var minIndex = 0;
+                       var maxIndex = collection.length - 1;
+                       var currentIndex;
+                       var currentElement;
+
+                       while (minIndex <= maxIndex) {
+                               currentIndex = (minIndex + maxIndex) / 2 | 0;
+                               currentElement = collection[currentIndex];
+
+                               if (currentElement < searchElement) {
+                                       minIndex = currentIndex + 1;
+                               }
+                               else if (currentElement > searchElement) {
+                                       maxIndex = currentIndex - 1;
+                               }
+                               else {
+                                       return currentIndex;
+                               }
+                       }
+
+                       if (currentIndex > maxIndex)
+                               return currentIndex - 1;
+                       if (currentIndex < minIndex)
+                               return currentIndex;
+               },
+
+               _getFirstIndexLessOrEqual: function (index) {
+                       return this._binaryIndexOf(this._indexes, index);
+               },
+
+               _twipsToPixels: function (twips) {
+                       if (!this.converter)
+                               return 0;
+
+                       return this.converter(twips);
+               },
+
+               _computeEntry: function (startIndex, index) {
+                       var entry = this._dataMap[startIndex];
+                       var pos = entry.pos + (index - startIndex) * entry.size;
+                       pos = this._twipsToPixels(pos);
+                       var size = this._twipsToPixels(entry.size);
+                       return {index: index, pos: pos, size: size};
+               }
+
+       });
+
+})();
\ No newline at end of file
diff --git a/loleaflet/src/control/Control.RowHeader.js 
b/loleaflet/src/control/Control.RowHeader.js
index 520a0893..4763f1b1 100644
--- a/loleaflet/src/control/Control.RowHeader.js
+++ b/loleaflet/src/control/Control.RowHeader.js
@@ -55,8 +55,7 @@ L.Control.RowHeader = L.Control.Header.extend({
                                        callback: function(key, options) {
                                                var index = 
rowHeaderObj._lastMouseOverIndex;
                                                if (index) {
-                                                       var row = 
rowHeaderObj._data[index].text;
-                                                       
rowHeaderObj.insertRow.call(rowHeaderObj, row);
+                                                       
rowHeaderObj.insertRow.call(rowHeaderObj, index);
                                                }
                                        }
                                },
@@ -65,8 +64,7 @@ L.Control.RowHeader = L.Control.Header.extend({
                                        callback: function(key, options) {
                                                var index = 
rowHeaderObj._lastMouseOverIndex;
                                                if (index) {
-                                                       var row = 
rowHeaderObj._data[index].text;
-                                                       
rowHeaderObj.deleteRow.call(rowHeaderObj, row);
+                                                       
rowHeaderObj.deleteRow.call(rowHeaderObj, index);
                                                }
                                        }
                                },
@@ -75,8 +73,7 @@ L.Control.RowHeader = L.Control.Header.extend({
                                        callback: function(key, options) {
                                                var index = 
rowHeaderObj._lastMouseOverIndex;
                                                if (index) {
-                                                       var row = 
rowHeaderObj._data[index].text;
-                                                       
rowHeaderObj.optimalHeight.call(rowHeaderObj, row);
+                                                       
rowHeaderObj.optimalHeight.call(rowHeaderObj, index);
                                                }
                                        }
                                },
@@ -85,8 +82,7 @@ L.Control.RowHeader = L.Control.Header.extend({
                                        callback: function(key, options) {
                                                var index = 
rowHeaderObj._lastMouseOverIndex;
                                                if (index) {
-                                                       var row = 
rowHeaderObj._data[index].text;
-                                                       
rowHeaderObj.hideRow.call(rowHeaderObj, row);
+                                                       
rowHeaderObj.hideRow.call(rowHeaderObj, index);
                                                }
                                        }
                                },
@@ -95,8 +91,7 @@ L.Control.RowHeader = L.Control.Header.extend({
                                        callback: function(key, options) {
                                                var index = 
rowHeaderObj._lastMouseOverIndex;
                                                if (index) {
-                                                       var row = 
rowHeaderObj._data[index].text;
-                                                       
rowHeaderObj.showRow.call(rowHeaderObj, row);
+                                                       
rowHeaderObj.showRow.call(rowHeaderObj, index);
                                                }
                                        }
                                }
@@ -105,45 +100,45 @@ L.Control.RowHeader = L.Control.Header.extend({
                });
        },
 
-       optimalHeight: function(row) {
+       optimalHeight: function(index) {
                if (!this._dialog) {
                        this._dialog = 
L.control.metricInput(this._onDialogResult, this, 0, {title: _('Optimal Row 
Height')});
                }
                if (this._map._docLayer._selections.getLayers().length === 0) {
-                       this._selectRow(row, 0);
+                       this._selectRow(index, 0);
                }
                this._dialog.addTo(this._map);
                this._map.enable(false);
                this._dialog.show();
        },
 
-       insertRow: function(row) {
+       insertRow: function(index) {
                // First select the corresponding row because
                // .uno:InsertRows doesn't accept any row number
                // as argument and just inserts before the selected row
                if (this._map._docLayer._selections.getLayers().length === 0) {
-                       this._selectRow(row, 0);
+                       this._selectRow(index, 0);
                }
                this._map.sendUnoCommand('.uno:InsertRows');
        },
 
-       deleteRow: function(row) {
+       deleteRow: function(index) {
                if (this._map._docLayer._selections.getLayers().length === 0) {
-                       this._selectRow(row, 0);
+                       this._selectRow(index, 0);
                }
                this._map.sendUnoCommand('.uno:DeleteRows');
        },
 
-       hideRow: function(row) {
+       hideRow: function(index) {
                if (this._map._docLayer._selections.getLayers().length === 0) {
-                       this._selectRow(row, 0);
+                       this._selectRow(index, 0);
                }
                this._map.sendUnoCommand('.uno:HideRow');
        },
 
-       showRow: function(row) {
+       showRow: function(index) {
                if (this._map._docLayer._selections.getLayers().length === 0) {
-                       this._selectRow(row, 0);
+                       this._selectRow(index, 0);
                }
                this._map.sendUnoCommand('.uno:ShowRow');
        },
@@ -163,50 +158,43 @@ L.Control.RowHeader = L.Control.Header.extend({
        },
 
        _onUpdateSelection: function (e) {
-               var data = this._data;
-               if (!data)
-                       return;
                var start = e.start.y;
                var end = e.end.y;
-               var twips;
                if (start !== -1) {
-                       twips = new L.Point(start, start);
-                       start = Math.round(data.converter.call(data.context, 
twips).y);
+                       start = this._twipsToPixels(start);
                }
                if (end !== -1) {
-                       twips = new L.Point(end, end);
-                       end = Math.round(data.converter.call(data.context, 
twips).y);
+                       end = this._twipsToPixels(end);
                }
-               this.updateSelection(data, start, end);
+               this.updateSelection(this._data, start, end);
        },
 
        _onUpdateCurrentRow: function (e) {
-               var data = this._data;
-               if (!data)
-                       return;
                var y = e.y;
                if (y !== -1) {
-                       var twips = new L.Point(y, y);
-                       y = Math.round(data.converter.call(data.context, 
twips).y);
+                       y = this._twipsToPixels(y);
                }
-               this.updateCurrent(data, y);
+               this.updateCurrent(this._data, y);
        },
 
        _updateRowHeader: function () {
                this._map.fire('updaterowcolumnheaders', {x: 0, y: 
this._map._getTopLeftPoint().y, offset: {x: 0, y: undefined}});
        },
 
-       drawHeaderEntry: function (index, isOver) {
-               if (!index || index <= 0 || index >= this._data.length)
+       drawHeaderEntry: function (entry, isOver, isHighlighted) {
+               if (!entry)
                        return;
 
                var ctx = this._canvasContext;
-               var content = this._data[index].text;
-               var start = this._data[index - 1].pos - this._topOffset;
-               var end = this._data[index].pos - this._topOffset;
+               var content = entry.index + this._topRow;
+               var start = entry.pos - entry.size - this._topOffset;
+               var end = entry.pos - this._topOffset;
                var height = end - start;
                var width = this._headerCanvas.width;
-               var isHighlighted = this._data[index].selected;
+
+               if (isHighlighted !== true && isHighlighted !== false) {
+                       isHighlighted = this.isHighlighted(entry.index);
+               }
 
                if (height <= 0)
                        return;
@@ -241,22 +229,23 @@ L.Control.RowHeader = L.Control.Header.extend({
        },
 
        getHeaderEntryBoundingClientRect: function (index) {
-               if (!index)
-                       index = this._mouseOverIndex; // use last mouse over 
position
+               var entry = this._mouseOverEntry;
+               if (index)
+                       entry = this._data.get(index);
 
-               if (!index || !this._data[index])
+               if (!entry)
                        return;
 
                var rect = this._headerCanvas.getBoundingClientRect();
 
-               var rowStart = this._data[index - 1].pos + this._position;
-               var rowEnd = this._data[index].pos + this._position;
+               var rowStart = entry.pos - entry.size + this._position;
+               var rowEnd = entry.pos + this._position;
 
                var left = rect.left;
                var right = rect.right;
                var top = rect.top + rowStart;
                var bottom = rect.top + rowEnd;
-               return { left: left, right: right, top: top, bottom: bottom };
+               return {left: left, right: right, top: top, bottom: bottom};
        },
 
        viewRowColumnHeaders: function (e) {
@@ -266,31 +255,60 @@ L.Control.RowHeader = L.Control.Header.extend({
        },
 
        fillRows: function (rows, converter, context) {
-               var iterator, twip, height;
+               if (rows.length < 2)
+                       return;
 
-               this._data = new Array(rows.length);
-               this._data.converter = converter;
-               this._data.context = context;
+               var entry, index, iterator, height, pos;
 
                var canvas = this._headerCanvas;
                canvas.width = 
parseInt(L.DomUtil.getStyle(this._headersContainer, 'width'));
                canvas.height = 
parseInt(L.DomUtil.getStyle(this._headersContainer, 'height'));
-
                this._canvasContext.clearRect(0, 0, canvas.width, 
canvas.height);
 
-               var topOffset = new L.Point(rows[0].size, rows[0].size);
+               // update first header index and reset no more valid variables
                this._topRow = parseInt(rows[0].text);
-               this._topOffset = Math.round(converter.call(context, 
topOffset).y);
-
-               this._data[0] = { pos: this._topOffset, text: '', selected: 
false };
+               this._current = -1;
+               this._selection.start = this._selection.end = -1;
+               this._mouseOverEntry = null;
+               this._lastMouseOverIndex = undefined;
+
+               // create header data handler instance
+               this._data = new L.Control.Header.DataImpl();
+
+               // setup conversion routine
+               this.converter = L.Util.bind(converter, context);
+               this._data.converter = L.Util.bind(this._twipsToPixels, this);
+
+               var startOffsetTw = parseInt(rows[0].size);
+               this._topOffset = this._twipsToPixels(startOffsetTw);
+
+               this._data.pushBack(0, {pos: startOffsetTw, size: 0});
+               var prevPos = startOffsetTw;
+               var nextIndex = parseInt(rows[1].text);
+               var last = rows.length - 1;
+
+               for (iterator = 1; iterator < last; iterator++) {
+                       index = nextIndex;
+                       pos = parseInt(rows[iterator].size);
+                       nextIndex = parseInt(rows[iterator+1].text);
+                       height = pos - prevPos;
+                       prevPos = Math.round(pos + height * (nextIndex - index 
- 1));
+                       index = index - this._topRow;
+                       entry = {pos: pos, size: height};
+                       this._data.pushBack(index, entry);
+               }
 
-               for (iterator = 1; iterator < rows.length; iterator++) {
-                       twip = new L.Point(rows[iterator].size, 
rows[iterator].size);
-                       this._data[iterator] = { pos: 
Math.round(converter.call(context, twip).y), text: rows[iterator].text, 
selected: false };
-                       height = this._data[iterator].pos - this._data[iterator 
- 1].pos;
-                       if (height > 0) {
-                               this.drawHeaderEntry(iterator, false);
-                       }
+               // setup last header entry
+               index = nextIndex - this._topRow;
+               pos = parseInt(rows[last].size);
+               height = pos - prevPos;
+               this._data.pushBack(index, {pos: pos, size: height});
+
+               // draw header
+               entry = this._data.getFirst();
+               while (entry) {
+                       this.drawHeaderEntry(entry, false);
+                       entry = this._data.getNext();
                }
 
                this.mouseInit(canvas);
@@ -305,7 +323,7 @@ L.Control.RowHeader = L.Control.Header.extend({
                var command = {
                        Row: {
                                type: 'long',
-                               value: parseInt(row - 1)
+                               value: row - 1
                        },
                        Modifier: {
                                type: 'unsigned short',
@@ -317,10 +335,10 @@ L.Control.RowHeader = L.Control.Header.extend({
        },
 
        _onHeaderClick: function (e) {
-               if (!this._mouseOverIndex)
+               if (!this._mouseOverEntry)
                        return;
 
-               var row = this._mouseOverIndex + this._topRow;
+               var row = this._mouseOverEntry.index + this._topRow;
 
                var modifier = 0;
                if (e.shiftKey) {
@@ -378,14 +396,14 @@ L.Control.RowHeader = L.Control.Header.extend({
                var end = new L.Point(e.clientX, e.clientY + offset.y);
                var distance = 
this._map._docLayer._pixelsToTwips(end.subtract(start));
 
-               if (this._mouseOverIndex) {
-                       var clickedRow = this._data[this._mouseOverIndex];
-                       var height = clickedRow.pos - 
this._data[this._mouseOverIndex - 1];
-                       var row = this._mouseOverIndex + this._topRow;
+               var clickedRow = this._mouseOverEntry;
+               if (clickedRow) {
+                       var height = clickedRow.size;
+                       var row = clickedRow.index + this._topRow;
 
-                       if (this._data[this._mouseOverIndex + 1]
-                               && this._data[this._mouseOverIndex + 1].pos === 
clickedRow.pos) {
+                       if (this._data.isZeroSize(clickedRow.index + 1)) {
                                row += 1;
+                               height = 0;
                        }
 
                        if (height !== distance.y) {
@@ -410,11 +428,11 @@ L.Control.RowHeader = L.Control.Header.extend({
        onDragClick: function (item, clicks, e) {
                this._map.removeLayer(this._horzLine);
 
-               if (!this._mouseOverIndex)
+               if (!this._mouseOverEntry)
                        return;
 
                if (clicks === 2) {
-                       var row = this._mouseOverIndex + this._topRow;
+                       var row = this._mouseOverEntry.index + this._topRow;
                        var command = {
                                Row: {
                                        type: 'long',
_______________________________________________
Libreoffice-commits mailing list
libreoffice-comm...@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits

Reply via email to