loleaflet/css/loleaflet.css                     |   18 -
 loleaflet/src/control/Control.ContextToolbar.js |  206 +++++++++++++-----
 loleaflet/src/control/Control.MobileInput.js    |   21 +
 loleaflet/src/geo/LatLngBounds.js               |   28 ++
 loleaflet/src/layer/marker/Marker.js            |    8 
 loleaflet/src/layer/tile/TileLayer.js           |   26 ++
 loleaflet/src/map/handler/Map.TouchGesture.js   |  264 +++++++++++++++++++++---
 7 files changed, 478 insertions(+), 93 deletions(-)

New commits:
commit 1020f07794ad85f96e129f60a4b4ed118417ae06
Author:     Marco Cecchetti <marco.cecche...@collabora.com>
AuthorDate: Wed Oct 2 15:31:39 2019 +0200
Commit:     Tor Lillqvist <t...@collabora.com>
CommitDate: Thu Oct 3 01:03:22 2019 +0200

    loleaflet: improve context toolbars in the iOS App
    
    On double tap on a word, the word is selected and a 'Cut|Copy|Paste'
    toolbar is shown.
    On double tap on an empty line or after a space a 'Paste' toolbar is
    shown and the text cursor is moved to tap point.
    On press on a not selected word the text cursor is moved to the touch
    point and a 'Paste' toolbar is shown.
    On press on a the selected cell a 'Paste' toolbar is shown.
    On press on the text cursor handle a 'Paste' toolbar is shown.
    The 'Paste' entry is shown only if there is a text content that can be
    pasted. An empty toolbar is never shown.
    On single tap the context toolbar is removed.
    On panning, pinching, dragging the text cursor handle or dragging the
    text selection handles a context toolbar (if active) is hidden and
    then shown again at the correct position when the action is over.
    On typing and on lost focus the context toolbar is removed.
    The context toolbar is placed so that to be centered wrt the text
    cursor or the north center of the text selection bounds.
    
    L.Control.ContextToolbar has been generalized so that now can be
    easily populated with any entry and extended to support any command.
    
    Change-Id: Ia112a491d952ed5b32c6dfbb600fc414ac1181e3
    Reviewed-on: https://gerrit.libreoffice.org/80040
    Reviewed-by: Tor Lillqvist <t...@collabora.com>
    Tested-by: Tor Lillqvist <t...@collabora.com>

diff --git a/loleaflet/css/loleaflet.css b/loleaflet/css/loleaflet.css
index 8589c811c..6796417c4 100644
--- a/loleaflet/css/loleaflet.css
+++ b/loleaflet/css/loleaflet.css
@@ -448,15 +448,15 @@ body {
                background: url('images/lc_rejecttrackedchange.svg') no-repeat 
center !important;
 }
 
-.loleaflet-context-copy {
+.loleaflet-context-copy-button {
                background: url('images/lc_copy.svg') no-repeat center 
!important;
 }
 
-.loleaflet-context-cut {
+.loleaflet-context-cut-button {
                background: url('images/lc_cut.svg') no-repeat center 
!important;
 }
 
-.loleaflet-context-paste {
+.loleaflet-context-paste-button {
                background: url('images/lc_paste.svg') no-repeat center 
!important;
 }
 
@@ -473,7 +473,7 @@ body {
 
 .loleaflet-ios-context-button {
        background-color: black;
-       height: 40px;
+       height: 36px;
 }
 
 .loleaflet-ios-context-left {
@@ -482,15 +482,15 @@ body {
     border-bottom-left-radius: 10px;
 }
 
-.loleaflet-ios-context-first-and-middle-entry {
-    padding-left: 8px;
-    padding-right: 8px;
+.loleaflet-ios-context-first-entry, .loleaflet-ios-context-middle-entry {
+    padding-left: 10px;
+    padding-right: 10px;
     border-right: 1px solid white;
 }
 
 .loleaflet-ios-context-last-entry {
-    padding-left: 8px;
-    padding-right: 8px;
+    padding-left: 10px;
+    padding-right: 10px;
 }
 
 .loleaflet-ios-context-right {
diff --git a/loleaflet/src/control/Control.ContextToolbar.js 
b/loleaflet/src/control/Control.ContextToolbar.js
index 8d97e29c0..1236906d3 100644
--- a/loleaflet/src/control/Control.ContextToolbar.js
+++ b/loleaflet/src/control/Control.ContextToolbar.js
@@ -7,100 +7,192 @@
 L.Control.ContextToolbar = L.Control.extend({
        options: {
                position: 'topleft',
-               item: ''
+       },
+
+       statics: {
+               TOOLBARS: {
+                       'TEXT_CURSOR_TOOLBAR': ['Paste'],
+                       'TEXT_SELECTION_TOOLBAR': ['Cut', 'Copy', 'Paste']
+               }
        },
 
        initialize: function (options) {
                L.setOptions(this, options);
+
        },
 
        onAdd: function () {
+               // console.log('==> ContextToolbar.onAdd');
                if (!this._container) {
                        this._initLayout();
                }
-               if (this.options.item === 'paste') {
-                       this._paste.style.display = '';
-                       this._cut.style.display = 'none';
-                       this._copy.style.display = 'none';
-               }
-
-               this._container.style.visibility = 'hidden';
+               this.hide();
                return this._container;
        },
 
        onRemove: function () {
-               this._paste.style.display = '';
-               this._cut.style.display = '';
-               this._copy.style.display = '';
-               this.options.item = '';
+               if (this._bar)
+                       // remove all previous entries
+                       L.DomUtil.empty(this._bar);
+               this.hide();
+               this._latlng = null;
+               // console.log('==> ContextToolbar.onRemove');
+       },
+
+       isInitialized: function () {
+               return  !!this._bar.firstChild;
+       },
+
+       isVisible: function () {
+               return  this._container && this._container.style.visibility === 
'';
+       },
+
+       hide: function () {
+               this._container.style.visibility = 'hidden';
+       },
+
+       show: function () {
+               this._container.style.visibility = '';
+       },
+
+       _createEntry: function (platform, command, noText, pos) {
+               if (!command || command.length === 0)
+                       return;
+
+               var tagTd = 'td';
+               var entryTag = command.toLowerCase();
+               var platformFragment = (platform && platform.length) ? '-' + 
platform + '-' : '';
+               pos = (pos && pos.length) ? pos : 'middle';
+               var flagEntryClass = 'loleaflet-context-' + entryTag; // this 
class is used only for retrieving the command type
+               var genericButtonClass = 'loleaflet' + platformFragment + 
'context-button';
+               var specificButtonClass = 'loleaflet' + platformFragment + 
'context-' + entryTag + '-button';
+               var posEntryClass = 'loleaflet' + platformFragment + 'context-' 
+ pos + '-entry';
+               var classList = flagEntryClass + ' ' + genericButtonClass + ' ' 
+ specificButtonClass + ' ' + posEntryClass;
+               var entry = L.DomUtil.create(tagTd, classList, this._bar);
+               if (!noText) {
+                       // get locale name for the command
+                       entry.innerHTML = _UNO('.uno:' + command);
+               }
+               var stopEvents = 'touchstart touchmove touchend mousedown 
mousemove mouseout mouseover mouseup mousewheel click scroll';
+
+               L.DomEvent.on(entry, stopEvents,  L.DomEvent.stopPropagation)
+                       .on(entry, 'mousedown', this.onMouseDown, this)
+                       .on(entry, 'mouseup', this.onMouseUp, this);
+
+               return entry;
+       },
+
+       _entryIs: function(name, entry) {
+               var flagEntryClass = 'loleaflet-context-' + name;
+               return L.DomUtil.hasClass(entry, flagEntryClass);
        },
 
        _initLayout: function () {
+               // create an empty toolbar, it will be populated with command 
entries later
                if (window.ThisIsTheiOSApp || window.ThisIsTheAndroidApp)
                        this._container = L.DomUtil.create('div', 
'loleaflet-ios-context-toolbar');
                else
                        this._container = L.DomUtil.create('div', 
'loleaflet-context-toolbar');
 
-               var tagTd = 'td',
-                   onUp = 'mouseup',
-                   onDown = 'mousedown',
-                   stopEvents = 'touchstart touchmove touchend mousedown 
mousemove mouseout mouseover mouseup mousewheel click scroll',
-                   container;
-
+               var container;
                if (window.ThisIsTheiOSApp || window.ThisIsTheAndroidApp)
                        container = L.DomUtil.create('table', 
'loleaflet-ios-context-table', this._container);
                else
                        container = L.DomUtil.create('table', 
'loleaflet-context-table', this._container);
 
-               var tbody = L.DomUtil.create('tbody', '', container),
-                   tr = L.DomUtil.create('tr', '', tbody);
+               var tbody = L.DomUtil.create('tbody', '', container);
+               this._bar = L.DomUtil.create('tr', '', tbody);
+       },
+
+       onAdded: function () {
+               this.show();
+       },
+
+       setEntries: function(commands) {
+               if (!this._bar)
+                       return;
+
+               // commands is the name of a predefined toolbar ?
+               if (typeof commands === 'string') {
+                       commands = L.Control.ContextToolbar.TOOLBARS[commands];
+                       if (!commands)
+                               return;
+               }
+
+               // remove all previous entries
+               L.DomUtil.empty(this._bar);
+
+               // check commands validity
+               var validCommands = [];
+               if (this._map && this._map._docLayer._internalCacheEmpty) {
+                       for (var k = 0; k < commands.length; ++k) {
+                               var cmd = commands[k];
+                               if (cmd === 'Paste')
+                                       continue;
+                               validCommands.push(cmd);
+                       }
+               } else {
+                       validCommands = commands;
+               }
+               if (!validCommands.length)
+                       return;
 
+               var tagTd = 'td';
+               var platform = '';
+               var noText = true;
                if (window.ThisIsTheiOSApp || window.ThisIsTheAndroidApp) {
-                       this._leftroundedend = L.DomUtil.create(tagTd, 
'loleaflet-ios-context-button loleaflet-ios-context-left', tr);
-                       this._cut = L.DomUtil.create(tagTd, 
'loleaflet-ios-context-button loleaflet-ios-context-first-and-middle-entry 
loleaflet-ios-context-cut', tr);
-                       this._cut.innerHTML = _UNO('.uno:Cut');
-                       this._copy = L.DomUtil.create(tagTd, 
'loleaflet-ios-context-button loleaflet-ios-context-first-and-middle-entry 
loleaflet-ios-context-copy', tr);
-                       this._copy.innerHTML = _UNO('.uno:Copy');
-                       this._paste = L.DomUtil.create(tagTd, 
'loleaflet-ios-context-button loleaflet-ios-context-last-entry 
loleaflet-ios-context-paste', tr);
-                       this._paste.innerHTML = _UNO('.uno:Paste');
-                       this._rightroundedend = L.DomUtil.create(tagTd, 
'loleaflet-ios-context-button loleaflet-ios-context-right', tr);
+                       platform = 'ios';
+                       noText = false;
+                       this._leftroundedend = L.DomUtil.create(tagTd, 
'loleaflet-ios-context-button loleaflet-ios-context-left', this._bar);
+               }
+
+               for (var i = 0; i < validCommands.length; ++i) {
+                       var command = validCommands[i];
+                       var pos;
+                       if (i === validCommands.length - 1)
+                               pos = 'last';
+                       else if (i === 0)
+                               pos = 'first';
+                       else
+                               pos = 'middle';
+                       this._createEntry(platform, command, noText, pos);
                }
-               else {
-                       this._cut = L.DomUtil.create(tagTd, 
'loleaflet-context-button loleaflet-context-cut', tr);
-                       this._copy = L.DomUtil.create(tagTd, 
'loleaflet-context-button loleaflet-context-copy', tr);
-                       this._paste = L.DomUtil.create(tagTd, 
'loleaflet-context-button loleaflet-context-paste', tr);
+
+               if (window.ThisIsTheiOSApp || window.ThisIsTheAndroidApp) {
+                       this._rightroundedend = L.DomUtil.create(tagTd, 
'loleaflet-ios-context-button loleaflet-ios-context-right', this._bar);
                }
-               L.DomEvent.on(this._cut, stopEvents,  
L.DomEvent.stopPropagation)
-                       .on(this._cut, onDown, this.onMouseDown, this)
-                       .on(this._cut, onUp, this.onMouseUp, this);
-               L.DomEvent.on(this._copy, stopEvents,  
L.DomEvent.stopPropagation)
-                       .on(this._copy, onDown, this.onMouseDown, this)
-                       .on(this._copy, onUp, this.onMouseUp, this);
-               L.DomEvent.on(this._paste, stopEvents,  
L.DomEvent.stopPropagation)
-                       .on(this._paste, onDown, this.onMouseDown, this)
-                       .on(this._paste, onUp, this.onMouseUp, this);
        },
 
-       onAdded: function () {
-               if (this._pos) {
+       setPosition: function (latlng) {
+               // hint: the toolbar should be populated earlier than set up 
the position
+               if (this._map && this._bar && this.isInitialized() && latlng) {
+                       this._latlng = latlng;
+                       var pos = this._map.project(latlng);
                        var maxBounds = this._map.getPixelBounds();
                        var size = 
L.point(this._container.clientWidth,this._container.clientHeight);
-                       this._pos._add(L.point(-size.x / 2, -size.y));
-                       var bounds = new L.Bounds(this._pos, 
this._pos.add(size));
+                       pos._add(L.point(-size.x / 2, -5 * size.y / 4));
+                       var bounds = new L.Bounds(pos, pos.add(size));
                        if (!maxBounds.contains(bounds)) {
                                var offset = L.point(0, 0);
                                if (bounds.max.x > maxBounds.max.x) {
-                                       offset.x = size.x;
+                                       offset.x = bounds.max.x - 
maxBounds.max.x;
                                }
-
                                if (bounds.max.y > maxBounds.max.y) {
-                                       offset.y = size.y;
+                                       offset.y = bounds.max.y - 
maxBounds.max.y;
+                               }
+                               pos._subtract(offset);
+                               if (bounds.min.x < maxBounds.min.x) {
+                                       offset.x = maxBounds.min.x - 
bounds.min.x;
+                               }
+                               if (bounds.min.y < maxBounds.min.y) {
+                                       offset.y = maxBounds.min.y - 
bounds.min.y;
                                }
-                               this._pos._subtract(offset);
+                               pos._add(offset);
                        }
-                       L.DomUtil.setPosition(this._container, this._pos);
+                       var containerPoint = 
this._map.latLngToContainerPoint(this._map.unproject(pos));
+                       L.DomUtil.setPosition(this._container, containerPoint);
+                       this.show();
                }
-               this._container.style.visibility = '';
        },
 
        onMouseDown: function (e) {
@@ -111,17 +203,15 @@ L.Control.ContextToolbar = L.Control.extend({
 
        onMouseUp: function (e) {
                var target = e.target || e.srcElement;
-
-               if (L.DomUtil.hasClass(target, 'loleaflet-context-cut') ||
-                  L.DomUtil.hasClass(target, 'loleaflet-ios-context-cut')) {
+               if (this._entryIs('cut', target)) {
                        this._map._socket.sendMessage('uno .uno:Cut');
+                       this._map._docLayer._internalCacheEmpty = false;
                }
-               else if (L.DomUtil.hasClass(target, 'loleaflet-context-copy') ||
-                       L.DomUtil.hasClass(target, 
'loleaflet-ios-context-copy')) {
+               else if (this._entryIs('copy', target)) {
                        this._map._socket.sendMessage('uno .uno:Copy');
+                       this._map._docLayer._internalCacheEmpty = false;
                }
-               else if (L.DomUtil.hasClass(target, 'loleaflet-context-paste') 
||
-                       L.DomUtil.hasClass(target, 
'loleaflet-ios-context-paste')) {
+               else if (this._entryIs('paste', target)) {
                        this._map._socket.sendMessage('uno .uno:Paste');
                }
 
diff --git a/loleaflet/src/control/Control.MobileInput.js 
b/loleaflet/src/control/Control.MobileInput.js
index 6eddbe5e8..07ab7b520 100644
--- a/loleaflet/src/control/Control.MobileInput.js
+++ b/loleaflet/src/control/Control.MobileInput.js
@@ -19,6 +19,9 @@ L.Control.MobileInput = L.Control.extend({
                        draggable: true
                });
 
+               if (window.ThisIsTheiOSApp) {
+                       this._cursorHandler.on('dragstart', this.onDragStart, 
this);
+               }
                this._cursorHandler.on('dragend', this.onDragEnd, this);
                this._currentKeysDown = {};
                this._ignoreKeypress = false;
@@ -40,10 +43,17 @@ L.Control.MobileInput = L.Control.extend({
                this._map.fire('input.press', this._cursorHandler.getLatLng());
        },
 
+       onDragStart: function () {
+               this._map.fire('input.dragstart', 
this._cursorHandler.getLatLng());
+       },
+
        onDragEnd: function () {
                var mousePos = 
this._map._docLayer._latLngToTwips(this._cursorHandler.getLatLng());
                this._map._docLayer._postMouseEvent('buttondown', mousePos.x, 
mousePos.y, 1, 1, 0);
                this._map._docLayer._postMouseEvent('buttonup', mousePos.x, 
mousePos.y, 1, 1, 0);
+               if (window.ThisIsTheiOSApp) {
+                       this._map.fire('input.dragend', 
this._cursorHandler.getLatLng());
+               }
        },
 
        onGotFocus: function () {
@@ -74,6 +84,10 @@ L.Control.MobileInput = L.Control.extend({
                        }
                        this._map.removeLayer(this._cursorHandler);
                }
+               if (window.ThisIsTheiOSApp) {
+                       // when the focus is lost remove the context toolbar
+                       this._map.fire('input.blur');
+               }
        },
 
        focus: function (focus) {
@@ -185,6 +199,13 @@ L.Control.MobileInput = L.Control.extend({
                                docLayer._postKeyboardEvent('input', charCode, 
unoKeyCode);
                                this._lastInput = unoKeyCode;
                        }
+                       if (window.ThisIsTheiOSApp) {
+                               // when the user start typing remove the 
context toolbar
+                               if (this._map.touchGesture._toolbar && 
this._map.touchGesture._toolbarAdded) {
+                                       
this._map.touchGesture._toolbar.remove();
+                                       this._map.touchGesture._toolbarAdded = 
null;
+                               }
+                       }
                }
                else if (this._isMobileSafariOriOSApp &&
                         e.type === 'keypress') {
diff --git a/loleaflet/src/geo/LatLngBounds.js 
b/loleaflet/src/geo/LatLngBounds.js
index e6f7396a2..8ef7ceb4f 100644
--- a/loleaflet/src/geo/LatLngBounds.js
+++ b/loleaflet/src/geo/LatLngBounds.js
@@ -60,7 +60,7 @@ L.LatLngBounds.prototype = {
                        new L.LatLng(ne.lat + heightBuffer, ne.lng + 
widthBuffer));
        },
 
-       // extend the bounds by a percentage
+       // extend the bounds vertically by a percentage
        padVertically: function (bufferRatio) { // (Number) -> LatLngBounds
                var sw = this._southWest,
                ne = this._northEast,
@@ -71,6 +71,17 @@ L.LatLngBounds.prototype = {
                        new L.LatLng(ne.lat + heightBuffer, ne.lng));
        },
 
+       // extend the bounds horizontally by a percentage
+       padHorizontally: function (bufferRatio) { // (Number) -> LatLngBounds
+               var sw = this._southWest,
+               ne = this._northEast,
+               widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio;
+
+               return new L.LatLngBounds(
+                       new L.LatLng(sw.lat, sw.lng - widthBuffer),
+                       new L.LatLng(ne.lat, ne.lng + widthBuffer));
+       },
+
        getCenter: function () { // -> LatLng
                return new L.LatLng(
                        (this._southWest.lat + this._northEast.lat) / 2,
@@ -145,6 +156,21 @@ L.LatLngBounds.prototype = {
                return latIntersects && lngIntersects;
        },
 
+       intersection: function (bounds) { // (LatLngBounds)
+               bounds = L.latLngBounds(bounds);
+
+               var sw = this._southWest,
+               ne = this._northEast,
+               sw2 = bounds.getSouthWest(),
+               ne2 = bounds.getNorthEast(),
+               nelat = Math.min(ne.lat, ne2.lat),
+               nelng = Math.min(ne.lng, ne2.lng),
+               swlat = Math.max(sw.lat, sw2.lat),
+               swlng = Math.max(sw.lng, sw2.lng);
+
+               return L.latLngBounds(L.latLng(swlat, swlng), L.latLng(nelat, 
nelng));
+       },
+
        toBBoxString: function () {
                return [this.getWest(), this.getSouth(), this.getEast(), 
this.getNorth()].join(',');
        },
diff --git a/loleaflet/src/layer/marker/Marker.js 
b/loleaflet/src/layer/marker/Marker.js
index 3db3dbfd9..b0229c102 100644
--- a/loleaflet/src/layer/marker/Marker.js
+++ b/loleaflet/src/layer/marker/Marker.js
@@ -113,6 +113,14 @@ L.Marker = L.Layer.extend({
                return this;
        },
 
+       getBounds: function () {
+               if (!this._map)
+                       return null;
+               var topLeftPx = this._map.project(this._latlng);
+               var bottomRightPx = L.point(topLeftPx.x + 
this._icon.clientWidth, topLeftPx.y + this._icon.clientHeight);
+               return new L.LatLngBounds(this._latlng, 
this._map.unproject(bottomRightPx));
+       },
+
        _initIcon: function () {
                var options = this.options,
                    classToAdd = 'leaflet-zoom-' + (this._zoomAnimated ? 
'animated' : 'hide');
diff --git a/loleaflet/src/layer/tile/TileLayer.js 
b/loleaflet/src/layer/tile/TileLayer.js
index 2d324784e..8b60f8ef4 100644
--- a/loleaflet/src/layer/tile/TileLayer.js
+++ b/loleaflet/src/layer/tile/TileLayer.js
@@ -211,6 +211,7 @@ L.TileLayer = L.GridLayer.extend({
                this._levels = {};
                this._tiles = {};
                this._tileCache = {};
+               this._internalCacheEmpty = true;
                this._map._socket.sendMessage('commandvalues 
command=.uno:LanguageStatus');
                this._map._socket.sendMessage('commandvalues 
command=.uno:ViewAnnotations');
                var that = this;
@@ -311,6 +312,9 @@ L.TileLayer = L.GridLayer.extend({
 
                for (var key in this._selectionHandles) {
                        this._selectionHandles[key].on('drag dragend', 
this._onSelectionHandleDrag, this);
+                       if (window.ThisIsTheiOSApp) {
+                               this._selectionHandles[key].on('dragstart', 
this._onSelectionHandleDrag, this);
+                       }
                }
 
                this._cellResizeMarkerStart.on('dragstart drag dragend', 
this._onCellResizeMarkerDrag, this);
@@ -1287,6 +1291,9 @@ L.TileLayer = L.GridLayer.extend({
                                weight: 2,
                                opacity: 0.25});
                        this._selections.addLayer(selection);
+                       if (window.ThisIsTheiOSApp) {
+                               this._map.fire('textselected', null);
+                       }
                        if (this._selectionContentRequest) {
                                clearTimeout(this._selectionContentRequest);
                        }
@@ -2198,6 +2205,19 @@ L.TileLayer = L.GridLayer.extend({
 
        // Update dragged text selection.
        _onSelectionHandleDrag: function (e) {
+               if (window.ThisIsTheiOSApp) {
+                       if (e.type === 'dragstart') {
+                               if (this._map.touchGesture._toolbar && 
this._map.touchGesture._toolbar.isVisible()) {
+                                       
this._map.touchGesture._toolbar.remove();
+                                       this._map.touchGesture._toolbarAdded = 
null;
+                               }
+                               if (this._map.touchGesture._toolbar) {
+                                       
this._map.touchGesture._newTextSelection = false;
+                               }
+                               return;
+                       }
+               }
+
                if (e.type === 'drag') {
                        e.target.isDragged = true;
 
@@ -2251,6 +2271,12 @@ L.TileLayer = L.GridLayer.extend({
                else if (this._selectionHandles.end === e.target) {
                        this._postSelectTextEvent('end', aPos.x, aPos.y);
                }
+
+               if (window.ThisIsTheiOSApp) {
+                       if (e.type === 'dragend') {
+                               this._map.fire('textselection.dragend', 
e.target.getLatLng());
+                       }
+               }
        },
 
        // Update dragged text selection.
diff --git a/loleaflet/src/map/handler/Map.TouchGesture.js 
b/loleaflet/src/map/handler/Map.TouchGesture.js
index d222d5a89..5d143f3f4 100644
--- a/loleaflet/src/map/handler/Map.TouchGesture.js
+++ b/loleaflet/src/map/handler/Map.TouchGesture.js
@@ -14,7 +14,9 @@ L.Map.TouchGesture = L.Handler.extend({
                CURSOR: 2,
                GRAPHIC: 4,
                MARKER: 8,
-               TABLE: 16
+               TABLE: 16,
+               TEXT_CURSOR_HANDLE: 32,
+               TEXT_SELECTION_HANDLE: 64
        },
 
        initialize: function (map) {
@@ -89,8 +91,14 @@ L.Map.TouchGesture = L.Handler.extend({
                this._hammer.on('pinchmove', L.bind(this._onPinch, this));
                this._hammer.on('pinchend', L.bind(this._onPinchEnd, this));
                this._hammer.on('tripletap', L.bind(this._onTripleTap, this));
-               if (window.ThisIsTheiOSApp)
+               if (window.ThisIsTheiOSApp) {
                        this._map.on('input.press', this._onInputPressiOSOnly, 
this);
+                       this._map.on('input.dragstart', 
this._onInputDragStartiOSOnly, this);
+                       this._map.on('input.dragend', 
this._onInputDragEndiOSOnly, this);
+                       this._map.on('input.blur', 
this._onInputLostFocusiOSOnly, this);
+                       this._map.on('textselected', 
this._onTextSelectediOSOnly, this);
+                       this._map.on('textselection.dragend', 
this._onTextSelectionHandleDragEndiOSOnly, this);
+               }
                this._map.on('updatepermission', this._onPermission, this);
                this._onPermission({perm: this._map._permission});
        },
@@ -131,6 +139,16 @@ L.Map.TouchGesture = L.Handler.extend({
                                this._marker = 
this._map._docLayer._graphicMarker.transform.getMarker(layerPoint);
                        }
 
+                       var cursorHandleBounds;
+                       if (this._map._clipboardContainer._cursorHandler)
+                               cursorHandleBounds = 
this._map._clipboardContainer._cursorHandler.getBounds();
+                       var startTextSelectionHandleBounds;
+                       if (this._map._docLayer._selectionHandles['start'])
+                               startTextSelectionHandleBounds = 
this._map._docLayer._selectionHandles['start'].getBounds();
+                       var endTextSelectionHandleBounds;
+                       if (this._map._docLayer._selectionHandles['end'])
+                               endTextSelectionHandleBounds = 
this._map._docLayer._selectionHandles['end'].getBounds();
+
                        if (this._marker) {
                                this._state = L.Map.TouchGesture.MARKER;
                        } else if (this._map._docLayer._graphicMarker && 
this._map._docLayer._graphicMarker.getBounds().contains(latlng)) {
@@ -140,9 +158,15 @@ L.Map.TouchGesture = L.Handler.extend({
                                        this._state = 
L.Map.TouchGesture.GRAPHIC;
                        } else if (this._map._docLayer._cellCursor && 
this._map._docLayer._cellCursor.contains(latlng)) {
                                this._state = L.Map.TouchGesture.CURSOR;
+                       } else if (cursorHandleBounds && 
cursorHandleBounds.padHorizontally(0.2).contains(latlng)) {
+                               this._state = 
L.Map.TouchGesture.TEXT_CURSOR_HANDLE;
+                       } else if ((startTextSelectionHandleBounds && 
startTextSelectionHandleBounds.padHorizontally(0.2).contains(latlng))
+                               || (endTextSelectionHandleBounds && 
endTextSelectionHandleBounds.padHorizontally(0.2).contains(latlng))) {
+                               this._state = 
L.Map.TouchGesture.TEXT_SELECTION_HANDLE;
                        } else {
                                this._state = L.Map.TouchGesture.MAP;
                        }
+                       // console.log('==> _onHammer: _state: ' + this._state);
                }
 
                if (e.isLast && this._state !== L.Map.TouchGesture.MAP) {
@@ -162,6 +186,30 @@ L.Map.TouchGesture = L.Handler.extend({
                }
        },
 
+       _addContextToolbar: function (commands, latlng, timeStamp) {
+               this._toolbar.remove();
+               this._toolbar.addTo(this._map);
+               this._toolbar.setEntries(commands);
+               this._toolbar.setPosition(latlng);
+               this._toolbarAdded = timeStamp;
+       },
+
+       _getTextSelectionNorthCenter: function () {
+               var result;
+               var selections = this._map._docLayer._selections;
+               if (selections) {
+                       var layers = selections.getLayers();
+                       if (layers && layers.length === 1) {
+                               var mapBounds = this._map.getBounds();
+                               var bounds = layers[0].getBounds();
+                               bounds = mapBounds.intersection(bounds);
+                               result = bounds.getCenter();
+                               result.lat = bounds.getNorth();
+                       }
+               }
+               return result;
+       },
+
        _onPress: function (e) {
                var point = e.pointers[0],
                    containerPoint = 
this._map.mouseEventToContainerPoint(point),
@@ -169,28 +217,81 @@ L.Map.TouchGesture = L.Handler.extend({
                    latlng = this._map.layerPointToLatLng(layerPoint),
                    mousePos = this._map._docLayer._latLngToTwips(latlng);
 
+               var docLayer = this._map._docLayer;
+
                if (window.ThisIsTheiOSApp) {
-                       // console.log('==> ' + e.timeStamp);
-                       if (!this._toolbar._map && 
this._map._docLayer.containsSelection(latlng)) {
-                               this._toolbar._pos = containerPoint;
-                               // console.log('==> Adding context toolbar ' + 
e.timeStamp);
-                               this._toolbar.addTo(this._map);
-                               this._toolbarAdded = e.timeStamp;
-                       } else if (this._toolbarAdded && e.timeStamp - 
this._toolbarAdded >= 1000) {
-                               // console.log('==> Removing context toolbar ' 
+ e.timeStamp);
-                               this._toolbar.remove();
-                               
this._map._contextMenu._onMouseDown({originalEvent: e.srcEvent});
-                               // send right click to trigger context menus
-                               
this._map._docLayer._postMouseEvent('buttondown', mousePos.x, mousePos.y, 1, 4, 
0);
-                               this._map._docLayer._postMouseEvent('buttonup', 
mousePos.x, mousePos.y, 1, 4, 0);
+                       // if user has pressed on cursor or text selection 
handles don't perform any action
+                       // so far we skip also press actions on graphics and 
cell cursor
+                       // we check focus in order to not get unexpected 
behavior with open dialogs
+                       var hasPressedOnCellCursor = this._state === 
L.Map.TouchGesture.CURSOR;
+                       if ((this._state === L.Map.TouchGesture.MAP && 
this._map.hasFocus()) || hasPressedOnCellCursor) {
+                               // console.log('==> onPress: ' + e.timeStamp);
+                               // no text selected
+                               if (!docLayer.containsSelection(latlng) && 
!hasPressedOnCellCursor) {
+                                       // I see several press events generated 
for the same press action, try to skip the redundant ones.
+                                       if (!this._prevPressContainerPoint || 
!(this._contextToolbarTimeout && 
containerPoint.equals(this._prevPressContainerPoint))) {
+                                               // we can skip when the toolbar 
is already active and user has pressed at the text cursor position
+                                               if (this._toolbar.isVisible() 
&& docLayer._visibleCursor.padHorizontally(0.2).contains(latlng))
+                                                       return;
+                                               // place the text cursor where 
user pressed
+                                               
docLayer._postMouseEvent('buttondown', mousePos.x, mousePos.y, 1, 1, 0);
+                                               
docLayer._postMouseEvent('buttonup', mousePos.x, mousePos.y, 1, 1, 0);
+
+                                               var that = this;
+                                               var timeout = 300;
+                                               var prevCursorPos = 
this._map.latLngToContainerPoint(docLayer._visibleCursor.getNorthEast());
+
+                                               var setContextToolbar = 
function (n) {
+                                                       var toolbarUpdated = 
false;
+                                                       // is the text cursor 
visible ?
+                                                       if 
(docLayer._isCursorVisible && 
!docLayer._isEmptyRectangle(docLayer._visibleCursor) && docLayer._cursorMarker) 
{
+                                                               var posLatLng = 
docLayer._visibleCursor.getNorthEast();
+                                                               var pos = 
that._map.latLngToContainerPoint(posLatLng);
+                                                               // do the 
client get the new cursor position ? if client doesn't, let's try to 
re-schedule this routine
+                                                               if 
(!pos.equals(prevCursorPos) || !that._toolbar.isVisible()) {
+                                                                       var 
commands = 'TEXT_CURSOR_TOOLBAR';
+                                                                       // 
console.log('==> onPress: Adding context toolbar ' + Date.now() + ', call: ' + 
n);
+                                                                       
that._addContextToolbar(commands, posLatLng, Date.now());
+                                                                       
toolbarUpdated = true;
+                                                               }
+                                                       }
+                                                       // if the toolbar has 
not been update re-schedule this routine
+                                                       if (!toolbarUpdated && 
n < 5) {
+                                                               n += 1;
+                                                               
that._contextToolbarTimeout = setTimeout(setContextToolbar, n * timeout, n);
+                                                       } else {
+                                                               
that._contextToolbarTimeout = null;
+                                                       }
+                                               };
+
+                                               if (this._contextToolbarTimeout)
+                                                       
clearTimeout(this._contextToolbarTimeout);
+                                               this._contextToolbarTimeout = 
setTimeout(setContextToolbar, timeout, 1);
+                                       }
+                               // if some text is selected and the toolbar is 
active, the user wants to trigger the context menu
+                               } else if (hasPressedOnCellCursor && 
!this._toolbar.isVisible()) {
+                                       if (!this._prevPressContainerPoint || 
!(this._toolbar.isVisible() && 
containerPoint.equals(this._prevPressContainerPoint))) {
+                                               var commands = 
'TEXT_CURSOR_TOOLBAR';
+                                               // console.log('==> onPress: 
Adding context toolbar ' + e.timeStamp);
+                                               
this._addContextToolbar(commands, latlng, e.timeStamp);
+                                       }
+                               } else if (this._toolbar.isVisible() && 
e.timeStamp - this._toolbarAdded >= 1000) {
+                                       // console.log('==> onPress: Removing 
context toolbar ' + e.timeStamp);
+                                       this._toolbar.remove();
+                                       this._toolbarAdded = null;
+                                       
this._map._contextMenu._onMouseDown({originalEvent: e.srcEvent});
+                                       // send right click to trigger context 
menus
+                                       docLayer._postMouseEvent('buttondown', 
mousePos.x, mousePos.y, 1, 4, 0);
+                                       docLayer._postMouseEvent('buttonup', 
mousePos.x, mousePos.y, 1, 4, 0);
+                               }
                        }
+                       this._prevPressContainerPoint = containerPoint;
                } else {
                        this._map._contextMenu._onMouseDown({originalEvent: 
e.srcEvent});
                        // send right click to trigger context menus
-                       this._map._docLayer._postMouseEvent('buttondown', 
mousePos.x, mousePos.y, 1, 4, 0);
-                       this._map._docLayer._postMouseEvent('buttonup', 
mousePos.x, mousePos.y, 1, 4, 0);
+                       docLayer._postMouseEvent('buttondown', mousePos.x, 
mousePos.y, 1, 4, 0);
+                       docLayer._postMouseEvent('buttonup', mousePos.x, 
mousePos.y, 1, 4, 0);
                }
-
                e.preventDefault();
        },
 
@@ -201,8 +302,10 @@ L.Map.TouchGesture = L.Handler.extend({
                    latlng = this._map.layerPointToLatLng(layerPoint),
                    mousePos = this._map._docLayer._latLngToTwips(latlng);
 
-               if (window.ThisIsTheiOSApp)
+               if (window.ThisIsTheiOSApp) {
                        this._toolbar.remove();
+                       this._toolbarAdded = null;
+               }
                this._map._contextMenu._onMouseDown({originalEvent: 
e.srcEvent});
                this._map._docLayer._postMouseEvent('buttondown', mousePos.x, 
mousePos.y, 1, 1, 0);
                this._map._docLayer._postMouseEvent('buttonup', mousePos.x, 
mousePos.y, 1, 1, 0);
@@ -217,8 +320,50 @@ L.Map.TouchGesture = L.Handler.extend({
                    latlng = this._map.layerPointToLatLng(layerPoint),
                    mousePos = this._map._docLayer._latLngToTwips(latlng);
 
-               this._map._docLayer._postMouseEvent('buttondown', mousePos.x, 
mousePos.y, 2, 1, 0);
-               this._map._docLayer._postMouseEvent('buttonup', mousePos.x, 
mousePos.y, 2, 1, 0);
+               var docLayer = this._map._docLayer;
+
+               if (window.ThisIsTheiOSApp) {
+                       this._newTextSelection = false;
+               }
+               docLayer._postMouseEvent('buttondown', mousePos.x, mousePos.y, 
2, 1, 0);
+               docLayer._postMouseEvent('buttonup', mousePos.x, mousePos.y, 2, 
1, 0);
+
+               if (window.ThisIsTheiOSApp) {
+                       var that = this;
+                       var timeout = 300;
+                       var prevCursorPos = 
this._map.latLngToContainerPoint(docLayer._visibleCursor.getSouthWest());
+
+                       var setContextToolbar = function (n) {
+                               var canBeUpdated = false;
+                               var commands, pos, posLatLng;
+                               if (that._newTextSelection) {
+                                       commands = 'TEXT_SELECTION_TOOLBAR';
+                                       posLatLng = 
that._getTextSelectionNorthCenter() || latlng;
+                                       canBeUpdated = true;
+                               } else if (docLayer._isCursorVisible && 
!docLayer._isEmptyRectangle(docLayer._visibleCursor) && docLayer._cursorMarker) 
{
+                                       posLatLng = 
docLayer._visibleCursor.getNorthEast();
+                                       pos = 
that._map.latLngToContainerPoint(posLatLng);
+                                       if (!pos.equals(prevCursorPos) || 
!that._toolbar.isVisible()) {
+                                               commands = 
'TEXT_CURSOR_TOOLBAR';
+                                               canBeUpdated = true;
+                                       }
+                               }
+                               if (!canBeUpdated && n < 5) {
+                                       n += 1;
+                                       that._contextToolbarTimeout = 
setTimeout(setContextToolbar, n * timeout, n);
+                               } else {
+                                       if (canBeUpdated) {
+                                               // console.log('==> 
onDoubleTap: Adding context toolbar ' + Date.now() + ', call: ' + n);
+                                               
that._addContextToolbar(commands, posLatLng, Date.now());
+                                       }
+                                       that._contextToolbarTimeout = null;
+                               }
+                       };
+
+                       if (this._contextToolbarTimeout)
+                               clearTimeout(this._contextToolbarTimeout);
+                       this._contextToolbarTimeout = 
setTimeout(setContextToolbar, timeout, 1);
+               }
        },
 
        _onTripleTap: function (e) {
@@ -239,6 +384,10 @@ L.Map.TouchGesture = L.Handler.extend({
                    latlng = this._map.layerPointToLatLng(layerPoint),
                    mousePos = this._map._docLayer._latLngToTwips(latlng);
 
+               if (window.ThisIsTheiOSApp) {
+                       this._toolbar.hide();
+               }
+
                var originalCellCursor = this._map._docLayer._cellCursor;
                var increaseRatio = 0.40;
                var increasedCellCursor = null;
@@ -310,6 +459,15 @@ L.Map.TouchGesture = L.Handler.extend({
                    latlng = this._map.layerPointToLatLng(layerPoint),
                    mousePos = this._map._docLayer._latLngToTwips(latlng);
 
+               if (window.ThisIsTheiOSApp) {
+                       if (this._toolbarAdded) {
+                               // set to the previous position
+                               
this._toolbar.setPosition(this._toolbar._latlng);
+                               this._toolbar.show();
+                               this._toolbarAdded = Date.now();
+                       }
+               }
+
                if (this._state === L.Map.TouchGesture.MARKER) {
                        this._map._fireDOMEvent(this._map, e.srcEvent, 
'mouseup');
                } else if (this._state === L.Map.TouchGesture.GRAPHIC) {
@@ -328,6 +486,9 @@ L.Map.TouchGesture = L.Handler.extend({
                if (this._map.getDocType() !== 'spreadsheet') {
                        this._pinchStartCenter = {x: e.center.x, y: e.center.y};
                }
+               if (window.ThisIsTheiOSApp) {
+                       this._toolbar.hide();
+               }
        },
 
        _onPinch: function (e) {
@@ -349,16 +510,69 @@ L.Map.TouchGesture = L.Handler.extend({
                        var oldZoom = this._map.getZoom(),
                            zoomDelta = this._zoom - oldZoom,
                            finalZoom = this._map._limitZoom(zoomDelta > 0 ? 
Math.ceil(this._zoom) : Math.floor(this._zoom));
+                       if (this._center) {
+                               this._map._animateZoom(this._center, finalZoom, 
true, true);
+                       }
+               }
 
-                       this._map._animateZoom(this._center, finalZoom, true, 
true);
+               if (window.ThisIsTheiOSApp) {
+                       if (this._toolbarAdded) {
+                               
this._toolbar.setPosition(this._toolbar._latlng);
+                               this._toolbar.show();
+                               this._toolbarAdded = Date.now();
+                       }
                }
        },
 
        _onInputPressiOSOnly: function (e) {
-               var pos = this._map.latLngToContainerPoint(e);
+               if (this._toolbar.isVisible())
+                       return;
+               var posLatLng = 
this._map._docLayer._visibleCursor.getNorthEast() || L.latLng(e);
+               this._addContextToolbar('TEXT_CURSOR_TOOLBAR', posLatLng, 
Date.now());
+       },
+
+       _onInputDragStartiOSOnly: function () {
+               this._toolbar.hide();
+               // not set this._toolbarAdded to null it's checked on drag end
+       },
+
+       _onInputDragEndiOSOnly: function (e) {
+               if (!this._toolbarAdded)
+                       return; // show the toolbar only if it was already 
active before starting dragging
+               var posLatLng = L.latLng(e);
+               this._toolbar.setPosition(posLatLng);
+               this._toolbar.show();
+               this._toolbarAdded = Date.now();
+       },
+
+       _onInputLostFocusiOSOnly: function () {
+               // remove the toolbar when a dialog pops up
+               if (!this._toolbar.isVisible())
+                       return;
                this._toolbar.remove();
-               this._toolbar._pos = pos;
-               this._toolbar.addTo(this._map);
+               this._toolbarAdded = null;
+       },
+
+       _onTextSelectionHandleDragEndiOSOnly: function (e) {
+               var posLatLng = L.latLng(e);
+               var that = this;
+               var timeout = 300;
+
+               var setContextToolbar = function (n) {
+                       if (that._newTextSelection) {
+                               posLatLng = that._getTextSelectionNorthCenter() 
|| posLatLng;
+                               
that._addContextToolbar('TEXT_SELECTION_TOOLBAR', posLatLng, Date.now());
+                       } else if (n < 5) {
+                               n += 1;
+                               setTimeout(setContextToolbar, n * timeout, n);
+                       }
+               };
+
+               setTimeout(setContextToolbar, timeout, 1);
+       },
+
+       _onTextSelectediOSOnly: function () {
+               this._newTextSelection = true;
        },
 
        _constructFakeEvent: function (evt, type) {
_______________________________________________
Libreoffice-commits mailing list
libreoffice-comm...@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits

Reply via email to