kit/ChildSession.cpp | 61 +++++- kit/ChildSession.hpp | 2 loleaflet/css/selectionMarkers.css | 2 loleaflet/src/control/Control.LokDialog.js | 253 +++++++++++++++++++++++++++-- wsd/ClientSession.cpp | 1 5 files changed, 290 insertions(+), 29 deletions(-)
New commits: commit a46fa588b1759c5114937eec2824cbabdbf2082b Author: Marco Cecchetti <marco.cecche...@collabora.com> AuthorDate: Thu Feb 6 11:28:00 2020 +0100 Commit: Marco Cecchetti <marco.cecche...@collabora.com> CommitDate: Thu Feb 6 15:58:36 2020 +0100 calc: formula input bar: adding support for text selection handles Change-Id: I6bc276945a7fd33f1358a3aa82ce0e7f45237771 Reviewed-on: https://gerrit.libreoffice.org/c/online/+/88090 Reviewed-by: Marco Cecchetti <marco.cecche...@collabora.com> Tested-by: Marco Cecchetti <marco.cecche...@collabora.com> diff --git a/kit/ChildSession.cpp b/kit/ChildSession.cpp index 58b420eaf..d45891b58 100644 --- a/kit/ChildSession.cpp +++ b/kit/ChildSession.cpp @@ -285,6 +285,7 @@ bool ChildSession::_handleInput(const char *buffer, int length) tokens[0] == "windowgesture" || tokens[0] == "uno" || tokens[0] == "selecttext" || + tokens[0] == "windowselecttext" || tokens[0] == "selectgraphic" || tokens[0] == "resetselection" || tokens[0] == "saveas" || @@ -379,7 +380,11 @@ bool ChildSession::_handleInput(const char *buffer, int length) } else if (tokens[0] == "selecttext") { - return selectText(buffer, length, tokens); + return selectText(buffer, length, tokens, LokEventTargetEnum::Document); + } + else if (tokens[0] == "windowselecttext") + { + return selectText(buffer, length, tokens, LokEventTargetEnum::Window); } else if (tokens[0] == "selectgraphic") { @@ -1478,25 +1483,55 @@ bool ChildSession::unoCommand(const char* /*buffer*/, int /*length*/, const std: return true; } -bool ChildSession::selectText(const char* /*buffer*/, int /*length*/, const std::vector<std::string>& tokens) +bool ChildSession::selectText(const char* /*buffer*/, int /*length*/, + const std::vector<std::string>& tokens, + const LokEventTargetEnum target) { + std::string swap; + unsigned winId = 0; int type, x, y; - if (tokens.size() != 4 || - !getTokenKeyword(tokens[1], "type", - {{"start", LOK_SETTEXTSELECTION_START}, - {"end", LOK_SETTEXTSELECTION_END}, - {"reset", LOK_SETTEXTSELECTION_RESET}}, - type) || - !getTokenInteger(tokens[2], "x", x) || - !getTokenInteger(tokens[3], "y", y)) + if (target == LokEventTargetEnum::Window) { - sendTextFrame("error: cmd=selecttext kind=syntax"); - return false; + if (tokens.size() != 5 || + !getTokenUInt32(tokens[1], "id", winId) || + !getTokenString(tokens[2], "swap", swap) || + (swap != "true" && swap != "false") || + !getTokenInteger(tokens[3], "x", x) || + !getTokenInteger(tokens[4], "y", y)) + { + LOG_ERR("error: cmd=windowselecttext kind=syntax"); + return false; + } + } + else if (target == LokEventTargetEnum::Document) + { + if (tokens.size() != 4 || + !getTokenKeyword(tokens[1], "type", + {{"start", LOK_SETTEXTSELECTION_START}, + {"end", LOK_SETTEXTSELECTION_END}, + {"reset", LOK_SETTEXTSELECTION_RESET}}, + type) || + !getTokenInteger(tokens[2], "x", x) || + !getTokenInteger(tokens[3], "y", y)) + { + sendTextFrame("error: cmd=selecttext kind=syntax"); + return false; + } } getLOKitDocument()->setView(_viewId); - getLOKitDocument()->setTextSelection(type, x, y); + switch (target) + { + case LokEventTargetEnum::Document: + getLOKitDocument()->setTextSelection(type, x, y); + break; + case LokEventTargetEnum::Window: + getLOKitDocument()->setWindowTextSelection(winId, swap == "true", x, y); + break; + default: + assert(false && "Unsupported select text target type"); + } return true; } diff --git a/kit/ChildSession.hpp b/kit/ChildSession.hpp index 2f677facd..4a64be709 100644 --- a/kit/ChildSession.hpp +++ b/kit/ChildSession.hpp @@ -269,7 +269,7 @@ private: bool dialogEvent(const char* buffer, int length, const std::vector<std::string>& tokens); bool completeFunction(const char* buffer, int length, const std::vector<std::string>& tokens); bool unoCommand(const char* buffer, int length, const std::vector<std::string>& tokens); - bool selectText(const char* buffer, int length, const std::vector<std::string>& tokens); + bool selectText(const char* buffer, int length, const std::vector<std::string>& tokens, const LokEventTargetEnum target); bool selectGraphic(const char* buffer, int length, const std::vector<std::string>& tokens); bool renderWindow(const char* buffer, int length, const std::vector<std::string>& tokens); bool resizeWindow(const char* buffer, int length, const std::vector<std::string>& tokens); diff --git a/loleaflet/css/selectionMarkers.css b/loleaflet/css/selectionMarkers.css index 2bf7f791c..70cd6d233 100644 --- a/loleaflet/css/selectionMarkers.css +++ b/loleaflet/css/selectionMarkers.css @@ -1,4 +1,5 @@ .leaflet-selection-marker-start { + position: absolute; margin-left: -28px; width: 30px; height: 44px; @@ -6,6 +7,7 @@ } .leaflet-selection-marker-end { + position: absolute; margin-left: -2px; width: 30px; height: 44px; diff --git a/loleaflet/src/control/Control.LokDialog.js b/loleaflet/src/control/Control.LokDialog.js index a16395258..9595d0496 100644 --- a/loleaflet/src/control/Control.LokDialog.js +++ b/loleaflet/src/control/Control.LokDialog.js @@ -158,6 +158,20 @@ L.Control.LokDialog = L.Control.extend({ return (id in this._dialogs) && this._dialogs[id].isCalcInputBar; }, + _isSelectionHandle: function(el) { + return L.DomUtil.hasClass(el, 'leaflet-selection-marker-start') || + L.DomUtil.hasClass(el, 'leaflet-selection-marker-end'); + }, + + _isSelectionHandleDragged: function() { + if (this._calcInputBar) { + var selectionInfo = this._calcInputBar.textSelection; + return (selectionInfo.startHandle && selectionInfo.startHandle.isDragged) || + (selectionInfo.endHandle && selectionInfo.endHandle.isDragged); + } + return false; + }, + // Given a prefixed dialog id like 'lokdialog-323', gives a raw id, 323. _toIntId: function(id) { if (typeof(id) === 'string') @@ -412,8 +426,16 @@ L.Control.LokDialog = L.Control.extend({ var strId = this._toIntId(dlgId); var selections = this._dialogs[strId].textSelection.rectangles; L.DomUtil.empty(selections); - if (!rectangles) + var handles = this._dialogs[strId].textSelection.handles; + var startHandle = this._dialogs[strId].textSelection.startHandle; + var endHandle = this._dialogs[strId].textSelection.endHandle; + + if (!rectangles || rectangles.length < 1) { + if (!startHandle.isDragged && !endHandle.isDragged) { + L.DomUtil.empty(handles); + } return; + } for (var i = 0; i < rectangles.length; ++i) { var container = L.DomUtil.create('div', 'leaflet-text-selection-container', selections); @@ -424,6 +446,117 @@ L.Control.LokDialog = L.Control.extend({ L.DomUtil.setStyle(container, 'left', rect.x + 'px'); L.DomUtil.setStyle(container, 'top', rect.y + 'px'); } + + var startRect = rectangles[0]; + var endRect = rectangles[rectangles.length - 1]; + if (startRect.width < 1 || endRect.width < 1) + return; + startRect = {x: startRect.x, y: startRect.y, width: 1, height: startRect.height}; + endRect = {x: endRect.x + endRect.width - 1, y: endRect.y, width: 1, height: endRect.height}; + var startPos = L.point(startRect.x, startRect.y + startRect.height); + startPos = startPos.subtract(L.point(0, 2)); + startHandle.lastPos = startPos; + startHandle.rowHeight = startRect.height; + var endPos = L.point(endRect.x, endRect.y + endRect.height); + endPos = endPos.subtract(L.point(0, 2)); + endHandle.lastPos = endPos; + endHandle.rowHeight = endRect.height; + + if (!startHandle.isDragged) { + if (!handles.children || !handles.children[0]) + handles.appendChild(startHandle); + //console.log('lokdialog: _updateTextSelection: startPos: x: ' + startPos.x + ', y: ' + startPos.y); + startHandle.pos = startPos; + L.DomUtil.setStyle(startHandle, 'left', startPos.x + 'px'); + L.DomUtil.setStyle(startHandle, 'top', startPos.y + 'px'); + } + + if (!endHandle.isDragged) { + if (!handles.children || !handles.children[1]) + handles.appendChild(endHandle); + //console.log('lokdialog: _updateTextSelection: endPos: x: ' + endPos.x + ', y: ' + endPos.y); + endHandle.pos = endPos; + L.DomUtil.setStyle(endHandle, 'left', endPos.x + 'px'); + L.DomUtil.setStyle(endHandle, 'top', endPos.y + 'px'); + } + }, + + _onSelectionHandleDragStart: function (e) { + L.DomEvent.stop(e); + var handles = e.target.parentNode; + var mousePos = L.DomEvent.getMousePosition(e.pointers ? e.srcEvent : e, handles); + e.target.isDragged = true; + e.target.dragStartPos = mousePos; + + if (!handles.lastDraggedHandle) + handles.lastDraggedHandle = 'end'; + var swap = handles.lastDraggedHandle !== e.target.type; + if (swap) { + handles.lastDraggedHandle = e.target.type; + var pos = e.target.pos; + this._map._socket.sendMessage('windowselecttext id=' + e.target.dialogId + + ' swap=true x=' + pos.x + ' y=' + pos.y); + } + }, + + _onSelectionHandleDrag: function (e) { + var handles = this._calcInputBar.textSelection.handles; + var startHandle = handles.children[0]; + var endHandle = handles.children[1]; + if (!endHandle || !startHandle) + return; + + var draggedHandle; + if (startHandle.isDragged) + draggedHandle = startHandle; + else if (endHandle.isDragged) + draggedHandle = endHandle; + if (!draggedHandle) + return; + + var dragEnd = e.type === 'mouseup' || e.type === 'mouseout' || e.type === 'panend'; + if (dragEnd) + draggedHandle.isDragged = false; + var mousePos = L.DomEvent.getMousePosition(e.pointers ? e.srcEvent : e, handles); + var pos = draggedHandle.pos.add(mousePos.subtract(draggedHandle.dragStartPos)); + var maxX = parseInt(handles.style.width) - 5; + var maxY = parseInt(handles.style.height) - 5; + if (pos.x < handles.offsetX) + pos.x = dragEnd ? draggedHandle.lastPos.x : handles.offsetX; + else if (mousePos.x > maxX) + pos.x = dragEnd ? draggedHandle.lastPos.x : maxX; + if (pos.y < handles.offsetY) + pos.y = dragEnd ? draggedHandle.lastPos.y : handles.offsetY; + else if (mousePos.y > maxY) + pos.y = dragEnd ? draggedHandle.lastPos.y : maxY; + + if (Math.abs(pos.y - draggedHandle.lastPos.y) < 6) { + pos.y = draggedHandle.lastPos.y; + } + + if (draggedHandle.type === 'end') { + if (startHandle.pos.y - pos.y > 2) + pos.y = draggedHandle.lastPos.y; + if (startHandle.pos.y - pos.y > -2 && pos.x - startHandle.pos.x < 2) + pos = draggedHandle.lastPos; + } + if (draggedHandle.type === 'start') { + if (pos.y - endHandle.pos.y > 2) + pos.y = draggedHandle.lastPos.y; + if (pos.y - endHandle.pos.y > -endHandle.rowHeight && endHandle.pos.x - pos.x < 2) + pos = draggedHandle.lastPos; + } + + var handlePos = pos; + if (dragEnd) { + handlePos = draggedHandle.lastPos; + draggedHandle.pos = pos; + } + + L.DomUtil.setStyle(draggedHandle, 'left', handlePos.x + 'px'); + L.DomUtil.setStyle(draggedHandle, 'top', handlePos.y + 'px'); + this._map._socket.sendMessage('windowselecttext id=' + draggedHandle.dialogId + + ' swap=false x=' + pos.x + ' y=' + pos.y); }, focus: function(dlgId, acceptInput) { @@ -615,6 +748,24 @@ L.Control.LokDialog = L.Control.extend({ var textSelectionLayer = L.DomUtil.create('div', 'inputbar_selection_layer', container); var selections = L.DomUtil.create('div', 'inputbar_selections', textSelectionLayer); + // create text selection handles + var handles = L.DomUtil.create('div', 'inputbar_selection_handles', textSelectionLayer); + L.DomUtil.setStyle(handles, 'position', 'absolute'); + L.DomUtil.setStyle(handles, 'background', 'transparent'); + this._setCanvasWidthHeight(handles, width, height); + handles.offsetX = 48; + handles.offsetY = 0; + var startHandle = document.createElement('div'); + L.DomUtil.addClass(startHandle, 'leaflet-selection-marker-start'); + startHandle.dialogId = id; + startHandle.type = 'start'; + L.DomEvent.on(startHandle, 'mousedown', this._onSelectionHandleDragStart, this); + var endHandle = document.createElement('div'); + L.DomUtil.addClass(endHandle, 'leaflet-selection-marker-end'); + endHandle.dialogId = id; + endHandle.type = 'end'; + L.DomEvent.on(endHandle, 'mousedown', this._onSelectionHandleDragStart, this); + // Don't show the inputbar until we get the contents. $(container).parent().hide(); @@ -629,14 +780,15 @@ L.Control.LokDialog = L.Control.extend({ width: width, height: height, cursor: null, - textSelection: {rectangles: selections}, + textSelection: {rectangles: selections, handles: handles, startHandle: startHandle, endHandle: endHandle}, child: null, // never used for inputbar title: null // never used for inputbar }; this._createDialogCursor(strId); - this._postLaunch(id, container, canvas); + this._postLaunch(id, container, handles); + this._setupCalcInputBarGestures(id, handles, startHandle, endHandle); this._calcInputBar = this._dialogs[id]; console.log('_launchCalcInputBar: end'); @@ -759,16 +911,17 @@ L.Control.LokDialog = L.Control.extend({ }, _postLaunch: function(id, panelContainer, panelCanvas) { - - this._setupWindowEvents(id, panelCanvas/*, dlgInput*/); - - L.DomEvent.on(panelContainer, 'mouseleave', function() { - // Move the mouse off-screen when we leave the sidebar - // so we don't leave edge-elements highlighted as if - // the mouse is still over them. - this._map.lastActiveTime = Date.now(); - this._postWindowMouseEvent('move', id, -1, -1, 1, 0, 0); - }, this); + if (window.mode.isDesktop()) { + this._setupWindowEvents(id, panelCanvas/*, dlgInput*/); + + L.DomEvent.on(panelContainer, 'mouseleave', function () { + // Move the mouse off-screen when we leave the sidebar + // so we don't leave edge-elements highlighted as if + // the mouse is still over them. + this._map.lastActiveTime = Date.now(); + this._postWindowMouseEvent('move', id, -1, -1, 1, 0, 0); + }, this); + } // Render window. this._sendPaintWindowRect(id); @@ -777,13 +930,32 @@ L.Control.LokDialog = L.Control.extend({ _setupWindowEvents: function(id, canvas/*, dlgInput*/) { L.DomEvent.on(canvas, 'contextmenu', L.DomEvent.preventDefault); L.DomEvent.on(canvas, 'mousemove', function(e) { - this._postWindowMouseEvent('move', id, e.offsetX, e.offsetY, 1, 0, 0); + if (this._isSelectionHandleDragged()) { + this._onSelectionHandleDrag(e); + return; + } + var pos = this._isSelectionHandle(e.target) ? L.DomEvent.getMousePosition(e, canvas) : {x: e.offsetX, y: e.offsetY}; + this._postWindowMouseEvent('move', id, pos.x, pos.y, 1, 0, 0); // Keep map active while user is playing with sidebar/dialog. this._map.lastActiveTime = Date.now(); }, this); + L.DomEvent.on(canvas, 'mouseleave', function(e) { + if (this._isSelectionHandleDragged()) { + this._onSelectionHandleDrag(e); + } + }, this); + L.DomEvent.on(canvas, 'mousedown mouseup', function(e) { L.DomEvent.stop(e); + if (this._isSelectionHandleDragged() && e.type === 'mouseup') { + this._onSelectionHandleDrag(e); + return; + } + + if (canvas.lastDraggedHandle) + canvas.lastDraggedHandle = null; + var buttons = 0; if (this._map['mouse']) { buttons |= e.button === this._map['mouse'].JSButtons.left ? this._map['mouse'].LOButtons.left : 0; @@ -792,9 +964,18 @@ L.Control.LokDialog = L.Control.extend({ } else { buttons = 1; } + + var modifier = 0; + var shift = e.shiftKey ? this._map.keyboard.keyModifier.shift : 0; + var ctrl = e.ctrlKey ? this._map.keyboard.keyModifier.ctrl : 0; + var alt = e.altKey ? this._map.keyboard.keyModifier.alt : 0; + var cmd = e.metaKey ? this._map.keyboard.keyModifier.ctrlMac : 0; + modifier = shift | ctrl | alt | cmd; + // 'mousedown' -> 'buttondown' var lokEventType = e.type.replace('mouse', 'button'); - this._postWindowMouseEvent(lokEventType, id, e.offsetX, e.offsetY, 1, buttons, 0); + var pos = this._isSelectionHandle(e.target) ? L.DomEvent.getMousePosition(e, canvas) : {x: e.offsetX, y: e.offsetY}; + this._postWindowMouseEvent(lokEventType, id, pos.x, pos.y, 1, buttons, modifier); this._map.setWinId(id); //dlgInput.focus(); }, this); @@ -806,6 +987,48 @@ L.Control.LokDialog = L.Control.extend({ }); }, + _setupCalcInputBarGestures: function(id, canvas, startHandle, endHandle) { + if (window.mode.isDesktop()) + return; + + var hammerContent = new Hammer.Manager(canvas, {}); + var that = this; + var singleTap = new Hammer.Tap({event: 'singletap' }); + var doubleTap = new Hammer.Tap({event: 'doubletap', taps: 2 }); + var pan = new Hammer.Pan({event: 'pan' }); + hammerContent.add([doubleTap, singleTap, pan]); + singleTap.requireFailure(doubleTap); + + + hammerContent.on('singletap doubletap', function(ev) { + var handles = that._calcInputBar.textSelection.handles; + handles.lastDraggedHandle = null; + var startHandle = handles.children[0]; + var endHandle = handles.children[1]; + if (startHandle) + startHandle.isDragged = false; + if (endHandle) + endHandle.isDragged = false; + + var point = L.DomEvent.getMousePosition(ev.srcEvent, handles); + + that._postWindowMouseEvent('buttondown', id, point.x, point.y, 1, 1, 0); + that._postWindowMouseEvent('buttonup', id, point.x, point.y, 1, 1, 0); + if (ev.type === 'doubletap') { + that._postWindowMouseEvent('buttondown', id, point.x, point.y, 1, 1, 0); + that._postWindowMouseEvent('buttonup', id, point.x, point.y, 1, 1, 0); + } + }); + + hammerContent.on('panmove panend', L.bind(this._onSelectionHandleDrag, this)); + + var hammerEndHandle = new Hammer.Manager(endHandle, {recognizers:[[Hammer.Pan]]}); + hammerEndHandle.on('panstart', L.bind(this._onSelectionHandleDragStart, this)); + + var hammerStartHandle = new Hammer.Manager(startHandle, {recognizers:[[Hammer.Pan]]}); + hammerStartHandle.on('panstart', L.bind(this._onSelectionHandleDragStart, this)); + }, + _setupGestures: function(dialogContainer, id, canvas) { var targetId = toZoomTargetId(canvas.id); var zoomTarget = $('#' + targetId).parent().get(0); diff --git a/wsd/ClientSession.cpp b/wsd/ClientSession.cpp index 60ed55c6f..1442b4a23 100644 --- a/wsd/ClientSession.cpp +++ b/wsd/ClientSession.cpp @@ -417,6 +417,7 @@ bool ClientSession::_handleInput(const char *buffer, int length) tokens[0] != "savetostorage" && tokens[0] != "selectgraphic" && tokens[0] != "selecttext" && + tokens[0] != "windowselecttext" && tokens[0] != "setclientpart" && tokens[0] != "selectclientpart" && tokens[0] != "moveselectedclientparts" && _______________________________________________ Libreoffice-commits mailing list libreoffice-comm...@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits