loleaflet/src/control/Control.ColumnHeader.js |  267 ++++++++++++++++++--------
 loleaflet/src/control/Control.Header.js       |  169 ++++++++++++----
 loleaflet/src/control/Control.RowHeader.js    |  263 ++++++++++++++++++-------
 3 files changed, 515 insertions(+), 184 deletions(-)

New commits:
commit 3dee70e3c4874abd6e7927b250d38d04d879c2bf
Author: Marco Cecchetti <marco.cecche...@collabora.com>
Date:   Thu Oct 26 12:00:05 2017 +0200

    loleaflet: now Calc headers are rendered throught Canvas 2d
    
    Change-Id: I5ab0d9a4af7a8bdba3eb0d07e89c811f7b1850b4
    Reviewed-on: https://gerrit.libreoffice.org/43880
    Reviewed-by: Marco Cecchetti <mrcek...@gmail.com>
    Tested-by: Marco Cecchetti <mrcek...@gmail.com>

diff --git a/loleaflet/src/control/Control.ColumnHeader.js 
b/loleaflet/src/control/Control.ColumnHeader.js
index a9571d25..6d86d7fa 100644
--- a/loleaflet/src/control/Control.ColumnHeader.js
+++ b/loleaflet/src/control/Control.ColumnHeader.js
@@ -25,50 +25,76 @@ L.Control.ColumnHeader = L.Control.Header.extend({
                var cornerHeader = L.DomUtil.create('div', 
'spreadsheet-header-corner', rowColumnFrame);
                L.DomEvent.on(cornerHeader, 'contextmenu', 
L.DomEvent.preventDefault);
                L.DomEvent.addListener(cornerHeader, 'click', 
this._onCornerHeaderClick, this);
-               var headersContainer = L.DomUtil.create('div', 
'spreadsheet-header-columns-container', rowColumnFrame);
-               this._columns = L.DomUtil.create('div', 
'spreadsheet-header-columns', headersContainer);
+               this._headersContainer = L.DomUtil.create('div', 
'spreadsheet-header-columns-container', rowColumnFrame);
 
+               this._headerCanvas = L.DomUtil.create('canvas', 
'spreadsheet-header-columns', this._headersContainer);
+               this._canvasContext = this._headerCanvas.getContext('2d');
+               this._headerCanvas.width = 
parseInt(L.DomUtil.getStyle(this._headersContainer, 'width'));
+               this._headerCanvas.height = 
parseInt(L.DomUtil.getStyle(this._headersContainer, 'height'));
+
+               L.DomUtil.setStyle(this._headerCanvas, 'cursor', this._cursor);
+
+               L.DomEvent.on(this._headerCanvas, 'mousemove', 
this._onCanvasMouseMove, this);
+               L.DomEvent.on(this._headerCanvas, 'mouseout', this._onMouseOut, 
this);
+               L.DomEvent.on(this._headerCanvas, 'click', this._onHeaderClick, 
this);
+
+               this._leftmostColumn = 0;
                this._leftOffset = 0;
                this._position = 0;
 
                var colHeaderObj = this;
                $.contextMenu({
-                       selector: '.spreadsheet-header-column-text',
+                       selector: '.spreadsheet-header-columns',
                        className: 'loleaflet-font',
                        items: {
                                'insertcolbefore': {
                                        name: _('Insert column before'),
                                        callback: function(key, options) {
-                                               var colAlpha = 
options.$trigger.attr('rel').split('spreadsheet-column-')[1];
-                                               
colHeaderObj.insertColumn.call(colHeaderObj, colAlpha);
+                                               var index = 
colHeaderObj._lastMouseOverIndex;
+                                               if (index) {
+                                                       var colAlpha = 
colHeaderObj._data[index].text;
+                                                       
colHeaderObj.insertColumn.call(colHeaderObj, colAlpha);
+                                               }
                                        }
                                },
                                'deleteselectedcol': {
                                        name: _('Delete column'),
                                        callback: function(key, options) {
-                                               var colAlpha = 
options.$trigger.attr('rel').split('spreadsheet-column-')[1];
-                                               
colHeaderObj.deleteColumn.call(colHeaderObj, colAlpha);
+                                               var index = 
colHeaderObj._lastMouseOverIndex;
+                                               if (index) {
+                                                       var colAlpha = 
colHeaderObj._data[index].text;
+                                                       
colHeaderObj.deleteColumn.call(colHeaderObj, colAlpha);
+                                               }
                                        }
                                },
                                'optimalwidth': {
                                        name: _('Optimal Width') + '...',
                                        callback: function(key, options) {
-                                               var colAlpha = 
options.$trigger.attr('rel').split('spreadsheet-column-')[1];
-                                               
colHeaderObj.optimalWidth.call(colHeaderObj, colAlpha);
+                                               var index = 
colHeaderObj._lastMouseOverIndex;
+                                               if (index) {
+                                                       var colAlpha = 
colHeaderObj._data[index].text;
+                                                       
colHeaderObj.optimalWidth.call(colHeaderObj, colAlpha);
+                                               }
                                        }
                                },
                                'hideColumn': {
                                        name: _('Hide Columns'),
                                        callback: function(key, options) {
-                                               var colAlpha = 
options.$trigger.attr('rel').split('spreadsheet-column-')[1];
-                                               
colHeaderObj.hideColumn.call(colHeaderObj, colAlpha);
+                                               var index = 
colHeaderObj._lastMouseOverIndex;
+                                               if (index) {
+                                                       var colAlpha = 
colHeaderObj._data[index].text;
+                                                       
colHeaderObj.hideColumn.call(colHeaderObj, colAlpha);
+                                               }
                                        }
                                },
                                'showColumn': {
                                        name: _('Show Columns'),
                                        callback: function(key, options) {
-                                               var colAlpha = 
options.$trigger.attr('rel').split('spreadsheet-column-')[1];
-                                               
colHeaderObj.showColumn.call(colHeaderObj, colAlpha);
+                                               var index = 
colHeaderObj._lastMouseOverIndex;
+                                               if (index) {
+                                                       var colAlpha = 
colHeaderObj._data[index].text;
+                                                       
colHeaderObj.showColumn.call(colHeaderObj, colAlpha);
+                                               }
                                        }
                                }
                        },
@@ -136,71 +162,145 @@ L.Control.ColumnHeader = L.Control.Header.extend({
        },
 
        _onClearSelection: function (e) {
-               this.clearSelection(this._columns);
+               this.clearSelection(this._data);
        },
 
        _onUpdateSelection: function (e) {
-               this.updateSelection(this._columns, e.start.x, e.end.x);
+               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);
+               }
+               if (end !== -1) {
+                       twips = new L.Point(end, end);
+                       end = Math.round(data.converter.call(data.context, 
twips).x);
+               }
+               this.updateSelection(data, start, end);
        },
 
        _onUpdateCurrentColumn: function (e) {
-               this.updateCurrent(this._columns, e.x);
+               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);
+               }
+               this.updateCurrent(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)
+                       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 width = end - start;
+               var height = this._headerCanvas.height;
+               var isHighlighted = this._data[index].selected;
+
+               if (width <= 0)
+                       return;
+
+               ctx.save();
+               ctx.translate(this._position + this._leftOffset, 0);
+               // background gradient
+               var selectionBackgroundGradient = null;
+               if (isHighlighted) {
+                       selectionBackgroundGradient = 
ctx.createLinearGradient(start, 0, start, height);
+                       selectionBackgroundGradient.addColorStop(0, 
this._selectionBackgroundGradient[0]);
+                       selectionBackgroundGradient.addColorStop(0.5, 
this._selectionBackgroundGradient[1]);
+                       selectionBackgroundGradient.addColorStop(1, 
this._selectionBackgroundGradient[2]);
+               }
+               // clip mask
+               ctx.beginPath();
+               ctx.rect(start, 0, width, height);
+               ctx.clip();
+               // draw background
+               ctx.fillStyle = isHighlighted ? selectionBackgroundGradient : 
isOver ? this._hoverColor : this._backgroundColor;
+               ctx.fillRect(start, 0, width, height);
+               // draw text content
+               ctx.fillStyle = isHighlighted ? this._selectionTextColor : 
this._textColor;
+               ctx.font = this._font;
+               ctx.textAlign = 'center';
+               ctx.textBaseline = 'middle';
+               ctx.fillText(content, end - width / 2, height / 2);
+               // draw row separator
+               ctx.fillStyle = this._borderColor;
+               ctx.fillRect(end -1, 0, 1, height);
+               ctx.restore();
+       },
+
+       getHeaderEntryBoundingClientRect: function (index) {
+               if (!index)
+                       index = this._mouseOverIndex; // use last mouse over 
position
+
+               if (!index || !this._data[index])
+                       return;
+
+               var rect = this._headerCanvas.getBoundingClientRect();
+
+               var colStart = this._data[index - 1].pos + this._position;
+               var colEnd = this._data[index].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 };
+       },
+
        viewRowColumnHeaders: function (e) {
                if (e.data.columns && e.data.columns.length > 0) {
                        this.fillColumns(e.data.columns, e.converter, 
e.context);
-                       L.DomUtil.setStyle(this._columns, 'left', 
(this._position + this._leftOffset) + 'px');
                }
        },
 
        fillColumns: function (columns, converter, context) {
-               var iterator, twip, width, column, text, resize;
+               var iterator, twip, width;
+
+               this._data = new Array(columns.length);
+               this._data.converter = converter;
+               this._data.context = context;
+
+               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);
+               this._leftmostColumn = parseInt(columns[0].text);
+               this._leftOffset = Math.round(converter.call(context, 
leftmostOffset).x);
+
+               this._data[0] = { pos: this._leftOffset, text: '', selected: 
false };
 
-               L.DomUtil.empty(this._columns);
-               var leftOffset = new L.Point(columns[0].size, columns[0].size);
-               // column[0] is a dummy column header whose text attribute is 
set to the column index
-               var leftmostCol = parseInt(columns[0].text);
-               this._leftOffset = Math.round(converter.call(context, 
leftOffset).x);
                for (iterator = 1; iterator < columns.length; iterator++) {
-                       width = columns[iterator].size - columns[iterator - 
1].size;
-                       twip = new L.Point(width, width);
-                       column = L.DomUtil.create('div', 
'spreadsheet-header-column', this._columns);
-                       text = L.DomUtil.create('div', 
'spreadsheet-header-column-text', column);
-                       resize = L.DomUtil.create('div', 
'spreadsheet-header-column-resize', column);
-                       L.DomEvent.on(resize, 'contextmenu', 
L.DomEvent.preventDefault);
-                       column.size = columns[iterator].size;
-                       var content = columns[iterator].text;
-                       text.setAttribute('rel', 'spreadsheet-column-' + 
content); // for easy addressing
-                       text.innerHTML = content;
-                       width = Math.round(converter.call(context, twip).x) - 1;
-                       if (width <= 0) {
-                               L.DomUtil.setStyle(column, 'display', 'none');
-                       } else if (width < 10) {
-                               text.column = iterator + leftmostCol;
-                               text.width = width;
-                               L.DomUtil.setStyle(column, 'width', width + 
'px');
-                               L.DomUtil.setStyle(column, 'cursor', 
'col-resize');
-                               L.DomUtil.setStyle(text, 'cursor', 
'col-resize');
-                               L.DomUtil.setStyle(resize, 'display', 'none');
-                               this.mouseInit(text);
-                       } else {
-                               resize.column = iterator + leftmostCol;
-                               resize.width = width;
-                               L.DomUtil.setStyle(column, 'width', width + 
'px');
-                               L.DomUtil.setStyle(text, 'width', width - 3 + 
'px');
-                               L.DomUtil.setStyle(resize, 'width', '3px');
-                               this.mouseInit(resize);
+                       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);
                        }
-                       L.DomEvent.addListener(text, 'click', 
this._onColumnHeaderClick, this);
                }
 
-               if ($('.spreadsheet-header-column-text').length > 0) {
-                       
$('.spreadsheet-header-column-text').contextMenu(this._map._permission === 
'edit');
+               this.mouseInit(canvas);
+
+               L.DomEvent.on(canvas, 'contextmenu', L.DomEvent.preventDefault);
+               if ($('.spreadsheet-header-columns').length > 0) {
+                       
$('.spreadsheet-header-columns').contextMenu(this._map._permission === 'edit');
                }
        },
 
@@ -232,8 +332,11 @@ L.Control.ColumnHeader = L.Control.Header.extend({
                this._map.sendUnoCommand('.uno:SelectColumn ', command);
        },
 
-       _onColumnHeaderClick: function (e) {
-               var colAlpha = 
e.target.getAttribute('rel').split('spreadsheet-column-')[1];
+       _onHeaderClick: function (e) {
+               if (!this._mouseOverIndex)
+                       return;
+
+               var colAlpha = this._data[this._mouseOverIndex].text;
 
                var modifier = 0;
                if (e.shiftKey) {
@@ -295,21 +398,31 @@ 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 (item.width != distance.x) {
-                       var command = {
-                               ColumnWidth: {
-                                       type: 'unsigned short',
-                                       value: 
this._map._docLayer.twipsToHMM(Math.max(distance.x, 0))
-                               },
-                               Column: {
-                                       type: 'unsigned short',
-                                       value: item.parentNode && 
item.parentNode.nextSibling &&
-                                              
L.DomUtil.getStyle(item.parentNode.nextSibling, 'display') === 'none' ? 
item.column + 1 : item.column
-                               }
-                       };
+               if (this._mouseOverIndex) {
+                       var clickedColumn = this._data[this._mouseOverIndex];
+                       var width = clickedColumn.pos - 
this._data[this._mouseOverIndex - 1];
+                       var column = this._mouseOverIndex + 
this._leftmostColumn;
+
+                       if (this._data[this._mouseOverIndex + 1]
+                               && this._data[this._mouseOverIndex + 1].pos === 
clickedColumn.pos) {
+                               column += 1;
+                       }
+
+                       if (width !== distance.x) {
+                               var command = {
+                                       ColumnWidth: {
+                                               type: 'unsigned short',
+                                               value: 
this._map._docLayer.twipsToHMM(Math.max(distance.x, 0))
+                                       },
+                                       Column: {
+                                               type: 'unsigned short',
+                                               value: column
+                                       }
+                               };
 
-                       this._map.sendUnoCommand('.uno:ColumnWidth', command);
-                       this._updateColumnHeader();
+                               this._map.sendUnoCommand('.uno:ColumnWidth', 
command);
+                               this._updateColumnHeader();
+                       }
                }
 
                this._map.removeLayer(this._vertLine);
@@ -318,11 +431,15 @@ L.Control.ColumnHeader = L.Control.Header.extend({
        onDragClick: function (item, clicks, e) {
                this._map.removeLayer(this._vertLine);
 
+               if (!this._mouseOverIndex)
+                       return;
+
                if (clicks === 2) {
+                       var column = this._mouseOverIndex + 
this._leftmostColumn;
                        var command = {
                                Col: {
                                        type: 'unsigned short',
-                                       value: item.column - 1
+                                       value: column - 1
                                },
                                Modifier: {
                                        type: 'unsigned short',
@@ -343,9 +460,13 @@ L.Control.ColumnHeader = L.Control.Header.extend({
                if (!this._initialized) {
                        this._initialize();
                }
-               if ($('.spreadsheet-header-column-text').length > 0) {
-                       $('.spreadsheet-header-column-text').contextMenu(e.perm 
=== 'edit');
+               if ($('.spreadsheet-header-columns').length > 0) {
+                       $('.spreadsheet-header-columns').contextMenu(e.perm === 
'edit');
                }
+       },
+
+       _getPos: function (point) {
+               return point.x;
        }
 });
 
diff --git a/loleaflet/src/control/Control.Header.js 
b/loleaflet/src/control/Control.Header.js
index 7b309339..1c522caf 100644
--- a/loleaflet/src/control/Control.Header.js
+++ b/loleaflet/src/control/Control.Header.js
@@ -8,60 +8,77 @@ L.Control.Header = L.Control.extend({
        },
 
        initialize: function () {
+               this._headerCanvas = null;
                this._clicks = 0;
                this._current = -1;
                this._selection = {start: -1, end: -1};
+               this._mouseOverIndex = undefined;
+               this._lastMouseOverIndex = undefined;
+               this._hitResizeArea = false;
+
+               // styles
+               this._backgroundColor = 'lightgray';
+               this._hoverColor = '#DDD';
+               this._borderColor = 'darkgray';
+               this._textColor = 'black';
+               this._font = '12px/1.5 "Segoe UI", Tahoma, Arial, Helvetica, 
sans-serif';
+               this._cursor = 'pointer';
+               this._selectionTextColor = 'white';
+               this._selectionBackgroundGradient = [ '#3465A4', '#729FCF', 
'#004586' ];
        },
 
        mouseInit: function (element) {
                L.DomEvent.on(element, 'mousedown', this._onMouseDown, this);
        },
 
-       select: function (item) {
-               if (item && !L.DomUtil.hasClass(item, 
'spreadsheet-header-selected')) {
-                       L.DomUtil.addClass(item, 'spreadsheet-header-selected');
-               }
+       select: function (data, index) {
+               if (!data[index])
+                       return;
+               data[index].selected = true;
+               this.drawHeaderEntry(index, false);
        },
 
-       unselect: function (item) {
-               if (item && L.DomUtil.hasClass(item, 
'spreadsheet-header-selected')) {
-                       L.DomUtil.removeClass(item, 
'spreadsheet-header-selected');
-               }
+       unselect: function (data, index) {
+               if (!data[index])
+                       return;
+               data[index].selected = false;
+               this.drawHeaderEntry(index, false);
        },
 
-       clearSelection: function (element) {
+       clearSelection: function (data) {
                if (this._selection.start === -1 && this._selection.end === -1)
                        return;
-               var childs = element.children;
                var start = (this._selection.start === -1) ? 0 : 
this._selection.start;
                var end = this._selection.end + 1;
                for (var iterator = start; iterator < end; iterator++) {
-                       this.unselect(childs[iterator]);
+                       this.unselect(data, iterator);
                }
 
                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(childs[this._current]);
+               this.select(data, this._current);
        },
 
-       updateSelection: function(element, start, end) {
-               var childs = element.children;
+       updateSelection: function(data, start, end) {
+               if (!data)
+                       return;
+
                var x0 = 0, x1 = 0;
                var itStart = -1, itEnd = -1;
                var selected = false;
                var iterator = 0;
-               for (var len = childs.length; iterator < len; iterator++) {
-                       x0 = (iterator > 0 ? childs[iterator - 1].size : 0);
-                       x1 = childs[iterator].size;
+               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 (x0 <= start && start < x1) {
                                selected = true;
                                itStart = iterator;
                        }
                        if (selected) {
-                               this.select(childs[iterator]);
+                               this.select(data, iterator);
                        }
                        if (x0 <= end && end <= x1) {
                                itEnd = iterator;
@@ -72,7 +89,7 @@ L.Control.Header = L.Control.extend({
                // if end is greater than the last fetched header position set 
itEnd to the max possible value
                // without this hack selecting a whole row and then a whole 
column (or viceversa) leads to an incorrect selection
                if (itStart !== -1 && itEnd === -1) {
-                       itEnd = childs.length - 1;
+                       itEnd = data.length - 1;
                }
 
                // we need to unselect the row (column) header entry for the 
current cell cursor position
@@ -80,43 +97,44 @@ 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(childs[this._current]);
+                               this.unselect(data, this._current);
                        }
                }
                if (this._selection.start !== -1 && itStart !== -1 && itStart > 
this._selection.start) {
                        for (iterator = this._selection.start; iterator < 
itStart; iterator++) {
-                               this.unselect(childs[iterator]);
+                               this.unselect(data, iterator);
                        }
                }
                if (this._selection.end !== -1 && itEnd !== -1 && itEnd < 
this._selection.end) {
                        for (iterator = itEnd + 1; iterator <= 
this._selection.end; iterator++) {
-                               this.unselect(childs[iterator]);
+                               this.unselect(data, iterator);
                        }
                }
                this._selection.start = itStart;
                this._selection.end = itEnd;
        },
 
-       updateCurrent: function (element, start) {
-               var childs = element.children;
+       updateCurrent: function (data, start) {
+               if (!data)
+                       return;
                if (start < 0) {
-                       this.unselect(childs[this._current]);
+                       this.unselect(data, this._current);
                        this._current = -1;
                        return;
                }
 
                var x0 = 0, x1 = 0;
-               for (var iterator = 0, len = childs.length; iterator < len; 
iterator++) {
-                       x0 = (iterator > 0 ? childs[iterator - 1].size : 0);
-                       x1 = childs[iterator].size;
-                       if (x0 <= start && start <= x1) {
+               for (var iterator = 1, len = data.length; iterator < len; 
iterator++) {
+                       x0 = (iterator > 0 ? data[iterator - 1].pos : 0);
+                       x1 = data[iterator].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(childs[this._current]);
-                                       this.select(childs[iterator]);
+                                       this.unselect(data, this._current);
+                                       this.select(data, iterator);
                                }
                                this._current = iterator;
                                break;
@@ -124,6 +142,71 @@ L.Control.Header = L.Control.extend({
                }
        },
 
+       _mouseEventToCanvasPos: function(canvas, evt) {
+               var rect = canvas.getBoundingClientRect();
+               return {
+                       x: evt.clientX - rect.left,
+                       y: evt.clientY - rect.top
+               };
+       },
+
+       _onMouseOut: function (e) {
+               if (this._mouseOverIndex) {
+                       this.drawHeaderEntry(this._mouseOverIndex, false);
+                       this._lastMouseOverIndex = this._mouseOverIndex; // 
used by context menu
+                       this._mouseOverIndex = undefined;
+               }
+               this._hitResizeArea = false;
+               L.DomUtil.setStyle(this._headerCanvas, 'cursor', this._cursor);
+       },
+
+       _onCanvasMouseMove: function (e) {
+               var target = e.target || e.srcElement;
+
+               if (!target || this._dragging) {
+                       return false;
+               }
+
+               var isMouseOverResizeArea = false;
+               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;
+                       if (pos > start && pos <= end) {
+                               mouseOverIndex = iterator;
+                               var resizeAreaStart = Math.max(start, end - 3);
+                               isMouseOverResizeArea = (pos > resizeAreaStart);
+                               break;
+                       }
+               }
+
+               if (mouseOverIndex !== this._mouseOverIndex) {
+                       if (this._mouseOverIndex) {
+                               this.drawHeaderEntry(this._mouseOverIndex, 
false);
+                       }
+                       if (mouseOverIndex) {
+                               this.drawHeaderEntry(mouseOverIndex, true);
+                       }
+               }
+
+               if (isMouseOverResizeArea !== this._hitResizeArea) {
+                       if (isMouseOverResizeArea) {
+                               L.DomEvent.off(this._headerCanvas, 'click', 
this._onHeaderClick, this);
+                       }
+                       else {
+                               L.DomEvent.on(this._headerCanvas, 'click', 
this._onHeaderClick, this);
+                       }
+                       var cursor = isMouseOverResizeArea ? 
this.options.cursor : this._cursor;
+                       L.DomUtil.setStyle(this._headerCanvas, 'cursor', 
cursor);
+                       this._hitResizeArea = isMouseOverResizeArea;
+               }
+
+               this._mouseOverIndex = mouseOverIndex;
+       },
+
        _onMouseDown: function (e) {
                var target = e.target || e.srcElement;
 
@@ -131,14 +214,21 @@ L.Control.Header = L.Control.extend({
                        return false;
                }
 
+               if (!this._hitResizeArea)
+                       return;
+
                L.DomUtil.disableImageDrag();
                L.DomUtil.disableTextSelection();
 
                L.DomEvent.stopPropagation(e);
+
+               L.DomEvent.off(target, 'mousemove', this._onCanvasMouseMove, 
this);
+               L.DomEvent.off(target, 'mouseout', this._onMouseOut, this);
+
                L.DomEvent.on(document, 'mousemove', this._onMouseMove, this);
                L.DomEvent.on(document, 'mouseup', this._onMouseUp, this);
 
-               var rect = target.parentNode.getBoundingClientRect();
+               var rect = this.getHeaderEntryBoundingClientRect();
                this._start = new L.Point(rect.left, rect.top);
                this._offset = new L.Point(rect.right - e.clientX, rect.bottom 
- e.clientY);
                this._item = target;
@@ -150,13 +240,6 @@ L.Control.Header = L.Control.extend({
                this._dragging = true;
                L.DomEvent.preventDefault(e);
 
-               var target = e.target || e.srcElement;
-               if (target.style.cursor !== this.options.cursor &&
-                  (L.DomUtil.hasClass(target, 
'spreadsheet-header-column-text') ||
-                   L.DomUtil.hasClass(target, 'spreadsheet-header-row-text'))) 
{
-                       target.style.cursor = this.options.cursor;
-               }
-
                this.onDragMove(this._item, this._start, this._offset, e);
        },
 
@@ -167,6 +250,9 @@ L.Control.Header = L.Control.extend({
                L.DomUtil.enableImageDrag();
                L.DomUtil.enableTextSelection();
 
+               L.DomEvent.on(this._item, 'mousemove', this._onCanvasMouseMove, 
this);
+               L.DomEvent.on(this._item, 'mouseout', this._onMouseOut, this);
+
                if (this._dragging) {
                        this.onDragEnd(this._item, this._start, this._offset, 
e);
                        this._clicks = 0;
@@ -182,5 +268,8 @@ L.Control.Header = L.Control.extend({
        onDragStart: function () {},
        onDragMove: function () {},
        onDragEnd: function () {},
-       onDragClick: function () {}
+       onDragClick: function () {},
+       getHeaderEntryBoundingClientRect: function () {},
+       drawHeaderEntry: function () {},
+       _getPos: function () {}
 });
diff --git a/loleaflet/src/control/Control.RowHeader.js 
b/loleaflet/src/control/Control.RowHeader.js
index 744a99a6..99945f7d 100644
--- a/loleaflet/src/control/Control.RowHeader.js
+++ b/loleaflet/src/control/Control.RowHeader.js
@@ -22,50 +22,76 @@ L.Control.RowHeader = L.Control.Header.extend({
                this._map.on('clearselectionheader', this._onClearSelection, 
this);
                this._map.on('updatecurrentheader', this._onUpdateCurrentRow, 
this);
                var rowColumnFrame = 
L.DomUtil.get('spreadsheet-row-column-frame');
-               var headersContainer = L.DomUtil.create('div', 
'spreadsheet-header-rows-container', rowColumnFrame);
-               this._rows = L.DomUtil.create('div', 'spreadsheet-header-rows', 
headersContainer);
+               this._headersContainer = L.DomUtil.create('div', 
'spreadsheet-header-rows-container', rowColumnFrame);
 
+               this._headerCanvas = L.DomUtil.create('canvas', 
'spreadsheet-header-rows', this._headersContainer);
+               this._canvasContext = this._headerCanvas.getContext('2d');
+               this._headerCanvas.width = 
parseInt(L.DomUtil.getStyle(this._headersContainer, 'width'));
+               this._headerCanvas.height = 
parseInt(L.DomUtil.getStyle(this._headersContainer, 'height'));
+
+               L.DomUtil.setStyle(this._headerCanvas, 'cursor', this._cursor);
+
+               L.DomEvent.on(this._headerCanvas, 'mousemove', 
this._onCanvasMouseMove, this);
+               L.DomEvent.on(this._headerCanvas, 'mouseout', this._onMouseOut, 
this);
+               L.DomEvent.on(this._headerCanvas, 'click', this._onHeaderClick, 
this);
+
+               this._topRow = 0;
                this._topOffset = 0;
                this._position = 0;
 
                var rowHeaderObj = this;
                $.contextMenu({
-                       selector: '.spreadsheet-header-row-text',
+                       selector: '.spreadsheet-header-rows',
                        className: 'loleaflet-font',
                        items: {
                                'insertrowabove': {
                                        name: _('Insert row above'),
                                        callback: function(key, options) {
-                                               var row = 
parseInt(options.$trigger.attr('rel').split('spreadsheet-row-')[1]);
-                                               
rowHeaderObj.insertRow.call(rowHeaderObj, row);
+                                               var index = 
rowHeaderObj._lastMouseOverIndex;
+                                               if (index) {
+                                                       var row = 
rowHeaderObj._data[index].text;
+                                                       
rowHeaderObj.insertRow.call(rowHeaderObj, row);
+                                               }
                                        }
                                },
                                'deleteselectedrow': {
                                        name: _('Delete row'),
                                        callback: function(key, options) {
-                                               var row = 
parseInt(options.$trigger.attr('rel').split('spreadsheet-row-')[1]);
-                                               
rowHeaderObj.deleteRow.call(rowHeaderObj, row);
+                                               var index = 
rowHeaderObj._lastMouseOverIndex;
+                                               if (index) {
+                                                       var row = 
rowHeaderObj._data[index].text;
+                                                       
rowHeaderObj.deleteRow.call(rowHeaderObj, row);
+                                               }
                                        }
                                },
                                'optimalheight': {
                                        name: _('Optimal Height') + '...',
                                        callback: function(key, options) {
-                                               var row = 
parseInt(options.$trigger.attr('rel').split('spreadsheet-row-')[1]);
-                                               
rowHeaderObj.optimalHeight.call(rowHeaderObj, row);
+                                               var index = 
rowHeaderObj._lastMouseOverIndex;
+                                               if (index) {
+                                                       var row = 
rowHeaderObj._data[index].text;
+                                                       
rowHeaderObj.optimalHeight.call(rowHeaderObj, row);
+                                               }
                                        }
                                },
                                'hideRow': {
                                        name: _('Hide Rows'),
                                        callback: function(key, options) {
-                                               var row = 
parseInt(options.$trigger.attr('rel').split('spreadsheet-row-')[1]);
-                                               
rowHeaderObj.hideRow.call(rowHeaderObj, row);
+                                               var index = 
rowHeaderObj._lastMouseOverIndex;
+                                               if (index) {
+                                                       var row = 
rowHeaderObj._data[index].text;
+                                                       
rowHeaderObj.hideRow.call(rowHeaderObj, row);
+                                               }
                                        }
                                },
                                'showRow': {
                                        name: _('Show Rows'),
                                        callback: function(key, options) {
-                                               var row = 
parseInt(options.$trigger.attr('rel').split('spreadsheet-row-')[1]);
-                                               
rowHeaderObj.showRow.call(rowHeaderObj, row);
+                                               var index = 
rowHeaderObj._lastMouseOverIndex;
+                                               if (index) {
+                                                       var row = 
rowHeaderObj._data[index].text;
+                                                       
rowHeaderObj.showRow.call(rowHeaderObj, row);
+                                               }
                                        }
                                }
                        },
@@ -127,72 +153,145 @@ L.Control.RowHeader = L.Control.Header.extend({
        },
 
        _onClearSelection: function (e) {
-               this.clearSelection(this._rows);
+               this.clearSelection(this._data);
        },
 
        _onUpdateSelection: function (e) {
-               this.updateSelection(this._rows, e.start.y, e.end.y);
+               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);
+               }
+               if (end !== -1) {
+                       twips = new L.Point(end, end);
+                       end = Math.round(data.converter.call(data.context, 
twips).y);
+               }
+               this.updateSelection(data, start, end);
        },
 
        _onUpdateCurrentRow: function (e) {
-               this.updateCurrent(this._rows, e.y);
+               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);
+               }
+               this.updateCurrent(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)
+                       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 height = end - start;
+               var width = this._headerCanvas.width;
+               var isHighlighted = this._data[index].selected;
+
+               if (height <= 0)
+                       return;
+
+               ctx.save();
+               ctx.translate(0, this._position + this._topOffset);
+               // background gradient
+               var selectionBackgroundGradient = null;
+               if (isHighlighted) {
+                       selectionBackgroundGradient = 
ctx.createLinearGradient(0, start, 0, start + height);
+                       selectionBackgroundGradient.addColorStop(0, 
this._selectionBackgroundGradient[0]);
+                       selectionBackgroundGradient.addColorStop(0.5, 
this._selectionBackgroundGradient[1]);
+                       selectionBackgroundGradient.addColorStop(1, 
this._selectionBackgroundGradient[2]);
+               }
+               // clip mask
+               ctx.beginPath();
+               ctx.rect(0, start, width, height);
+               ctx.clip();
+               // draw background
+               ctx.fillStyle = isHighlighted ? selectionBackgroundGradient : 
isOver ? this._hoverColor : this._backgroundColor;
+               ctx.fillRect(0, start, width, height);
+               // draw text content
+               ctx.fillStyle = isHighlighted ? this._selectionTextColor : 
this._textColor;
+               ctx.font = this._font;
+               ctx.textAlign = 'center';
+               ctx.textBaseline = 'middle';
+               ctx.fillText(content, width / 2, end - (height / 2));
+               // draw row separator
+               ctx.fillStyle = this._borderColor;
+               ctx.fillRect(0, end -1, width, 1);
+               ctx.restore();
+       },
+
+       getHeaderEntryBoundingClientRect: function (index) {
+               if (!index)
+                       index = this._mouseOverIndex; // use last mouse over 
position
+
+               if (!index || !this._data[index])
+                       return;
+
+               var rect = this._headerCanvas.getBoundingClientRect();
+
+               var rowStart = this._data[index - 1].pos + this._position;
+               var rowEnd = this._data[index].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 };
+       },
+
        viewRowColumnHeaders: function (e) {
                if (e.data.rows && e.data.rows.length) {
                        this.fillRows(e.data.rows, e.converter, e.context);
-                       L.DomUtil.setStyle(this._rows, 'top', (this._position + 
this._topOffset) + 'px');
                }
        },
 
        fillRows: function (rows, converter, context) {
-               var iterator, twip, height, row, text, resize;
+               var iterator, twip, height;
+
+               this._data = new Array(rows.length);
+               this._data.converter = converter;
+               this._data.context = context;
+
+               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);
 
-               L.DomUtil.empty(this._rows);
                var topOffset = new L.Point(rows[0].size, rows[0].size);
-               var topRow = parseInt(rows[0].text);
+               this._topRow = parseInt(rows[0].text);
                this._topOffset = Math.round(converter.call(context, 
topOffset).y);
+
+               this._data[0] = { pos: this._topOffset, text: '', selected: 
false };
+
                for (iterator = 1; iterator < rows.length; iterator++) {
-                       height = rows[iterator].size - rows[iterator - 1].size;
-                       twip = new L.Point(height, height);
-                       row = L.DomUtil.create('div', 'spreadsheet-header-row', 
this._rows);
-                       text = L.DomUtil.create('div', 
'spreadsheet-header-row-text', row);
-                       resize = L.DomUtil.create('div', 
'spreadsheet-header-row-resize', row);
-                       L.DomEvent.on(resize, 'contextmenu', 
L.DomEvent.preventDefault);
-                       row.size = rows[iterator].size;
-                       var content = rows[iterator].text;
-                       text.setAttribute('rel', 'spreadsheet-row-' + content); 
// for easy addressing
-                       text.innerHTML = content;
-                       height = Math.round(converter.call(context, twip).y) - 
1;
-                       if (height <= 0) {
-                               L.DomUtil.setStyle(row, 'display', 'none');
-                       } else if (height < 10) {
-                               text.row = iterator + topRow;
-                               text.height = height;
-                               L.DomUtil.setStyle(row, 'height', height + 
'px');
-                               L.DomUtil.setStyle(row, 'cursor', 'row-resize');
-                               L.DomUtil.setStyle(text, 'line-height', height 
+ 'px');
-                               L.DomUtil.setStyle(text, 'cursor', 
'row-resize');
-                               L.DomUtil.setStyle(resize, 'display', 'none');
-                               this.mouseInit(text);
-                       } else {
-                               resize.row = iterator + topRow;
-                               resize.height = height;
-                               L.DomUtil.setStyle(row, 'height', height + 
'px');
-                               L.DomUtil.setStyle(text, 'line-height', height 
- 3 + 'px');
-                               L.DomUtil.setStyle(text, 'height', height - 3 + 
'px');
-                               L.DomUtil.setStyle(resize, 'height', '3px');
-                               this.mouseInit(resize);
+                       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);
                        }
-                       L.DomEvent.addListener(text, 'click', 
this._onRowHeaderClick, this);
                }
 
-               if ($('.spreadsheet-header-row-text').length > 0) {
-                       
$('.spreadsheet-header-row-text').contextMenu(this._map._permission === 'edit');
+               this.mouseInit(canvas);
+
+               L.DomEvent.on(canvas, 'contextmenu', L.DomEvent.preventDefault);
+               if ($('.spreadsheet-header-rows').length > 0) {
+                       
$('.spreadsheet-header-rows').contextMenu(this._map._permission === 'edit');
                }
        },
 
@@ -211,8 +310,12 @@ L.Control.RowHeader = L.Control.Header.extend({
                this._map.sendUnoCommand('.uno:SelectRow ', command);
        },
 
-       _onRowHeaderClick: function (e) {
-               var row = 
e.target.getAttribute('rel').split('spreadsheet-row-')[1];
+       _onHeaderClick: function (e) {
+               if (!this._mouseOverIndex)
+                       return;
+
+               var row = this._mouseOverIndex + this._topRow;
+
                var modifier = 0;
                if (e.shiftKey) {
                        modifier += this._map.keyboard.keyModifier.shift;
@@ -269,20 +372,30 @@ 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 (item.height != distance.y) {
-                       var command = {
-                               RowHeight: {
-                                       type: 'unsigned short',
-                                       value: 
this._map._docLayer.twipsToHMM(Math.max(distance.y, 0))
-                               },
-                               Row: {
-                                       type: 'long',
-                                       value: item.parentNode && 
item.parentNode.nextSibling &&
-                                              
L.DomUtil.getStyle(item.parentNode.nextSibling, 'display') === 'none' ? 
item.row + 1 : item.row
-                               }
-                       };
+               if (this._mouseOverIndex) {
+                       var clickedRow = this._data[this._mouseOverIndex];
+                       var height = clickedRow.pos - 
this._data[this._mouseOverIndex - 1];
+                       var row = this._mouseOverIndex + this._topRow;
+
+                       if (this._data[this._mouseOverIndex + 1]
+                               && this._data[this._mouseOverIndex + 1].pos === 
clickedRow.pos) {
+                               row += 1;
+                       }
+
+                       if (height !== distance.y) {
+                               var command = {
+                                       RowHeight: {
+                                               type: 'unsigned short',
+                                               value: 
this._map._docLayer.twipsToHMM(Math.max(distance.y, 0))
+                                       },
+                                       Row: {
+                                               type: 'long',
+                                               value: row
+                                       }
+                               };
 
-                       this._map.sendUnoCommand('.uno:RowHeight', command);
+                               this._map.sendUnoCommand('.uno:RowHeight', 
command);
+                       }
                }
 
                this._map.removeLayer(this._horzLine);
@@ -291,11 +404,15 @@ L.Control.RowHeader = L.Control.Header.extend({
        onDragClick: function (item, clicks, e) {
                this._map.removeLayer(this._horzLine);
 
+               if (!this._mouseOverIndex)
+                       return;
+
                if (clicks === 2) {
+                       var row = this._mouseOverIndex + this._topRow;
                        var command = {
                                Row: {
                                        type: 'long',
-                                       value: item.row - 1
+                                       value: row - 1
                                },
                                Modifier: {
                                        type: 'unsigned short',
@@ -324,9 +441,13 @@ L.Control.RowHeader = L.Control.Header.extend({
                        this._initialize();
                }
                // Enable context menu on row headers only if permission is 
'edit'
-               if ($('.spreadsheet-header-row-text').length > 0) {
-                       $('.spreadsheet-header-row-text').contextMenu(e.perm 
=== 'edit');
+               if ($('.spreadsheet-header-rows').length > 0) {
+                       $('.spreadsheet-header-rows').contextMenu(e.perm === 
'edit');
                }
+       },
+
+       _getPos: function (point) {
+               return point.y;
        }
 });
 
_______________________________________________
Libreoffice-commits mailing list
libreoffice-comm...@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits

Reply via email to