[Libreoffice-commits] online.git: kit/ChildSession.cpp kit/ChildSession.hpp loleaflet/css loleaflet/src wsd/ClientSession.cpp

Marco Cecchetti (via logerrit) logerrit at kemper.freedesktop.org
Thu Feb 6 14:58:57 UTC 2020


 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.cecchetti at collabora.com>
AuthorDate: Thu Feb 6 11:28:00 2020 +0100
Commit:     Marco Cecchetti <marco.cecchetti at 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.cecchetti at collabora.com>
    Tested-by: Marco Cecchetti <marco.cecchetti at 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" &&


More information about the Libreoffice-commits mailing list