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

Dennis Francis (via logerrit) logerrit at kemper.freedesktop.org
Wed Jul 8 15:02:27 UTC 2020


 loleaflet/src/control/Control.ColumnHeader.js |   77 +++--
 loleaflet/src/control/Control.Header.js       |  377 ++++++++++++++------------
 loleaflet/src/control/Control.RowHeader.js    |   77 +++--
 loleaflet/src/layer/CalcGridLines.js          |   28 -
 loleaflet/src/layer/tile/CalcTileLayer.js     |    3 
 5 files changed, 323 insertions(+), 239 deletions(-)

New commits:
commit 0b50166333219a5e111aa6e5344f940ead6ad628
Author:     Dennis Francis <dennis.francis at collabora.com>
AuthorDate: Tue Jul 7 15:42:47 2020 +0530
Commit:     Dennis Francis <dennis.francis at collabora.com>
CommitDate: Wed Jul 8 17:02:07 2020 +0200

    make row/col headers render correctly for split-panes
    
    For this to work, we need sheet-geometry data. GapTickMap is dropped
    with a replacement HeaderInfo class that is easier/less confusing to
    work with split-panes. All indices in HeaderInfo are 0-based and a
    column/row is referred to as an 'element' when things have to be
    generic.
    
    Change-Id: Ibddac8901d48cada554b715af70195ef9b9832e2
    Reviewed-on: https://gerrit.libreoffice.org/c/online/+/98357
    Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoffice at gmail.com>
    Tested-by: Jenkins
    Reviewed-by: Dennis Francis <dennis.francis at collabora.com>

diff --git a/loleaflet/src/control/Control.ColumnHeader.js b/loleaflet/src/control/Control.ColumnHeader.js
index 80559a20d..8a07f1e86 100644
--- a/loleaflet/src/control/Control.ColumnHeader.js
+++ b/loleaflet/src/control/Control.ColumnHeader.js
@@ -11,6 +11,7 @@ L.Control.ColumnHeader = L.Control.Header.extend({
 
 	onAdd: function (map) {
 		map.on('updatepermission', this._onUpdatePermission, this);
+		map.on('moveend zoomchanged sheetgeometrychanged splitposchanged', this._updateCanvas, this);
 		this._initialized = false;
 	},
 
@@ -159,6 +160,13 @@ L.Control.ColumnHeader = L.Control.Header.extend({
 		this._updateColumnHeader();
 	},
 
+	_updateCanvas: function () {
+		if (this._headerInfo) {
+			this._headerInfo.update();
+			this._redrawHeaders();
+		}
+	},
+
 	setScrollPosition: function (e) {
 		var position = -e.x;
 		this._position = Math.min(0, position);
@@ -186,7 +194,7 @@ L.Control.ColumnHeader = L.Control.Header.extend({
 	},
 
 	_onUpdateCurrentColumn: function (e) {
-		var x = e.curX;
+		var x = e.curX - 1; // 1-based to 0-based.
 		var w = this._twipsToPixels(e.width);
 		var slim = w <= 1;
 		this.updateCurrent(x, slim);
@@ -201,10 +209,10 @@ L.Control.ColumnHeader = L.Control.Header.extend({
 			return;
 
 		var ctx = this._canvasContext;
-		var content = this._colIndexToAlpha(entry.index);
+		var content = this._colIndexToAlpha(entry.index + 1);
 		var startOrt = this._canvasHeight - this._headerHeight;
-		var startPar = entry.pos - entry.size - this._startOffset;
-		var endPar = entry.pos - this._startOffset;
+		var startPar = entry.pos - entry.size;
+		var endPar = entry.pos;
 		var width = endPar - startPar;
 		var height = this._headerHeight;
 
@@ -218,7 +226,6 @@ L.Control.ColumnHeader = L.Control.Header.extend({
 		ctx.save();
 		var scale = L.getDpiScaleFactor();
 		ctx.scale(scale, scale);
-		ctx.translate(this._position + this._startOffset, 0);
 		// background gradient
 		var selectionBackgroundGradient = null;
 		if (isHighlighted) {
@@ -279,14 +286,13 @@ L.Control.ColumnHeader = L.Control.Header.extend({
 		var level = group.level;
 
 		var startOrt = spacing + (headSize + spacing) * level;
-		var startPar = group.startPos - this._startOffset;
+		var startPar = this._headerInfo.docToHeaderPos(group.startPos);
 		var height = group.endPos - group.startPos;
 
 		ctx.save();
 		var scale = L.getDpiScaleFactor();
 		ctx.scale(scale, scale);
 
-		ctx.translate(this._position + this._startOffset, 0);
 		// clip mask
 		ctx.beginPath();
 		ctx.rect(startPar, startOrt, height, headSize);
@@ -357,7 +363,7 @@ L.Control.ColumnHeader = L.Control.Header.extend({
 	getHeaderEntryBoundingClientRect: function (index) {
 		var entry = this._mouseOverEntry;
 		if (index) {
-			entry = this._tickMap.getGap(index);
+			entry = this._headerInfo.getColData(index);
 		}
 
 		if (!entry)
@@ -365,8 +371,8 @@ L.Control.ColumnHeader = L.Control.Header.extend({
 
 		var rect = this._canvas.getBoundingClientRect();
 
-		var colStart = entry.pos - entry.size + this._position;
-		var colEnd = entry.pos + this._position;
+		var colStart = entry.pos - entry.size;
+		var colEnd = entry.pos;
 
 		var left = rect.left + colStart;
 		var right = rect.left + colEnd;
@@ -408,11 +414,12 @@ L.Control.ColumnHeader = L.Control.Header.extend({
 		}
 
 		var sheetGeometry = this._map._docLayer.sheetGeometry;
-		var columnsGeometry = sheetGeometry ? sheetGeometry.getColumnsGeometry() : undefined;
 
-		// create data structure for column widths
-		this._tickMap = new L.Control.Header.GapTickMap(this._map, columns, columnsGeometry);
-		this._startOffset = this._tickMap.getStartOffset();
+		if (!this._headerInfo) {
+			// create data structure for column widths
+			this._headerInfo = new L.Control.Header.HeaderInfo(this._map, true /* isCol */);
+			this._map._colHdr = this._headerInfo;
+		}
 
 		// setup conversion routine
 		this.converter = L.Util.bind(converter, context);
@@ -437,13 +444,7 @@ L.Control.ColumnHeader = L.Control.Header.extend({
 			this.resize(this._headerHeight);
 		}
 
-		// Initial draw
-		this._tickMap.forEachGap(function(gap) {
-			this.drawHeaderEntry(gap, false);
-		}.bind(this));
-
-		// draw group controls
-		this.drawOutline();
+		this._redrawHeaders();
 
 		this.mouseInit(canvas);
 
@@ -452,6 +453,16 @@ L.Control.ColumnHeader = L.Control.Header.extend({
 		}
 	},
 
+	_redrawHeaders: function () {
+		this._canvasContext.clearRect(0, 0, this._canvas.width, this._canvas.height);
+		this._headerInfo.forEachElement(function(elemData) {
+			this.drawHeaderEntry(elemData, false);
+		}.bind(this));
+
+		// draw group controls
+		this.drawOutline();
+	},
+
 	_colAlphaToNumber: function(alpha) {
 		var res = 0;
 		var offset = 'A'.charCodeAt();
@@ -482,7 +493,7 @@ L.Control.ColumnHeader = L.Control.Header.extend({
 		var command = {
 			Col: {
 				type: 'unsigned short',
-				value: colNumber - 1
+				value: colNumber
 			},
 			Modifier: {
 				type: 'unsigned short',
@@ -549,11 +560,18 @@ L.Control.ColumnHeader = L.Control.Header.extend({
 	},
 
 	_getVertLatLng: function (start, offset, e) {
-		var limit = this._map.mouseEventToContainerPoint({clientX: start.x, clientY: start.y});
+		var size = this._map.getSize();
 		var drag = this._map.mouseEventToContainerPoint(e);
+		var entryStart = this._dragEntry.pos - this._dragEntry.size;
+		var xdocpos = this._headerInfo.headerToDocPos(Math.max(drag.x, entryStart));
+		var ymin = this._map.getPixelBounds().min.y;
+		var ymax = ymin + size.y;
+		if (this._headerInfo.hasSplits()) {
+			ymin = 0;
+		}
 		return [
-			this._map.containerPointToLatLng(new L.Point(Math.max(limit.x, drag.x + offset.x), 0)),
-			this._map.containerPointToLatLng(new L.Point(Math.max(limit.x, drag.x + offset.x), this._map.getSize().y))
+			this._map.unproject(new L.Point(xdocpos, ymin)),
+			this._map.unproject(new L.Point(xdocpos, ymax)),
 		];
 	},
 
@@ -583,8 +601,9 @@ L.Control.ColumnHeader = L.Control.Header.extend({
 			var width = clickedColumn.size;
 			var column = clickedColumn.index;
 
-			if (this._tickMap.isZeroSize(clickedColumn.index + 1)) {
-				column += 1;
+			var nextCol = this._headerInfo.getNextIndex(clickedColumn.index);
+			if (this._headerInfo.isZeroSize(nextCol)) {
+				column = nextCol;
 				width = 0;
 			}
 
@@ -596,7 +615,7 @@ L.Control.ColumnHeader = L.Control.Header.extend({
 					},
 					Column: {
 						type: 'unsigned short',
-						value: column
+						value: column + 1 // core expects 1-based index.
 					}
 				};
 
@@ -619,7 +638,7 @@ L.Control.ColumnHeader = L.Control.Header.extend({
 			var command = {
 				Col: {
 					type: 'unsigned short',
-					value: column - 1
+					value: column
 				},
 				Modifier: {
 					type: 'unsigned short',
diff --git a/loleaflet/src/control/Control.Header.js b/loleaflet/src/control/Control.Header.js
index 3ef4bb2e0..8dff63acc 100644
--- a/loleaflet/src/control/Control.Header.js
+++ b/loleaflet/src/control/Control.Header.js
@@ -172,17 +172,17 @@ L.Control.Header = L.Control.extend({
 	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 start = (this._selection.start < 1) ? 0 : this._selection.start;
+		var end = this._headerInfo.getNextIndex(this._selection.end);
 
-		for (var i=start; i<end; i++) {
+		for (var i = start; i < end; i = this._headerInfo.getNextIndex(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);
+				this.select(this._headerInfo.getElementData(i), true);
 			} else {
-				this.unselect(this._tickMap.getGap(i));
+				this.unselect(this._headerInfo.getElementData(i));
 			}
 		}
 
@@ -193,25 +193,28 @@ L.Control.Header = L.Control.extend({
 	// 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)
+		if (!this._headerInfo)
 			return;
 
+		start = this._headerInfo.docToHeaderPos(start);
+		end = this._headerInfo.docToHeaderPos(end);
+
 		var x0 = 0, x1 = 0;
 		var itStart = -1, itEnd = -1;
 
 		// 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 = this._tickMap.getGap(this._tickMap.getMinTickIdx() + 1);
+		var entry = this._headerInfo.getElementData(this._headerInfo.getMinIndex());
 		if (entry) {
 			x0 = entry.pos - entry.size;
 			if (start < x0 && end > x0) {
-				itStart = 1;
+				itStart = 0;
 			}
 		}
 
-		this._tickMap.forEachGap((function(entry) {
-			x0 = entry.start;
+		this._headerInfo.forEachElement((function(entry) {
+			x0 = entry.pos - entry.size;
 			x1 = entry.pos;
 			if (start < x1 && end > x0) {
 				this.select(entry, false);
@@ -221,7 +224,7 @@ L.Control.Header = L.Control.extend({
 			} else {
 				this.unselect(entry);
 				if (itStart !== -1 && itEnd === -1) {
-					itEnd = entry.index - 1;
+					itEnd = this._headerInfo.getPreviousIndex(entry.index);
 				}
 			}
 		}).bind(this));
@@ -229,7 +232,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 = this._tickMap.getMaxTickIdx() - 1;
+			itEnd = this._headerInfo.getMaxIndex();
 		}
 
 		this._selection.start = itStart;
@@ -240,24 +243,24 @@ L.Control.Header = L.Control.extend({
 	// 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 (!this._headerInfo) {return;}
 
 		if (cursorPos < 0) {
-			this.unselect(this._tickMap.getGap(this._current));
+			this.unselect(this._headerInfo.getElementData(this._current));
 			this._current = -1;
 			return;
 		}
 
-		var prevEntry = cursorPos > 0 ? this._tickMap.getGap(cursorPos - 1) : null;
+		var prevEntry = cursorPos > 0 ? this._headerInfo.getPreviousIndex(cursorPos) : null;
 		var zeroSizeEntry = slim && prevEntry && prevEntry.size === 0;
 
-		var entry = this._tickMap.getGap(cursorPos);
+		var entry = this._headerInfo.getElementData(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(this._tickMap.getGap(this._current));
+			this.unselect(this._headerInfo.getElementData(this._current));
 			// no selection when the cell cursor is slim
 			if (entry && !zeroSizeEntry)
 				this.select(entry, true);
@@ -280,22 +283,23 @@ L.Control.Header = L.Control.extend({
 	},
 
 	_entryAtPoint: function(point) {
-		if (!this._tickMap)
+		if (!this._headerInfo)
 			return false;
 
 		var position = this._getParallelPos(point);
-		position = position - this._position;
 
 		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;
+		this._headerInfo.forEachElement(function(entry) {
+			var end = entry.pos;
+			var start = end - entry.size;
+			if (position >= start && position < end) {
+				var resizeAreaStart = Math.max(start, end - 3);
+				if (that.isHeaderSelected(entry.index)) {
+					resizeAreaStart = end - that._resizeHandleSize;
 				}
 				var isMouseOverResizeArea = (position > resizeAreaStart);
-				result = {entry: gap, hit: isMouseOverResizeArea};
+				result = {entry: entry, hit: isMouseOverResizeArea};
 				return true;
 			}
 		});
@@ -325,6 +329,7 @@ L.Control.Header = L.Control.extend({
 		this._start = new L.Point(rectangle.left, rectangle.top);
 		this._offset = new L.Point(rectangle.right - event.center.x, rectangle.bottom - event.center.y);
 		this._item = target;
+		this._dragEntry = result.entry;
 		this.onDragStart(this._item, this._start, this._offset, {clientX: event.center.x, clientY: event.center.y});
 	},
 
@@ -337,6 +342,7 @@ L.Control.Header = L.Control.extend({
 		L.DomUtil.enableTextSelection();
 
 		this.onDragEnd(this._item, this._start, this._offset, {clientX: event.center.x, clientY: event.center.y});
+		this._dragEntry = null;
 
 		this._mouseOverEntry = null;
 		this._item = this._start = this._offset = null;
@@ -402,7 +408,7 @@ L.Control.Header = L.Control.extend({
 			entry = result.entry;
 		}
 
-		if (mouseOverIndex && (!this._mouseOverEntry || mouseOverIndex !== this._mouseOverEntry.index)) {
+		if (typeof mouseOverIndex === 'number' && (!this._mouseOverEntry || mouseOverIndex !== this._mouseOverEntry.index)) {
 			var mouseOverIsCurrent = false;
 			if (this._mouseOverEntry != null) {
 				mouseOverIsCurrent = (this._mouseOverEntry.index == this._current);
@@ -456,8 +462,8 @@ L.Control.Header = L.Control.extend({
 		if (!group)
 			return false;
 
-		var pos = this._getParallelPos(this._mouseEventToCanvasPos(this._canvas, e));
-		pos = pos - this._position;
+		var pos = this._headerInfo.headerToDocPos(
+			this._getParallelPos(this._mouseEventToCanvasPos(this._canvas, e)));
 		if (group.startPos < pos && pos < group.startPos + this._groupHeadSize) {
 			this._updateOutlineState(/*isColumnOutline: */ this._isColumn, group);
 			return true;
@@ -474,8 +480,8 @@ L.Control.Header = L.Control.extend({
 		if (!group && !group.hidden)
 			return false;
 
-		var pos = this._getParallelPos(this._mouseEventToCanvasPos(this._canvas, e));
-		pos = pos - this._position;
+		var pos = this._headerInfo.headerToDocPos(
+			this._getParallelPos(this._mouseEventToCanvasPos(this._canvas, e)));
 		if (group.startPos + this._groupHeadSize < pos && pos < group.endPos) {
 			this._updateOutlineState(/*isColumnOutline: */ this._isColumn, group);
 			return true;
@@ -525,6 +531,7 @@ L.Control.Header = L.Control.extend({
 		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;
+		this._dragEntry = this._mouseOverEntry;
 
 		this.onDragStart(this._item, this._start, this._offset, e);
 	},
@@ -561,6 +568,7 @@ L.Control.Header = L.Control.extend({
 
 		this._item = this._start = this._offset = null;
 		this._dragging = false;
+		this._dragEntry = null;
 	},
 
 	_twipsToPixels: function (twips) {
@@ -771,165 +779,208 @@ L.Control.Header = L.Control.extend({
 L.Control.Header.rowHeaderWidth = undefined;
 L.Control.Header.colHeaderHeight = undefined;
 
-/*
- * 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 inputs received are given in tile pixels, and stored as such.
- * outputs are returned in CSS pixels.
- *
- * **NB.** CSS pixels are scaled (down) from the tile pixels by the a factor
- * from TileLayer - 2x for retina, 1x for non-retina.
- *
- * **NB.** twip to pixel mapping is made non-obvious by the need to ensure that
- * there are no cumulative rounding errors from TWIP heights to pixels. We have to
- * match the core here, so we just use pixels.
- */
-L.Control.Header.GapTickMap = L.Class.extend({
-
-	initialize: function (map, ticks, dimensionGeometry) {
-
-		if (dimensionGeometry) {
-			// Until .uno:ViewRowColumnHeaders is not phased out, we need to live with
-			// GapTickMap datastructure to avoid an invasive refactoring.
-			// L.SheetGeometry and L.SheetDimension datastructures can directly provide
-			// position/size of any row/column intuitively without using unnecessary
-			// terminologies like (1-based) Gap and (0-based) Tick.
-			var dimrange = dimensionGeometry.getViewElementRange();
-			var start = Math.max(0, dimrange.start - 2);
-			var startData = dimensionGeometry.getElementData(start);
-			var startText = start ? start + 1 : 0;
-			var endText = Math.min(dimensionGeometry.getMaxIndex(), dimrange.end + 2) + 1;
-
-			this._minTickIdx = startText;
-			this._maxTickIdx = endText;
-			this._startOffset = start ? startData.startpos + startData.size : 0;
-			this._tilePixelScale = 1; // We already have everything in css px.
-
-			ticks = start ? [] : [0];
-			dimensionGeometry.forEachInRange(start,
-				this._maxTickIdx - 1, function (idx, data) {
-					ticks[idx + 1] = data.startpos + data.size;
-				});
-
-			this._ticks = ticks;
-
-			return;
+L.Control.Header.HeaderInfo = L.Class.extend({
+
+	initialize: function (map, isCol) {
+		console.assert(map && isCol !== undefined, 'map and isCol required');
+		this._map = map;
+		this._isCol = isCol;
+		console.assert(this._map._docLayer.sheetGeometry, 'no sheet geometry data-structure found!');
+		var sheetGeom = this._map._docLayer.sheetGeometry;
+		this._dimGeom = this._isCol ? sheetGeom.getColumnsGeometry() : sheetGeom.getRowsGeometry();
+		this.update();
+	},
+
+	update: function () {
+		var bounds = this._map.getPixelBounds();
+		var startPx = this._isCol ? bounds.getTopLeft().x : bounds.getTopLeft().y;
+		this._docVisStart = startPx;
+		var endPx = this._isCol ? bounds.getBottomRight().x : bounds.getBottomRight().y;
+		var startIdx = this._dimGeom.getIndexFromPos(startPx, 'csspixels');
+		var endIdx = this._dimGeom.getIndexFromPos(endPx - 1, 'csspixels');
+		this._elements = [];
+
+		var splitPosContext = this._map.getSplitPanesContext();
+
+		this._hasSplits = false;
+		this._splitIndex = 0;
+		var splitPos = 0;
+
+		if (splitPosContext) {
+
+			splitPos = this._isCol ? splitPosContext.getSplitPos().x : splitPosContext.getSplitPos().y;
+			var splitIndex = this._dimGeom.getIndexFromPos(splitPos + 1, 'csspixels');
+
+			if (splitIndex) {
+				// Make sure splitPos is aligned to the cell boundary.
+				splitPos = this._dimGeom.getElementData(splitIndex).startpos;
+				this._splitPos = splitPos;
+				this._dimGeom.forEachInRange(0, splitIndex - 1,
+					function (idx, data) {
+						this._elements[idx] = {
+							index: idx,
+							pos: data.startpos + data.size, // end position on the header canvas
+							size: data.size,
+							origsize: data.size,
+						};
+					}.bind(this)
+				);
+
+				this._hasSplits = true;
+				this._splitIndex = splitIndex;
+
+				var freeStartPos = startPx + splitPos + 1;
+				var freeStartIndex = this._dimGeom.getIndexFromPos(freeStartPos + 1, 'csspixels');
+
+				startIdx = freeStartIndex;
+			}
 		}
 
-		var gapSize;
-		this._ticks = [];
+		// first free index
+		var dataFirstFree = this._dimGeom.getElementData(startIdx);
+		var firstFreeEnd = dataFirstFree.startpos + dataFirstFree.size - startPx;
+		var firstFreeStart = splitPos;
+		var firstFreeSize = Math.max(0, firstFreeEnd - firstFreeStart);
+		this._elements[startIdx] = {
+			index: startIdx,
+			pos: firstFreeEnd, // end position on the header canvas
+			size: firstFreeSize,
+			origsize: dataFirstFree.size,
+		};
 
-		// 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 pixels, as a string
-			knownTicks[ parseInt(ticks[i].text) ] = parseInt(ticks[i].size);
-		}
+		this._dimGeom.forEachInRange(startIdx + 1,
+			endIdx, function (idx, data) {
+				var startpos = data.startpos - startPx;
+				var endpos = startpos + data.size;
+				var size = endpos - startpos;
+				this._elements[idx] = {
+					index: idx,
+					pos: endpos, // end position on the header canvas
+					size: size,
+					origsize: size,
+				};
+			}.bind(this));
 
-		// This *assumes* the input is ordered - i.e. tick indexes only grow
-		this._minTickIdx = parseInt(ticks[0].text);
-		this._maxTickIdx = knownTicks.length - 1;
-		this._startOffset = parseInt(ticks[0].size);
-		this._tilePixelScale = map._docLayer._tilePixelScale;
+		this._startIndex = startIdx;
+		this._endIndex = endIdx;
+	},
 
-		for (var idx=this._minTickIdx; idx <= this._maxTickIdx; idx++) {
+	docToHeaderPos: function (docPos) {
 
-			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;
-			}
+		if (!this._hasSplits) {
+			return docPos - this._docVisStart;
 		}
+
+		if (docPos <= this._splitPos) {
+			return docPos;
+		}
+
+		// max here is to prevent encroachment of the fixed pane-area.
+		return Math.max(docPos - this._docVisStart, this._splitPos);
 	},
 
-	// Gets the position of the i-th tick (or `undefined` if the index falls outside).
-	getTick: function getTick(i) {
-		// to get CSS pixels we need to adjust for DPI scale
-		// since we render at full native pixel resolution &
-		// account in those units.
-		return this._ticks[i] / this._tilePixelScale;
+	headerToDocPos: function (hdrPos) {
+		if (!this._hasSplits) {
+			return hdrPos + this._docVisStart;
+		}
+
+		if (hdrPos <= this._splitPos) {
+			return hdrPos;
+		}
+
+		return hdrPos + this._docVisStart;
 	},
 
 	getStartOffset: function() {
-		return this._startOffset / this._tilePixelScale;
+		return 0;
 	},
 
-	getMinTickIdx: function() {
-		return this._minTickIdx;
+	isZeroSize: function (i) {
+		var elem = this._elements[i];
+		console.assert(elem, 'queried a non existent row/col in the header : ' + i);
+		return elem.size === 0;
 	},
-	getMaxTickIdx: function() {
-		return this._maxTickIdx;
+
+	hasSplits: function () {
+		return this._hasSplits;
 	},
 
-	// 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);
+	// Index after the split.
+	getSplitIndex: function () {
+		return this._splitIndex;
+	},
 
-		if (start === undefined || end === undefined || isNaN(start) || isNaN(end)) {
-			return undefined;
-		}
+	getStartIndex: function () {
+		return this._startIndex;
+	},
 
-		return {
-			index: i,
-			start: start,
-			end: end,
-			size: end - start,
-			pos: end,
-		};
+	getEndIndex: function () {
+		return this._endIndex;
 	},
 
-	// Returns true when the i-th gap has zero size.
-	isZeroSize: function isZeroSize(i) {
-		return this.getGap(i).size === 0;
+	getMinIndex: function () {
+		return this._hasSplits ? 0 : this._startIndex;
 	},
 
-	// 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;
+	getMaxIndex: function () {
+		return this._endIndex;
+	},
+
+	getElementData: function (index) {
+		return this._elements[index];
+	},
+
+	getRowData: function (index) {
+		console.assert(!this._isCol, 'this is a column header instance!');
+		return this.getElementData(index);
+	},
+
+	getColData: function (index) {
+		console.assert(this._isCol, 'this is a row header instance!');
+		return this.getElementData(index);
+	},
+
+	getPreviousIndex: function (index) {
+
+		var prevIndex;
+		if (this._splitIndex && index === this._startIndex) {
+			prevIndex = this._splitIndex - 1;
 		}
+		else {
+			prevIndex = index - 1;
+		}
+
+		return prevIndex;
 	},
 
-	// 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;
+	getNextIndex: function (index) {
+
+		var nextIndex;
+		if (this._splitIndex && index === (this._splitIndex - 1)) {
+			nextIndex = this._startIndex;
+		}
+		else {
+			nextIndex = index + 1;
+		}
+
+		return nextIndex;
+	},
+
+	forEachElement: function (callback) {
+		var idx;
+		if (this._hasSplits) {
+			for (idx = 0; idx < this._splitIndex; ++idx) {
+				console.assert(this._elements[idx], 'forEachElement failed');
+				if (callback(this._elements[idx])) {
+					return;
+				}
+			}
+		}
+		for (idx = this._startIndex; idx <= this._endIndex; ++idx) {
+			console.assert(this._elements[idx], 'forEachElement failed');
+			if (callback(this._elements[idx])) {
+				return;
+			}
 		}
 	},
 
diff --git a/loleaflet/src/control/Control.RowHeader.js b/loleaflet/src/control/Control.RowHeader.js
index 820f2205b..2e55fc4b7 100644
--- a/loleaflet/src/control/Control.RowHeader.js
+++ b/loleaflet/src/control/Control.RowHeader.js
@@ -11,6 +11,7 @@ L.Control.RowHeader = L.Control.Header.extend({
 
 	onAdd: function (map) {
 		map.on('updatepermission', this._onUpdatePermission, this);
+		map.on('moveend zoomchanged sheetgeometrychanged splitposchanged', this._updateCanvas, this);
 		this._initialized = false;
 	},
 
@@ -152,6 +153,13 @@ L.Control.RowHeader = L.Control.Header.extend({
 		this._map.sendUnoCommand('.uno:ShowRow');
 	},
 
+	_updateCanvas: function () {
+		if (this._headerInfo) {
+			this._headerInfo.update();
+			this._redrawHeaders();
+		}
+	},
+
 	setScrollPosition: function (e) {
 		var position = -e.y;
 		this._position = Math.min(0, position);
@@ -179,7 +187,7 @@ L.Control.RowHeader = L.Control.Header.extend({
 	},
 
 	_onUpdateCurrentRow: function (e) {
-		var y = e.curY;
+		var y = e.curY - 1; // 1-based to 0-based.
 		var h = this._twipsToPixels(e.height);
 		var slim = h <= 1;
 		this.updateCurrent(y, slim);
@@ -194,10 +202,10 @@ L.Control.RowHeader = L.Control.Header.extend({
 			return;
 
 		var ctx = this._canvasContext;
-		var content = entry.index;
+		var content = entry.index + 1;
 		var startOrt = this._canvasWidth - this._headerWidth;
-		var startPar = entry.pos - entry.size - this._startOffset;
-		var endPar = entry.pos - this._startOffset;
+		var startPar = entry.pos - entry.size;
+		var endPar = entry.pos;
 		var height = endPar - startPar;
 		var width = this._headerWidth;
 
@@ -211,7 +219,6 @@ L.Control.RowHeader = L.Control.Header.extend({
 		ctx.save();
 		var scale = L.getDpiScaleFactor();
 		ctx.scale(scale, scale);
-		ctx.translate(0, this._position + this._startOffset);
 		// background gradient
 		var selectionBackgroundGradient = null;
 		if (isHighlighted) {
@@ -268,14 +275,13 @@ L.Control.RowHeader = L.Control.Header.extend({
 		var level = group.level;
 
 		var startOrt = spacing + (headSize + spacing) * level;
-		var startPar = group.startPos - this._startOffset;
+		var startPar = this._headerInfo.docToHeaderPos(group.startPos);
 		var height = group.endPos - group.startPos;
 
 		ctx.save();
 		var scale = L.getDpiScaleFactor();
 		ctx.scale(scale, scale);
 
-		ctx.translate(0, this._position + this._startOffset);
 		// clip mask
 		ctx.beginPath();
 		ctx.rect(startOrt, startPar, headSize, height);
@@ -347,15 +353,15 @@ L.Control.RowHeader = L.Control.Header.extend({
 		var entry = this._mouseOverEntry;
 
 		if (index)
-			entry = this._tickMap.getGap(index);
+			entry = this._headerInfo.getRowData(index);
 
 		if (!entry)
 			return;
 
 		var rect = this._canvas.getBoundingClientRect();
 
-		var rowStart = entry.pos - entry.size + this._position;
-		var rowEnd = entry.pos + this._position;
+		var rowStart = entry.pos - entry.size;
+		var rowEnd = entry.pos;
 
 		var left = rect.left;
 		var right = rect.right;
@@ -397,11 +403,12 @@ L.Control.RowHeader = L.Control.Header.extend({
 		}
 
 		var sheetGeometry = this._map._docLayer.sheetGeometry;
-		var rowsGeometry = sheetGeometry ? sheetGeometry.getRowsGeometry() : undefined;
 
-		// create data structure for row heights
-		this._tickMap = new L.Control.Header.GapTickMap(this._map, rows, rowsGeometry);
-		this._startOffset = this._tickMap.getStartOffset();
+		if (!this._headerInfo) {
+			// create data structure for row heights
+			this._headerInfo = new L.Control.Header.HeaderInfo(this._map, false /* isCol */);
+			this._map._rowHdr = this._headerInfo;
+		}
 
 		// setup conversion routine
 		this.converter = L.Util.bind(converter, context);
@@ -426,13 +433,7 @@ L.Control.RowHeader = L.Control.Header.extend({
 			this.resize(this._headerWidth);
 		}
 
-		// Initial draw
-		this._tickMap.forEachGap(function(gap) {
-			this.drawHeaderEntry(gap, false);
-		}.bind(this));
-
-		// draw group controls
-		this.drawOutline();
+		this._redrawHeaders();
 
 		this.mouseInit(canvas);
 
@@ -441,11 +442,21 @@ L.Control.RowHeader = L.Control.Header.extend({
 		}
 	},
 
+	_redrawHeaders: function () {
+		this._canvasContext.clearRect(0, 0, this._canvas.width, this._canvas.height);
+		this._headerInfo.forEachElement(function(elemData) {
+			this.drawHeaderEntry(elemData, false);
+		}.bind(this));
+
+		// draw group controls
+		this.drawOutline();
+	},
+
 	_selectRow: function(row, modifier) {
 		var command = {
 			Row: {
 				type: 'long',
-				value: row - 1
+				value: row
 			},
 			Modifier: {
 				type: 'unsigned short',
@@ -505,11 +516,18 @@ L.Control.RowHeader = L.Control.Header.extend({
 	},
 
 	_getHorzLatLng: function (start, offset, e) {
-		var limit = this._map.mouseEventToContainerPoint({clientX: start.x, clientY: start.y});
+		var size = this._map.getSize();
 		var drag = this._map.mouseEventToContainerPoint(e);
+		var entryStart = this._dragEntry.pos - this._dragEntry.size;
+		var ydocpos = this._headerInfo.headerToDocPos(Math.max(drag.y, entryStart));
+		var xmin = this._map.getPixelBounds().min.x;
+		var xmax = xmin + size.x;
+		if (this._headerInfo.hasSplits()) {
+			xmin = 0;
+		}
 		return [
-			this._map.containerPointToLatLng(new L.Point(0, Math.max(limit.y, drag.y + offset.y))),
-			this._map.containerPointToLatLng(new L.Point(this._map.getSize().x, Math.max(limit.y, drag.y + offset.y)))
+			this._map.unproject(new L.Point(xmin, ydocpos)),
+			this._map.unproject(new L.Point(xmax, ydocpos)),
 		];
 	},
 
@@ -539,8 +557,9 @@ L.Control.RowHeader = L.Control.Header.extend({
 			var height = clickedRow.size;
 			var row = clickedRow.index;
 
-			if (this._tickMap.isZeroSize(clickedRow.index + 1)) {
-				row += 1;
+			var nextRow = this._headerInfo.getNextIndex(clickedRow.index);
+			if (this._headerInfo.isZeroSize(nextRow)) {
+				row = nextRow;
 				height = 0;
 			}
 
@@ -552,7 +571,7 @@ L.Control.RowHeader = L.Control.Header.extend({
 					},
 					Row: {
 						type: 'long',
-						value: row
+						value: row + 1 // core expects 1-based index.
 					}
 				};
 
@@ -574,7 +593,7 @@ L.Control.RowHeader = L.Control.Header.extend({
 			var command = {
 				Row: {
 					type: 'long',
-					value: row - 1
+					value: row
 				},
 				Modifier: {
 					type: 'unsigned short',
diff --git a/loleaflet/src/layer/CalcGridLines.js b/loleaflet/src/layer/CalcGridLines.js
index 37492b606..fce802101 100644
--- a/loleaflet/src/layer/CalcGridLines.js
+++ b/loleaflet/src/layer/CalcGridLines.js
@@ -72,23 +72,17 @@ L.CalcGridLines = L.LayerGroup.extend({
 	// Redraw col/row lines whenever new information about them is available.
 	// One websocket message might have info about cols, rows, or both
 	onUpdate: function onUpdate(ev) {
-		var ticks;
+		var headerInfo, pos;
 
-		// Aux stuff to scale twips from the websocket message
-		// into map coordinate units
+		// Aux stuff to convert css pixels to map coordinate units
 		var pixelToMapUnitRatio = this._map.options.crs.scale(this._map.getZoom());
 
-		var colDataInEvent = ev.data && ev.data.columns && ev.data.columns.length;
-		var rowDataInEvent = ev.data && ev.data.rows && ev.data.rows.length;
-
-		if (colDataInEvent || ev.updatecolumns) {
-			var columnsData = colDataInEvent ? ev.data.columns : undefined;
-			var columnsGeometry = colDataInEvent ? undefined : this._map._docLayer.sheetGeometry.getColumnsGeometry();
-			ticks = new L.Control.Header.GapTickMap(this._map, columnsData, columnsGeometry);
+		if (ev.updatecolumns) {
+			headerInfo = new L.Control.Header.HeaderInfo(this._map, true /* isCol */);
 			this._colLines.clearLayers();
 
-			ticks.forEachTick(function(idx, pos) {
-				pos /= pixelToMapUnitRatio;
+			headerInfo.forEachElement(function(columnData) {
+				pos = headerInfo.headerToDocPos(columnData.pos) / pixelToMapUnitRatio;
 				this._colLines.addLayer(
 					L.polyline([[[ L.Util.MIN_SAFE_INTEGER, pos ],[ L.Util.MAX_SAFE_INTEGER, pos ]]],
 						this.options
@@ -97,14 +91,12 @@ L.CalcGridLines = L.LayerGroup.extend({
 			}.bind(this));
 		}
 
-		if (rowDataInEvent || ev.updaterows) {
-			var rowsData = rowDataInEvent ? ev.data.rows : undefined;
-			var rowsGeometry = rowDataInEvent ? undefined : this._map._docLayer.sheetGeometry.getRowsGeometry();
-			ticks = new L.Control.Header.GapTickMap(this._map, rowsData, rowsGeometry);
+		if (ev.updaterows) {
+			headerInfo = new L.Control.Header.HeaderInfo(this._map, false /* isCol */);
 			this._rowLines.clearLayers();
 
-			ticks.forEachTick(function(idx, pos) {
-				pos /= pixelToMapUnitRatio;
+			headerInfo.forEachElement(function(rowData) {
+				pos = headerInfo.headerToDocPos(rowData.pos) / pixelToMapUnitRatio;
 				this._rowLines.addLayer(
 					// Note that y-coordinates are inverted: Leaflet's CRS.Simple assumes
 					// down = negative latlngs, whereas loolkit assumes down = positive twips
diff --git a/loleaflet/src/layer/tile/CalcTileLayer.js b/loleaflet/src/layer/tile/CalcTileLayer.js
index 4ff7b51ba..9d711b48f 100644
--- a/loleaflet/src/layer/tile/CalcTileLayer.js
+++ b/loleaflet/src/layer/tile/CalcTileLayer.js
@@ -449,6 +449,7 @@ L.CalcTileLayer = L.TileLayer.extend({
 		}
 		this._restrictDocumentSize();
 		this._replayPrintTwipsMsgs();
+		this._map.fire('zoomchanged');
 		this.refreshViewData();
 		this._map._socket.sendMessage('commandvalues command=.uno:ViewAnnotationsPosition');
 	},
@@ -719,6 +720,8 @@ L.CalcTileLayer = L.TileLayer.extend({
 			this._pixelsToTwips(this._map.getSize()));
 		this._updateHeadersGridLines(undefined, true /* updateCols */,
 			true /* updateRows */);
+
+		this._map.fire('sheetgeometrychanged');
 	},
 
 	_onCommandValuesMsg: function (textMsg) {


More information about the Libreoffice-commits mailing list