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.fran...@collabora.com> AuthorDate: Tue Jul 7 15:42:47 2020 +0530 Commit: Dennis Francis <dennis.fran...@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 <jenkinscollaboraoff...@gmail.com> Tested-by: Jenkins Reviewed-by: Dennis Francis <dennis.fran...@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) { _______________________________________________ Libreoffice-commits mailing list libreoffice-comm...@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits