[Libreoffice-commits] online.git: Branch 'distro/collabora/collabora-online-3-0' - loleaflet/src

Marco Cecchetti marco.cecchetti at collabora.com
Wed Nov 29 13:49:54 UTC 2017


 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.cecchetti at 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 <kendy at collabora.com>
    Tested-by: Jan Holesovsky <kendy at 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',


More information about the Libreoffice-commits mailing list