[Libreoffice-commits] online.git: loleaflet/src

Iván Sánchez Ortega (via logerrit) logerrit at kemper.freedesktop.org
Mon Nov 4 15:06:02 UTC 2019


 loleaflet/src/control/Control.ColumnHeader.js |   73 +---
 loleaflet/src/control/Control.Header.js       |  382 +++++++++++---------------
 loleaflet/src/control/Control.RowHeader.js    |   69 +---
 3 files changed, 209 insertions(+), 315 deletions(-)

New commits:
commit e1f4825dea9a89fc0460ae8a7b7e15d8cc66760c
Author:     Iván Sánchez Ortega <ivan.sanchez at collabora.com>
AuthorDate: Thu May 16 11:44:55 2019 +0200
Commit:     Michael Meeks <michael.meeks at collabora.com>
CommitDate: Mon Nov 4 15:05:37 2019 +0000

    loleaflet: Simplify data structs for Calc column/row headers
    
    This replaces the (undocumented) L.Control.Header.DataImp data structure
    with the (documented) L.Control.Header.GapTickMap data struct.
    
    This is done in preparation for further work that needs to handle row/col sizes.
    
    As a side effect, all column/row indices for the headers are now absolute to
    the sheet, as opposed to relative to the visible ones in the screen.
    
    The impact of these changes on performance should be negligible.
    
    Change-Id: I1688b84db7445e97ac4d75dd073e32cc20e29d9d

diff --git a/loleaflet/src/control/Control.ColumnHeader.js b/loleaflet/src/control/Control.ColumnHeader.js
index 2b96ed317..19f4a1adb 100644
--- a/loleaflet/src/control/Control.ColumnHeader.js
+++ b/loleaflet/src/control/Control.ColumnHeader.js
@@ -57,7 +57,6 @@ L.Control.ColumnHeader = L.Control.Header.extend({
 			},
 			this);
 
-		this._startHeaderIndex = 0;
 		this._startOffset = 0;
 		this._position = 0;
 
@@ -172,7 +171,7 @@ L.Control.ColumnHeader = L.Control.Header.extend({
 	},
 
 	_onClearSelection: function () {
-		this.clearSelection(this._data);
+		this.clearSelection();
 	},
 
 	_onUpdateSelection: function (e) {
@@ -184,14 +183,14 @@ L.Control.ColumnHeader = L.Control.Header.extend({
 		if (end !== -1) {
 			end = this._twipsToPixels(end);
 		}
-		this.updateSelection(this._data, start, end);
+		this.updateSelection(start, end);
 	},
 
 	_onUpdateCurrentColumn: function (e) {
-		var x = e.curX - this._startHeaderIndex;
+		var x = e.curX;
 		var w = this._twipsToPixels(e.width);
 		var slim = w <= 1;
-		this.updateCurrent(this._data, x, slim);
+		this.updateCurrent(x, slim);
 	},
 
 	_updateColumnHeader: function () {
@@ -203,7 +202,7 @@ L.Control.ColumnHeader = L.Control.Header.extend({
 			return;
 
 		var ctx = this._canvasContext;
-		var content = this._colIndexToAlpha(entry.index + this._startHeaderIndex);
+		var content = this._colIndexToAlpha(entry.index);
 		var startOrt = this._canvasHeight - this._headerHeight;
 		var startPar = entry.pos - entry.size - this._startOffset;
 		var endPar = entry.pos - this._startOffset;
@@ -358,8 +357,9 @@ L.Control.ColumnHeader = L.Control.Header.extend({
 
 	getHeaderEntryBoundingClientRect: function (index) {
 		var entry = this._mouseOverEntry;
-		if (index)
-			entry = this._data.get(index);
+		if (index) {
+			entry = this._tickMap.getGap(index);
+		}
 
 		if (!entry)
 			return;
@@ -386,26 +386,25 @@ L.Control.ColumnHeader = L.Control.Header.extend({
 		if (columns.length < 2)
 			return;
 
-		var headerEntry, index, iterator, width, pos;
-
 		var canvas = this._canvas;
 		this._setCanvasWidth();
 		this._setCanvasHeight();
 		this._canvasContext.clearRect(0, 0, canvas.width, canvas.height);
 
-		// update first header index and reset no more valid variables
-		this._startHeaderIndex = parseInt(columns[0].text);
-		this._current = -1; // no more valid
-		this._selection.start = this._selection.end = -1; // no more valid
+		// Reset state
+		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();
+		// create data structure for column widths
+		this._tickMap = new L.Control.Header.GapTickMap(
+			columns,
+			L.Util.bind(this._twipsToPixels, this)
+		);
 
 		// setup conversion routine
 		this.converter = L.Util.bind(converter, context);
-		this._data.converter = L.Util.bind(this._twipsToPixels, this);
 
 		// create group array
 		this._groupLevels = parseInt(columns[0].groupLevels);
@@ -414,28 +413,6 @@ L.Control.ColumnHeader = L.Control.Header.extend({
 		var startOffsetTw = parseInt(columns[0].size);
 		this._startOffset = 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._startHeaderIndex;
-			headerEntry = {pos: pos, size: width};
-			this._data.pushBack(index, headerEntry);
-		}
-
-		// setup last header entry
-		index = nextIndex - this._startHeaderIndex;
-		pos = parseInt(columns[last].size);
-		width = pos - prevPos;
-		this._data.pushBack(index, {pos: pos, size: width});
-
 		// collect group controls data
 		if (colGroups !== undefined && this._groups) {
 			this._collectGroupsData(colGroups);
@@ -448,12 +425,10 @@ L.Control.ColumnHeader = L.Control.Header.extend({
 			this.resize(this._headerHeight);
 		}
 
-		// draw header
-		headerEntry = this._data.getFirst();
-		while (headerEntry) {
-			this.drawHeaderEntry(headerEntry, false);
-			headerEntry = this._data.getNext();
-		}
+		// Initial draw
+		this._tickMap.forEachGap(function(gap) {
+			this.drawHeaderEntry(gap, false);
+		}.bind(this));
 
 		// draw group controls
 		this.drawOutline();
@@ -514,7 +489,7 @@ L.Control.ColumnHeader = L.Control.Header.extend({
 		if (!this._mouseOverEntry)
 			return;
 
-		var col = this._mouseOverEntry.index + this._startHeaderIndex;
+		var col = this._mouseOverEntry.index;
 
 		var modifier = 0;
 		if (e.shiftKey) {
@@ -595,9 +570,9 @@ L.Control.ColumnHeader = L.Control.Header.extend({
 		var clickedColumn = this._mouseOverEntry;
 		if (clickedColumn) {
 			var width = clickedColumn.size;
-			var column = clickedColumn.index + this._startHeaderIndex;
+			var column = clickedColumn.index;
 
-			if (this._data.isZeroSize(clickedColumn.index + 1)) {
+			if (this._tickMap.isZeroSize(clickedColumn.index + 1)) {
 				column += 1;
 				width = 0;
 			}
@@ -629,7 +604,7 @@ L.Control.ColumnHeader = L.Control.Header.extend({
 			return;
 
 		if (clicks === 2) {
-			var column = this._mouseOverEntry.index + this._startHeaderIndex;
+			var column = this._mouseOverEntry.index;
 			var command = {
 				Col: {
 					type: 'unsigned short',
diff --git a/loleaflet/src/control/Control.Header.js b/loleaflet/src/control/Control.Header.js
index 81e784dcd..daf142e3b 100644
--- a/loleaflet/src/control/Control.Header.js
+++ b/loleaflet/src/control/Control.Header.js
@@ -1,6 +1,9 @@
 /* -*- js-indent-level: 8 -*- */
 /*
 * Control.Header
+*
+* Abstract class, basis for ColumnHeader and RowHeader controls.
+* Used only in spreadsheets, implements the row/column headers.
 */
 /* global L Hammer */
 
@@ -47,7 +50,6 @@ L.Control.Header = L.Control.extend({
 					}
 				},
 				this);
-
 		}
 		else {
 			var rowColumnFrame = L.DomUtil.get('spreadsheet-row-column-frame');
@@ -149,116 +151,95 @@ L.Control.Header = L.Control.extend({
 		return (this._selection.start <= index && index <= this._selection.end);
 	},
 
-	clearSelection: function (data) {
+	clearSelection: function () {
 		if (this._selection.start === -1 && this._selection.end === -1)
 			return;
 		var start = (this._selection.start < 1) ? 1 : this._selection.start;
 		var end = this._selection.end + 1;
 
-		var entry = data.getAt(start);
-
-		while (entry && entry.index < end) {
-			this.unselect(entry);
-			entry = data.getNext(start);
+		for (var i=start; i<end; i++) {
+			if (i === this._current) {
+				// after clearing selection, we need to select the header entry for the current cursor position,
+				// since we can't be sure that the selection clearing is due to click on a cell
+				// different from the one where the cursor is already placed
+				this.select(this._tickMap.getGap(i), true);
+			} else {
+				this.unselect(this._tickMap.getGap(i));
+			}
 		}
 
 		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.get(this._current), true);
 	},
 
-	updateSelection: function(data, start, end) {
-		if (!data || data.isEmpty())
+	// Sets the internal this._selection values accordingly, unselects the previous set of rows/cols and
+	// selects the new set of rows/cols.
+	// Start and end are given in pixels absolute to the document
+	updateSelection: function(start, end) {
+		if (!this._tickMap)
 			return;
 
 		var x0 = 0, x1 = 0;
 		var itStart = -1, itEnd = -1;
-		var selected = false;
 
 		// 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();
+		var entry = this._tickMap.getGap(this._tickMap.getMinTickIdx() + 1);
 		if (entry) {
 			x0 = entry.pos - entry.size;
 			if (start < x0 && end > x0) {
-				selected = true;
 				itStart = 1;
 			}
 		}
 
-		while (entry) {
-			x0 = entry.pos - entry.size;
+		this._tickMap.forEachGap((function(entry) {
+			x0 = entry.start;
 			x1 = entry.pos;
-			if (x0 <= start && start < x1) {
-				selected = true;
-				itStart = entry.index;
-			}
-			if (selected) {
+			if (start < x1 && end > x0) {
 				this.select(entry, false);
+				if (itStart === -1) {
+					itStart = entry.index;
+				}
+			} else {
+				this.unselect(entry);
+				if (itStart !== -1 && itEnd === -1) {
+					itEnd = entry.index - 1;
+				}
 			}
-			if (x0 <= end && end <= x1) {
-				itEnd = entry.index;
-				break;
-			}
-			entry = data.getNext();
-		}
+		}).bind(this));
 
 		// 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.getLength() - 1;
+			itEnd = this._tickMap.getMaxTickIdx() - 1;
 		}
 
-		// we need to unselect the row (column) header entry for the current cell cursor position
-		// since the selection could be due to selecting a whole row (column), so the selection
-		// 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.get(this._current));
-			}
-		}
-
-		if (this._selection.start !== -1 && itStart !== -1 && itStart > this._selection.start) {
-			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) {
-			entry = data.getAt(itEnd + 1);
-			while (entry && entry.index <= this._selection.end) {
-				this.unselect(entry);
-				entry = data.getNext();
-			}
-		}
 		this._selection.start = itStart;
 		this._selection.end = itEnd;
 	},
 
-	updateCurrent: function (data, cursorPos, slim) {
-		if (!data || data.isEmpty())
-			return;
+
+	// Called whenever the cell cursor is in a cell corresponding to the cursorPos-th
+	// column/row.
+	updateCurrent: function (cursorPos, slim) {
+		if (!this._tickMap) {return}
 
 		if (cursorPos < 0) {
-			this.unselect(data.get(this._current));
+			this.unselect(this._tickMap.getGap(this._current));
 			this._current = -1;
 			return;
 		}
 
-		var prevEntry = cursorPos > 0 ? data.get(cursorPos - 1) : null;
+		var prevEntry = cursorPos > 0 ? this._tickMap.getGap(cursorPos - 1) : null;
 		var zeroSizeEntry = slim && prevEntry && prevEntry.size === 0;
 
-		var entry = data.get(cursorPos);
+		var entry = this._tickMap.getGap(cursorPos);
 		if (this._selection.start === -1 && this._selection.end === -1) {
 			// when a whole row (column) is selected the cell cursor is moved to the first column (row)
 			// but this action should not cause to select/unselect anything, on the contrary we end up
 			// with all column (row) header entries selected but the one where the cell cursor was
 			// previously placed
-			this.unselect(data.get(this._current));
+			this.unselect(this._tickMap.getGap(this._current));
 			// no selection when the cell cursor is slim
 			if (entry && !zeroSizeEntry)
 				this.select(entry, true);
@@ -284,21 +265,20 @@ L.Control.Header = L.Control.extend({
 		var position = this._getParallelPos(point);
 		position = position - this._position;
 
-		var eachEntry = this._data.getFirst();
-		while (eachEntry) {
-			var start = eachEntry.pos - eachEntry.size;
-			var end = eachEntry.pos;
-			if (position > start && position <= end) {
-				var resizeAreaStart = Math.max(start, end - 3);
-				if (this.isHeaderSelected(eachEntry.index)) {
-					resizeAreaStart = end - this._resizeHandleSize;
+		var that = this;
+		var result = null;
+		this._tickMap.forEachGap(function(gap) {
+			if (position >= gap.start && position < gap.end) {
+				var resizeAreaStart = Math.max(gap.start, gap.end - 3);
+				if (that.isHeaderSelected(gap.index)) {
+					resizeAreaStart = gap.end - that._resizeHandleSize;
 				}
 				var isMouseOverResizeArea = (position > resizeAreaStart);
-				return {entry: eachEntry, hit: isMouseOverResizeArea};
+				result = {entry: gap, hit: isMouseOverResizeArea};
+				return true;
 			}
-			eachEntry = this._data.getNext();
-		}
-		return null;
+		});
+		return result;
 	},
 
 	_onPanStart: function (event) {
@@ -395,7 +375,7 @@ L.Control.Header = L.Control.extend({
 		var isMouseOverResizeArea = false;
 		var result = this._entryAtPoint(this._mouseEventToCanvasPos(this._canvas, e));
 
-		var entry = this._data.getFirst();
+		var entry = undefined;
 		if (result) {
 			isMouseOverResizeArea = result.hit;
 			mouseOverIndex = result.entry.index;
@@ -752,164 +732,128 @@ L.Control.Header = L.Control.extend({
 
 });
 
-(function () {
-	L.Control.Header.rowHeaderWidth = undefined;
-	L.Control.Header.colHeaderHeight = undefined;
-
-	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;
-			}
+L.Control.Header.rowHeaderWidth = undefined;
+L.Control.Header.colHeaderHeight = undefined;
 
-			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;
-						}
-					}
-					if (i < len) {
-						this._currentRange = i;
-						this._currentIndex = index;
-					}
-					else {
-						// we iterated over all entries, reset everything
-						this._currentIndex = undefined;
-						this._currentRange = undefined;
-						this._skipZeroSize = false;
-						return null;
-					}
+/*
+ * GapTickMap is a data structure for handling the dimensions of each
+ * column/row in the column/row header.
+ *
+ * A "tick" is the position of the boundary between two cols/rows, a "gap" is
+ * the space between two ticks - the position and width/height of a col/row.
+ *
+ * Data about ticks is 0-indexed: the first tick (top of row 1 / left of column A) is 0.
+ *
+ * Data about gaps is 1-indexed: The 1st gap (row 1 / column A) is 1, and spans
+ * from tick 0 to tick 1.
+ *
+ * A GapTickMap is fed data coming from a 'viewrowcolumnheaders' UNO message.
+ * That contains the position of some of the ticks. The rest of the tick positions
+ * is extrapolated as follows:
+ * - The first two ticks are assumed to be consecutive. This sets the size of
+ *   the first gap.
+ * - If the position of n-th tick is not explicit, then its position is the (n-1)-th tick plus
+ *   the size of the last known gap.
+ * - When a new tick position is defined, it resets the size of the last known gap
+ *
+ * All the outputs are mapped through a scale function. Inputs are meant
+ * to be given in twips, outputs are meant to be returned in CSS pixels.
+ */
+L.Control.Header.GapTickMap = L.Class.extend({
+
+	initialize: function (ticks, scaleCallback) {
+
+		var gapSize;
+		this._ticks = [];
+		this.scaleCallback = scaleCallback || function() {return 0;}
+
+		// Sanitize input
+		var knownTicks = [];
+		for (var i in ticks) {
+			// The field in the input data struct is called "text" but it's the
+			// column/row index, as a string.
+			// Idem for "size": it's the tick position in twips, as a string
+			knownTicks[ parseInt(ticks[i].text) ] = parseInt(ticks[i].size);
+		}
+
+		// This *assumes* the input is ordered - i.e. tick indexes only grow
+		this._minTickIdx = parseInt(ticks[0].text);
+		this._maxTickIdx = knownTicks.length - 1;
+
+		for (var idx=this._minTickIdx; idx <= this._maxTickIdx; idx++) {
+
+			if (idx in knownTicks) {
+				this._ticks[idx] = knownTicks[idx];
+				gapSize = this._ticks[idx] - this._ticks[idx - 1];
+			} else {
+				if (isNaN(gapSize) || gapSize < 0) {
+					// This should never happen, unless data from the UNO message
+					// is not in strictly increasing order, or the first two ticks
+					// are not consecutive.
+					throw new Error('Malformed data for column/row sizes.');
 				}
+				this._ticks[idx] = this._ticks[idx - 1] + gapSize;
 			}
+		}
+	},
 
-			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;
+	// Gets the position of the i-th tick (or `undefined` if the index falls outside).
+	getTick: function getTick(i) {
+		return this.scaleCallback(this._ticks[i]);
+	},
 
-			while (minIndex <= maxIndex) {
-				currentIndex = (minIndex + maxIndex) / 2 | 0;
-				currentElement = collection[currentIndex];
+	getMinTickIdx: function() {
+		return this._minTickIdx;
+	},
+	getMaxTickIdx: function() {
+		return this._maxTickIdx;
+	},
 
-				if (currentElement < searchElement) {
-					minIndex = currentIndex + 1;
-				}
-				else if (currentElement > searchElement) {
-					maxIndex = currentIndex - 1;
-				}
-				else {
-					return currentIndex;
-				}
-			}
+	// Gets the start and size of the i-th gap.
+	// returns an Object of the form {index: i, pos: start, size: width/height },
+	// or `undefined` if the index falls outside.
+	getGap: function getGap(i) {
+		var start = this.getTick(i-1);
+		var end = this.getTick(i);
 
-			if (currentIndex > maxIndex)
-				return currentIndex - 1;
-			if (currentIndex < minIndex)
-				return currentIndex;
-		},
+		if (start === undefined || end === undefined || isNaN(start) || isNaN(end)) {
+			return undefined;
+		}
 
-		_getFirstIndexLessOrEqual: function (index) {
-			return this._binaryIndexOf(this._indexes, index);
-		},
+		return {
+			index: i,
+			start: start,
+			end: end,
+			size: end - start,
+			pos: end,
+		}
+	},
 
-		_twipsToPixels: function (twips) {
-			if (!this.converter)
-				return 0;
+	// Returns true when the i-th gap has zero size.
+	isZeroSize: function isZeroSize(i) {
+		return this.getGap(i).size === 0;
+	},
 
-			return this.converter(twips);
-		},
+	// Runs the given callback function for each tick
+	// The callback function receives two parameters: the tick index and the
+	// (interpolated) tick position
+	forEachTick: function forEachTick(callback) {
+		for (var idx=this._minTickIdx; idx <= this._maxTickIdx; idx++) {
+			if (callback(idx, this.getTick(idx)))
+				break;
+		}
+	},
 
-		_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};
+	// Runs the given callback function for each gap
+	// The callback receives one parameter, in the same format as the return value
+	// of getGap()
+	forEachGap: function forEachGap(callback) {
+		for (var idx=this._minTickIdx; idx < this._maxTickIdx; idx++) {
+			if (callback(this.getGap(idx+1)))
+				break;
 		}
+	},
 
-	});
+});
 
-})();
diff --git a/loleaflet/src/control/Control.RowHeader.js b/loleaflet/src/control/Control.RowHeader.js
index ab566a9a1..d4c4c9eeb 100644
--- a/loleaflet/src/control/Control.RowHeader.js
+++ b/loleaflet/src/control/Control.RowHeader.js
@@ -57,7 +57,6 @@ L.Control.RowHeader = L.Control.Header.extend({
 			},
 			this);
 
-		this._startHeaderIndex = 0;
 		this._startOffset = 0;
 		this._position = 0;
 
@@ -165,7 +164,7 @@ L.Control.RowHeader = L.Control.Header.extend({
 	},
 
 	_onClearSelection: function () {
-		this.clearSelection(this._data);
+		this.clearSelection();
 	},
 
 	_onUpdateSelection: function (e) {
@@ -177,14 +176,14 @@ L.Control.RowHeader = L.Control.Header.extend({
 		if (end !== -1) {
 			end = this._twipsToPixels(end);
 		}
-		this.updateSelection(this._data, start, end);
+		this.updateSelection(start, end);
 	},
 
 	_onUpdateCurrentRow: function (e) {
-		var y = e.curY - this._startHeaderIndex;
+		var y = e.curY;
 		var h = this._twipsToPixels(e.height);
 		var slim = h <= 1;
-		this.updateCurrent(this._data, y, slim);
+		this.updateCurrent(y, slim);
 	},
 
 	_updateRowHeader: function () {
@@ -196,7 +195,7 @@ L.Control.RowHeader = L.Control.Header.extend({
 			return;
 
 		var ctx = this._canvasContext;
-		var content = entry.index + this._startHeaderIndex;
+		var content = entry.index;
 		var startOrt = this._canvasWidth - this._headerWidth;
 		var startPar = entry.pos - entry.size - this._startOffset;
 		var endPar = entry.pos - this._startOffset;
@@ -347,8 +346,9 @@ L.Control.RowHeader = L.Control.Header.extend({
 
 	getHeaderEntryBoundingClientRect: function (index) {
 		var entry = this._mouseOverEntry;
-		if (index)
-			entry = this._data.get(index);
+		if (index) {
+			entry = this._tickMap.get(index);
+		}
 
 		if (!entry)
 			return;
@@ -375,26 +375,25 @@ L.Control.RowHeader = L.Control.Header.extend({
 		if (rows.length < 2)
 			return;
 
-		var headerEntry, index, iterator, height, pos;
-
 		var canvas = this._canvas;
 		this._setCanvasWidth();
 		this._setCanvasHeight();
 		this._canvasContext.clearRect(0, 0, canvas.width, canvas.height);
 
-		// update first header index and reset no more valid variables
-		this._startHeaderIndex = parseInt(rows[0].text);
+		// Reset state
 		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();
+		// create data structure for row heights
+		this._tickMap = new L.Control.Header.GapTickMap(
+			rows,
+			L.Util.bind(this._twipsToPixels, this)
+		);
 
 		// setup conversion routine
 		this.converter = L.Util.bind(converter, context);
-		this._data.converter = L.Util.bind(this._twipsToPixels, this);
 
 		// create group array
 		this._groupLevels = parseInt(rows[0].groupLevels);
@@ -403,28 +402,6 @@ L.Control.RowHeader = L.Control.Header.extend({
 		var startOffsetTw = parseInt(rows[0].size);
 		this._startOffset = 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._startHeaderIndex;
-			headerEntry = {pos: pos, size: height};
-			this._data.pushBack(index, headerEntry);
-		}
-
-		// setup last header entry
-		index = nextIndex - this._startHeaderIndex;
-		pos = parseInt(rows[last].size);
-		height = pos - prevPos;
-		this._data.pushBack(index, {pos: pos, size: height});
-
 		// collect group controls data
 		if (rowGroups !== undefined && this._groups) {
 			this._collectGroupsData(rowGroups);
@@ -437,12 +414,10 @@ L.Control.RowHeader = L.Control.Header.extend({
 			this.resize(this._headerWidth);
 		}
 
-		// draw header
-		headerEntry = this._data.getFirst();
-		while (headerEntry) {
-			this.drawHeaderEntry(headerEntry, false);
-			headerEntry = this._data.getNext();
-		}
+		// Initial draw
+		this._tickMap.forEachGap(function(gap) {
+			this.drawHeaderEntry(gap, false);
+		}.bind(this));
 
 		// draw group controls
 		this.drawOutline();
@@ -477,7 +452,7 @@ L.Control.RowHeader = L.Control.Header.extend({
 		if (!this._mouseOverEntry)
 			return;
 
-		var row = this._mouseOverEntry.index + this._startHeaderIndex;
+		var row = this._mouseOverEntry.index;
 
 		var modifier = 0;
 		if (e.shiftKey) {
@@ -551,9 +526,9 @@ L.Control.RowHeader = L.Control.Header.extend({
 		var clickedRow = this._mouseOverEntry;
 		if (clickedRow) {
 			var height = clickedRow.size;
-			var row = clickedRow.index + this._startHeaderIndex;
+			var row = clickedRow.index;
 
-			if (this._data.isZeroSize(clickedRow.index + 1)) {
+			if (this._tickMap.isZeroSize(clickedRow.index + 1)) {
 				row += 1;
 				height = 0;
 			}
@@ -584,7 +559,7 @@ L.Control.RowHeader = L.Control.Header.extend({
 			return;
 
 		if (clicks === 2) {
-			var row = this._mouseOverEntry.index + this._startHeaderIndex;
+			var row = this._mouseOverEntry.index;
 			var command = {
 				Row: {
 					type: 'long',


More information about the Libreoffice-commits mailing list