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