[Libreoffice-commits] online.git: Branch 'private/tml/ios-gen2' - 155 commits - android/lib android/README common/Authorization.hpp common/FileUtil.cpp common/FileUtil.hpp common/JailUtil.cpp common/Log.cpp common/MessageQueue.cpp common/Protocol.cpp common/Protocol.hpp common/Util.cpp common/Util.hpp configure.ac cypress_test/data cypress_test/integration_tests cypress_test/Makefile.am cypress_test/package.json cypress_test/plugins debian/loolwsd.postinst.in .gitignore gtk/mobile.cpp ios/GEN2.txt ios/Mobile kit/ChildSession.cpp kit/ForKit.cpp kit/Kit.cpp kit/Kit.hpp kit/SetupKitEnvironment.hpp loleaflet/admin loleaflet/css loleaflet/html loleaflet/images loleaflet/l10n loleaflet/Makefile.am loleaflet/po loleaflet/reference.html loleaflet/src loolwsd-systemplate-setup Makefile.am net/FakeSocket.cpp net/Socket.cpp test/Makefile.am test/WopiProofTests.cpp tools/Config.cpp wsd/Admin.cpp wsd/ClientSession.cpp wsd/ClientSession.hpp wsd/DocumentBroker.cpp wsd/DocumentBroker.hpp wsd/LOOLWSD.cpp wsd/LOOL WSD.hpp wsd/Storage.cpp wsd/Storage.hpp

Samuel Mehrbrodt (via logerrit) logerrit at kemper.freedesktop.org
Thu Jul 9 10:05:54 UTC 2020


Rebased ref, commits from common ancestor:
commit 20eaab272069226485c194d639388b6b7038c211
Author:     Samuel Mehrbrodt <Samuel.Mehrbrodt at cib.de>
AuthorDate: Tue Jul 7 21:07:21 2020 +0200
Commit:     Samuel Mehrbrodt <Samuel.Mehrbrodt at cib.de>
CommitDate: Thu Jul 9 10:25:36 2020 +0200

    Log number of active sessions
    
    Change-Id: Id161f09bc637e5dcf5ea0beaf11e360de7aa1fa2
    Reviewed-on: https://gerrit.libreoffice.org/c/online/+/98298
    Tested-by: Jenkins
    Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoffice at gmail.com>
    Reviewed-by: Samuel Mehrbrodt <Samuel.Mehrbrodt at cib.de>

diff --git a/wsd/DocumentBroker.cpp b/wsd/DocumentBroker.cpp
index f198bb8e3..fe9cdb7f0 100644
--- a/wsd/DocumentBroker.cpp
+++ b/wsd/DocumentBroker.cpp
@@ -2257,7 +2257,7 @@ void DocumentBroker::broadcastMessage(const std::string& message)
 {
     assertCorrectThread();
 
-    LOG_DBG("Broadcasting message [" << message << "] to all sessions.");
+    LOG_DBG("Broadcasting message [" << message << "] to all " << _sessions.size() <<  " sessions.");
     for (const auto& sessionIt : _sessions)
     {
         sessionIt.second->sendTextFrame(message);
commit dc5c44f712dfbaa42a8a0fe171638ca241799a93
Author:     Tamás Zolnai <tamas.zolnai at collabora.com>
AuthorDate: Thu Jul 9 07:54:24 2020 +0200
Commit:     Tamás Zolnai <tamas.zolnai at collabora.com>
CommitDate: Thu Jul 9 09:09:13 2020 +0200

    cypress: avoid failure of time field insertion (impress, mobile).
    
    Change-Id: I0330ae701a8d6a84f2cb57bc5ccef88f9fe56ecf
    Reviewed-on: https://gerrit.libreoffice.org/c/online/+/98394
    Tested-by: Jenkins
    Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoffice at gmail.com>
    Reviewed-by: Tamás Zolnai <tamas.zolnai at collabora.com>

diff --git a/cypress_test/integration_tests/mobile/impress/insertion_wizard_spec.js b/cypress_test/integration_tests/mobile/impress/insertion_wizard_spec.js
index 6c65caf8f..2627c4642 100644
--- a/cypress_test/integration_tests/mobile/impress/insertion_wizard_spec.js
+++ b/cypress_test/integration_tests/mobile/impress/insertion_wizard_spec.js
@@ -19,7 +19,7 @@ describe('Impress insertion wizard.', function() {
 		helper.afterAll(testFileName);
 	});
 
-	function selectionShouldBeTextShape() {
+	function selectionShouldBeTextShape(checkTextShape = true) {
 		// Check that the shape is there
 		cy.get('.leaflet-pane.leaflet-overlay-pane svg')
 			.should(function(svg) {
@@ -27,8 +27,9 @@ describe('Impress insertion wizard.', function() {
 				expect(svg[0].getBBox().height).to.be.greaterThan(0);
 			});
 
-		cy.get('.leaflet-pane.leaflet-overlay-pane svg g.com\\.sun\\.star\\.drawing\\.TextShape')
-			.should('exist');
+		if (checkTextShape)
+			cy.get('.leaflet-pane.leaflet-overlay-pane svg g.com\\.sun\\.star\\.drawing\\.TextShape')
+				.should('exist');
 
 		// Check also that the shape is fully visible
 		// TODO: shapes are hungs out of the slide after insertion
@@ -260,7 +261,7 @@ describe('Impress insertion wizard.', function() {
 			.click();
 
 		// Check that the shape is there
-		selectionShouldBeTextShape();
+		selectionShouldBeTextShape(false);
 
 		// Check the text
 		impressMobileHelper.selectTextOfShape();
@@ -280,7 +281,7 @@ describe('Impress insertion wizard.', function() {
 			.click();
 
 		// Check that the shape is there
-		selectionShouldBeTextShape();
+		selectionShouldBeTextShape(false);
 
 		// Check the text
 		impressMobileHelper.selectTextOfShape();
commit 83557cbb881f22e0a70c808076176560af5513b9
Author:     Michael Meeks <michael.meeks at collabora.com>
AuthorDate: Wed Jul 8 21:18:55 2020 +0100
Commit:     Michael Meeks <michael.meeks at collabora.com>
CommitDate: Wed Jul 8 23:11:26 2020 +0200

    Adapt l10n for latest Android locale goodness.
    
    cf. https://gist.github.com/amake/0ac7724681ac1c178c6f95a5b09f03ce
    
    Change-Id: Ie0cc7f210a800fd835356d246ec661757e9ab89f
    Reviewed-on: https://gerrit.libreoffice.org/c/online/+/98391
    Tested-by: Jenkins
    Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoffice at gmail.com>
    Reviewed-by: Michael Meeks <michael.meeks at collabora.com>

diff --git a/loleaflet/l10n/admin-localizations.json b/loleaflet/l10n/admin-localizations.json
index a42f0de97..9ac48be94 100644
--- a/loleaflet/l10n/admin-localizations.json
+++ b/loleaflet/l10n/admin-localizations.json
@@ -122,7 +122,9 @@
     "xh": "../l10n/ui-xh.json",
     "zh-cn": "../l10n/ui-zh_CN.json",
     "zh-CN": "../l10n/ui-zh_CN.json",
+    "zh-Hans": "../l10n/ui-zh_CN.json",
     "zh-tw": "../l10n/ui-zh_TW.json",
     "zh-TW": "../l10n/ui-zh_TW.json",
+    "zh-Hant": "../l10n/ui-zh_TW.json",
     "zu": "../l10n/ui-zu.json"
 }
diff --git a/loleaflet/l10n/help-localizations.json b/loleaflet/l10n/help-localizations.json
index 6dc5456be..559ac795e 100644
--- a/loleaflet/l10n/help-localizations.json
+++ b/loleaflet/l10n/help-localizations.json
@@ -123,7 +123,9 @@
     "xh": "%SERVICE_ROOT%/loleaflet/%VERSION%/l10n/help-xh.json",
     "zh-cn": "%SERVICE_ROOT%/loleaflet/%VERSION%/l10n/help-zh_CN.json",
     "zh-CN": "%SERVICE_ROOT%/loleaflet/%VERSION%/l10n/help-zh_CN.json",
+    "zh-Hans": "%SERVICE_ROOT%/loleaflet/%VERSION%/l10n/help-zh_CN.json",
     "zh-tw": "%SERVICE_ROOT%/loleaflet/%VERSION%/l10n/help-zh_TW.json",
     "zh-TW": "%SERVICE_ROOT%/loleaflet/%VERSION%/l10n/help-zh_TW.json",
+    "zh-Hant": "%SERVICE_ROOT%/loleaflet/%VERSION%/l10n/help-zh_TW.json",
     "zu": "%SERVICE_ROOT%/loleaflet/%VERSION%/l10n/help-zu.json"
 }
diff --git a/loleaflet/l10n/localizations.json b/loleaflet/l10n/localizations.json
index 2c988e5ba..b68c97ad6 100644
--- a/loleaflet/l10n/localizations.json
+++ b/loleaflet/l10n/localizations.json
@@ -123,7 +123,9 @@
     "xh": "%SERVICE_ROOT%/loleaflet/%VERSION%/l10n/ui-xh.json",
     "zh-cn": "%SERVICE_ROOT%/loleaflet/%VERSION%/l10n/ui-zh_CN.json",
     "zh-CN": "%SERVICE_ROOT%/loleaflet/%VERSION%/l10n/ui-zh_CN.json",
+    "zh-Hans": "%SERVICE_ROOT%/loleaflet/%VERSION%/l10n/ui-zh_CN.json",
     "zh-tw": "%SERVICE_ROOT%/loleaflet/%VERSION%/l10n/ui-zh_TW.json",
     "zh-TW": "%SERVICE_ROOT%/loleaflet/%VERSION%/l10n/ui-zh_TW.json",
+    "zh-Hant": "%SERVICE_ROOT%/loleaflet/%VERSION%/l10n/ui-zh_TW.json",
     "zu": "%SERVICE_ROOT%/loleaflet/%VERSION%/l10n/ui-zu.json"
 }
diff --git a/loleaflet/l10n/locore-localizations.json b/loleaflet/l10n/locore-localizations.json
index e99e1e85e..c828f7ffb 100644
--- a/loleaflet/l10n/locore-localizations.json
+++ b/loleaflet/l10n/locore-localizations.json
@@ -128,7 +128,9 @@
     "xh": "%SERVICE_ROOT%/loleaflet/%VERSION%/l10n/locore/xh.json",
     "zh-cn": "%SERVICE_ROOT%/loleaflet/%VERSION%/l10n/locore/zh-CN.json",
     "zh-CN": "%SERVICE_ROOT%/loleaflet/%VERSION%/l10n/locore/zh-CN.json",
+    "zh-Hans": "%SERVICE_ROOT%/loleaflet/%VERSION%/l10n/locore/zh-CN.json",
     "zh-tw": "%SERVICE_ROOT%/loleaflet/%VERSION%/l10n/locore/zh-TW.json",
     "zh-TW": "%SERVICE_ROOT%/loleaflet/%VERSION%/l10n/locore/zh-TW.json",
+    "zh-Hant": "%SERVICE_ROOT%/loleaflet/%VERSION%/l10n/locore/zh-TW.json",
     "zu": "%SERVICE_ROOT%/loleaflet/%VERSION%/l10n/locore/zu.json"
 }
diff --git a/loleaflet/l10n/uno-localizations.json b/loleaflet/l10n/uno-localizations.json
index 6ed799720..bf1566305 100644
--- a/loleaflet/l10n/uno-localizations.json
+++ b/loleaflet/l10n/uno-localizations.json
@@ -128,7 +128,9 @@
     "xh": "%SERVICE_ROOT%/loleaflet/%VERSION%/l10n/uno/xh.json",
     "zh-cn": "%SERVICE_ROOT%/loleaflet/%VERSION%/l10n/uno/zh-CN.json",
     "zh-CN": "%SERVICE_ROOT%/loleaflet/%VERSION%/l10n/uno/zh-CN.json",
+    "zh-Hans": "%SERVICE_ROOT%/loleaflet/%VERSION%/l10n/uno/zh-CN.json",
     "zh-tw": "%SERVICE_ROOT%/loleaflet/%VERSION%/l10n/uno/zh-TW.json",
     "zh-TW": "%SERVICE_ROOT%/loleaflet/%VERSION%/l10n/uno/zh-TW.json",
+    "zh-Hant": "%SERVICE_ROOT%/loleaflet/%VERSION%/l10n/uno/zh-TW.json",
     "zu": "%SERVICE_ROOT%/loleaflet/%VERSION%/l10n/uno/zu.json"
 }
commit 4a9ae824975948b6c4f582db1a02306e8fbd3675
Author:     Tamás Zolnai <tamas.zolnai at collabora.com>
AuthorDate: Wed Jul 8 16:29:41 2020 +0200
Commit:     Tamás Zolnai <tamas.zolnai at collabora.com>
CommitDate: Wed Jul 8 18:27:10 2020 +0200

    cypress: reenable some shape related tests.
    
    Change-Id: Ic21b812cf8c0370dcab9e8474f30206a2b88f70a
    Reviewed-on: https://gerrit.libreoffice.org/c/online/+/98381
    Tested-by: Jenkins
    Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoffice at gmail.com>
    Reviewed-by: Tamás Zolnai <tamas.zolnai at collabora.com>

diff --git a/cypress_test/integration_tests/mobile/writer/shape_properties_spec.js b/cypress_test/integration_tests/mobile/writer/shape_properties_spec.js
index e8c442cee..6f5b43321 100644
--- a/cypress_test/integration_tests/mobile/writer/shape_properties_spec.js
+++ b/cypress_test/integration_tests/mobile/writer/shape_properties_spec.js
@@ -113,7 +113,7 @@ describe('Change shape properties via mobile wizard.', function() {
 			.should('have.attr', 'd', 'M 1965,4863 L 7957,18073 1965,18073 1965,4863 1965,4863 Z');
 	});
 
-	it.skip('Change size with keep ratio enabled.', function() {
+	it('Change size with keep ratio enabled.', function() {
 		openPosSizePanel();
 
 		// Enable keep ratio
@@ -122,14 +122,8 @@ describe('Change shape properties via mobile wizard.', function() {
 		cy.get('#ratio #ratio')
 			.should('have.prop', 'checked', true);
 
-		// TODO: flickering here?
-		cy.wait(300);
-
 		// Change height
-		cy.get('#selectheight .spinfield')
-			.clear()
-			.type('5.2')
-			.type('{enter}');
+		helper.inputOnIdle('#selectheight .spinfield', '5.2');
 
 		cy.get('.leaflet-pane.leaflet-overlay-pane svg g svg g g g path')
 			.should('not.have.attr', 'd', defaultGeometry);
@@ -195,7 +189,7 @@ describe('Change shape properties via mobile wizard.', function() {
 			.should('have.attr', 'stroke', 'rgb(152,0,0)');
 	});
 
-	it.skip('Change line style', function() {
+	it('Change line style', function() {
 		openLinePropertyPanel();
 
 		helper.clickOnIdle('#linestyle');
@@ -208,7 +202,7 @@ describe('Change shape properties via mobile wizard.', function() {
 			.should('have.length.greaterThan', 12);
 	});
 
-	it.skip('Change line width', function() {
+	it('Change line width', function() {
 		openLinePropertyPanel();
 
 		cy.get('#linewidth .spinfield')
commit 42c15edeef9bdd7528cfde7091134cfbbc8925da
Author:     Dennis Francis <dennis.francis at collabora.com>
AuthorDate: Tue Jul 7 18:51:49 2020 +0530
Commit:     Dennis Francis <dennis.francis at collabora.com>
CommitDate: Wed Jul 8 17:21:51 2020 +0200

    enable split-panes feature for Calc for non-mobile
    
    Change-Id: I8be8c36ed5266a7d5eed13cbba95e849fa9723a2
    Reviewed-on: https://gerrit.libreoffice.org/c/online/+/98363
    Tested-by: Jenkins
    Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoffice at gmail.com>
    Reviewed-by: Dennis Francis <dennis.francis at collabora.com>

diff --git a/loleaflet/src/layer/tile/CalcTileLayer.js b/loleaflet/src/layer/tile/CalcTileLayer.js
index 116a4231d..2f1a6ac8a 100644
--- a/loleaflet/src/layer/tile/CalcTileLayer.js
+++ b/loleaflet/src/layer/tile/CalcTileLayer.js
@@ -4,7 +4,7 @@
  */
 
 /* global */
-L.CalcTileLayer = L.TileLayer.extend({
+L.CalcTileLayer = (L.Browser.mobile ? L.TileLayer : L.CanvasTileLayer).extend({
 	options: {
 		// TODO: sync these automatically from SAL_LOK_OPTIONS
 		sheetGeometryDataEnabled: true,
commit d1b3f4c6af35849e0da7a1148a88f183a4e1101f
Author:     Dennis Francis <dennis.francis at collabora.com>
AuthorDate: Wed Jul 8 07:10:13 2020 +0530
Commit:     Dennis Francis <dennis.francis at collabora.com>
CommitDate: Wed Jul 8 17:18:33 2020 +0200

    wsd/kit: use splitx/splity in clientvisiblearea message...
    
    to filter tile-invalidation messages, so that the client gets
    invalidations/new tiles for all split panes.
    
    Change-Id: Ifacc452ed6bb43dfd36ff16386fb4a547ec8302b
    Reviewed-on: https://gerrit.libreoffice.org/c/online/+/98362
    Tested-by: Jenkins
    Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoffice at gmail.com>
    Reviewed-by: Dennis Francis <dennis.francis at collabora.com>

diff --git a/kit/ChildSession.cpp b/kit/ChildSession.cpp
index c2644e68a..39b9d3b41 100644
--- a/kit/ChildSession.cpp
+++ b/kit/ChildSession.cpp
@@ -850,7 +850,7 @@ bool ChildSession::clientVisibleArea(const char* /*buffer*/, int /*length*/, con
     int width;
     int height;
 
-    if (tokens.size() != 5 ||
+    if ((tokens.size() != 5 && tokens.size() != 7) ||
         !getTokenInteger(tokens[1], "x", x) ||
         !getTokenInteger(tokens[2], "y", y) ||
         !getTokenInteger(tokens[3], "width", width) ||
diff --git a/wsd/ClientSession.cpp b/wsd/ClientSession.cpp
index b313742ec..abed4c5df 100644
--- a/wsd/ClientSession.cpp
+++ b/wsd/ClientSession.cpp
@@ -58,6 +58,8 @@ ClientSession::ClientSession(
     _state(SessionState::DETACHED),
     _keyEvents(1),
     _clientVisibleArea(0, 0, 0, 0),
+    _splitX(0),
+    _splitY(0),
     _clientSelectedPart(-1),
     _tileWidthPixel(0),
     _tileHeightPixel(0),
@@ -571,7 +573,7 @@ bool ClientSession::_handleInput(const char *buffer, int length)
         int y;
         int width;
         int height;
-        if (tokens.size() != 5 ||
+        if ((tokens.size() != 5 && tokens.size() != 7) ||
             !getTokenInteger(tokens[1], "x", x) ||
             !getTokenInteger(tokens[2], "y", y) ||
             !getTokenInteger(tokens[3], "width", width) ||
@@ -584,6 +586,20 @@ bool ClientSession::_handleInput(const char *buffer, int length)
         }
         else
         {
+            if (tokens.size() == 7)
+            {
+                int splitX, splitY;
+                if (!getTokenInteger(tokens[5], "splitx", splitX) ||
+                    !getTokenInteger(tokens[6], "splity", splitY))
+                {
+                    LOG_WRN("Invalid syntax for '" << tokens[0] << "' message: [" << firstLine << "].");
+                    return true;
+                }
+
+                _splitX = splitX;
+                _splitY = splitY;
+            }
+
             _clientVisibleArea = Util::Rectangle(x, y, width, height);
             resetWireIdMap();
             return forwardToChild(std::string(buffer, length), docBroker);
@@ -1732,15 +1748,31 @@ void ClientSession::handleTileInvalidation(const std::string& message,
         return;
     }
 
-    // Visible area can have negative value as position, but we have tiles only in the positive range
-    Util::Rectangle normalizedVisArea = getNormalizedVisibleArea();
-
     std::pair<int, Util::Rectangle> result = TileCache::parseInvalidateMsg(message);
     int part = result.first;
     Util::Rectangle& invalidateRect = result.second;
 
-    // We can ignore the invalidation if it's outside of the visible area
-    if(!normalizedVisArea.intersects(invalidateRect))
+    constexpr SplitPaneName panes[4] = {
+        TOPLEFT_PANE,
+        TOPRIGHT_PANE,
+        BOTTOMLEFT_PANE,
+        BOTTOMRIGHT_PANE
+    };
+    Util::Rectangle paneRects[4];
+    int numPanes = 0;
+    for(int i = 0; i < 4; ++i)
+    {
+        if(!isSplitPane(panes[i]))
+            continue;
+
+        Util::Rectangle rect = getNormalizedVisiblePaneArea(panes[i]);
+        if (rect.intersects(invalidateRect)) {
+            paneRects[numPanes++] = rect;
+        }
+    }
+
+    // We can ignore the invalidation if it's outside of all split-panes.
+    if(!numPanes)
         return;
 
     if( part == -1 ) // If no part is specified we use the part used by the client
@@ -1751,26 +1783,30 @@ void ClientSession::handleTileInvalidation(const std::string& message,
     std::vector<TileDesc> invalidTiles;
     if(part == _clientSelectedPart || _isTextDocument)
     {
-        // Iterate through visible tiles
-        for(int i = std::ceil(normalizedVisArea.getTop() / _tileHeightTwips);
-                    i <= std::ceil(normalizedVisArea.getBottom() / _tileHeightTwips); ++i)
+        for(int paneIdx = 0; paneIdx < numPanes; ++paneIdx)
         {
-            for(int j = std::ceil(normalizedVisArea.getLeft() / _tileWidthTwips);
-                j <= std::ceil(normalizedVisArea.getRight() / _tileWidthTwips); ++j)
+            const Util::Rectangle& normalizedVisArea = paneRects[paneIdx];
+            // Iterate through visible tiles
+            for(int i = std::ceil(normalizedVisArea.getTop() / _tileHeightTwips);
+                        i <= std::ceil(normalizedVisArea.getBottom() / _tileHeightTwips); ++i)
             {
-                // Find tiles affected by invalidation
-                Util::Rectangle tileRect (j * _tileWidthTwips, i * _tileHeightTwips, _tileWidthTwips, _tileHeightTwips);
-                if(invalidateRect.intersects(tileRect))
+                for(int j = std::ceil(normalizedVisArea.getLeft() / _tileWidthTwips);
+                    j <= std::ceil(normalizedVisArea.getRight() / _tileWidthTwips); ++j)
                 {
-                    invalidTiles.emplace_back(normalizedViewId, part, _tileWidthPixel, _tileHeightPixel, j * _tileWidthTwips, i * _tileHeightTwips, _tileWidthTwips, _tileHeightTwips, -1, 0, -1, false);
+                    // Find tiles affected by invalidation
+                    Util::Rectangle tileRect (j * _tileWidthTwips, i * _tileHeightTwips, _tileWidthTwips, _tileHeightTwips);
+                    if(invalidateRect.intersects(tileRect))
+                    {
+                        invalidTiles.emplace_back(normalizedViewId, part, _tileWidthPixel, _tileHeightPixel, j * _tileWidthTwips, i * _tileHeightTwips, _tileWidthTwips, _tileHeightTwips, -1, 0, -1, false);
 
-                    TileWireId oldWireId = 0;
-                    auto iter = _oldWireIds.find(invalidTiles.back().generateID());
-                    if(iter != _oldWireIds.end())
-                        oldWireId = iter->second;
+                        TileWireId oldWireId = 0;
+                        auto iter = _oldWireIds.find(invalidTiles.back().generateID());
+                        if(iter != _oldWireIds.end())
+                            oldWireId = iter->second;
 
-                    invalidTiles.back().setOldWireId(oldWireId);
-                    invalidTiles.back().setWireId(0);
+                        invalidTiles.back().setOldWireId(oldWireId);
+                        invalidTiles.back().setWireId(0);
+                    }
                 }
             }
         }
@@ -1784,6 +1820,80 @@ void ClientSession::handleTileInvalidation(const std::string& message,
     }
 }
 
+bool ClientSession::isSplitPane(const SplitPaneName paneName) const
+{
+    if (paneName == BOTTOMRIGHT_PANE)
+        return true;
+
+    if (paneName == TOPLEFT_PANE)
+        return (_splitX && _splitY);
+
+    if (paneName == TOPRIGHT_PANE)
+        return _splitY;
+
+    if (paneName == BOTTOMLEFT_PANE)
+        return _splitX;
+
+    return false;
+}
+
+Util::Rectangle ClientSession::getNormalizedVisiblePaneArea(const SplitPaneName paneName) const
+{
+    Util::Rectangle normalizedVisArea = getNormalizedVisibleArea();
+    if (!_splitX && !_splitY)
+        return paneName == BOTTOMRIGHT_PANE ? normalizedVisArea : Util::Rectangle();
+
+    int freeStartX = normalizedVisArea.getLeft() + _splitX;
+    int freeStartY = normalizedVisArea.getTop()  + _splitY;
+    int freeWidth = normalizedVisArea.getWidth() - _splitX;
+    int freeHeight = normalizedVisArea.getHeight() - _splitY;
+
+    switch (paneName)
+    {
+    case BOTTOMRIGHT_PANE:
+        return Util::Rectangle(freeStartX, freeStartY, freeWidth, freeHeight);
+    case TOPLEFT_PANE:
+        return (_splitX && _splitY) ? Util::Rectangle(0, 0, _splitX, _splitY) : Util::Rectangle();
+    case TOPRIGHT_PANE:
+        return _splitY ? Util::Rectangle(freeStartX, 0, freeWidth, _splitY) : Util::Rectangle();
+    case BOTTOMLEFT_PANE:
+        return _splitX ? Util::Rectangle(0, freeStartY, _splitX, freeHeight) : Util::Rectangle();
+    default:
+        assert(false && "Unknown split-pane name");
+    }
+
+    return Util::Rectangle();
+}
+
+bool ClientSession::isTileInsideVisibleArea(const TileDesc& tile) const
+{
+    if (!_splitX && !_splitY)
+    {
+        return (tile.getTilePosX() >= _clientVisibleArea.getLeft() && tile.getTilePosX() <= _clientVisibleArea.getRight() &&
+            tile.getTilePosY() >= _clientVisibleArea.getTop() && tile.getTilePosY() <= _clientVisibleArea.getBottom());
+    }
+
+    constexpr SplitPaneName panes[4] = {
+        TOPLEFT_PANE,
+        TOPRIGHT_PANE,
+        BOTTOMLEFT_PANE,
+        BOTTOMRIGHT_PANE
+    };
+
+    for (int i = 0; i < 4; ++i)
+    {
+        if (!isSplitPane(panes[i]))
+            continue;
+
+        Util::Rectangle paneRect = getNormalizedVisiblePaneArea(panes[i]);
+        if (tile.getTilePosX() >= paneRect.getLeft() && tile.getTilePosX() <= paneRect.getRight() &&
+            tile.getTilePosY() >= paneRect.getTop() && tile.getTilePosY() <= paneRect.getBottom())
+            return true;
+    }
+
+    return false;
+}
+
 void ClientSession::resetWireIdMap()
 {
     _oldWireIds.clear();
@@ -1802,9 +1912,7 @@ void ClientSession::traceTileBySend(const TileDesc& tile, bool deduplicated)
     else
     {
         // Track only tile inside the visible area
-        if(_clientVisibleArea.hasSurface() &&
-           tile.getTilePosX() >= _clientVisibleArea.getLeft() && tile.getTilePosX() <= _clientVisibleArea.getRight() &&
-           tile.getTilePosY() >= _clientVisibleArea.getTop() && tile.getTilePosY() <= _clientVisibleArea.getBottom())
+        if(_clientVisibleArea.hasSurface() && isTileInsideVisibleArea(tile))
         {
             _oldWireIds.insert(std::pair<std::string, TileWireId>(tileID, tile.getWireId()));
         }
diff --git a/wsd/ClientSession.hpp b/wsd/ClientSession.hpp
index cc153bdd8..6304cd4a9 100644
--- a/wsd/ClientSession.hpp
+++ b/wsd/ClientSession.hpp
@@ -138,6 +138,20 @@ public:
     /// Visible area can have negative value as position, but we have tiles only in the positive range
     Util::Rectangle getNormalizedVisibleArea() const;
 
+    /// The client's visible area can be divided into a maximum of 4 panes.
+    enum SplitPaneName {
+        TOPLEFT_PANE,
+        TOPRIGHT_PANE,
+        BOTTOMLEFT_PANE,
+        BOTTOMRIGHT_PANE
+    };
+
+    /// Returns true if the given split-pane is currently valid.
+    bool isSplitPane(const SplitPaneName) const;
+
+    /// Returns the normalized visible area of a given split-pane.
+    Util::Rectangle getNormalizedVisiblePaneArea(const SplitPaneName) const;
+
     int getTileWidthInTwips() const { return _tileWidthTwips; }
     int getTileHeightInTwips() const { return _tileHeightTwips; }
 
@@ -220,6 +234,8 @@ private:
     void handleTileInvalidation(const std::string& message,
                                 const std::shared_ptr<DocumentBroker>& docBroker);
 
+    bool isTileInsideVisibleArea(const TileDesc& tile) const;
+
 private:
     std::weak_ptr<DocumentBroker> _docBroker;
 
@@ -255,6 +271,10 @@ private:
     /// Visible area of the client
     Util::Rectangle _clientVisibleArea;
 
+    /// Split position that defines the current split panes
+    int _splitX;
+    int _splitY;
+
     /// Selected part of the document viewed by the client (no parts in Writer)
     int _clientSelectedPart;
 
commit 6cba945dc16b634b1db95491b7d1cab3f120e6e1
Author:     Dennis Francis <dennis.francis at collabora.com>
AuthorDate: Wed Jul 8 07:02:26 2020 +0530
Commit:     Dennis Francis <dennis.francis at collabora.com>
CommitDate: Wed Jul 8 17:12:48 2020 +0200

    handle tile-invalidation messages for split-panes
    
    Change-Id: Ib349987dee456198d611eabbc9171a14f0670f24
    Reviewed-on: https://gerrit.libreoffice.org/c/online/+/98361
    Tested-by: Jenkins
    Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoffice at gmail.com>
    Reviewed-by: Dennis Francis <dennis.francis at collabora.com>

diff --git a/loleaflet/src/geometry/Bounds.js b/loleaflet/src/geometry/Bounds.js
index 957e03fe1..cadc8e225 100644
--- a/loleaflet/src/geometry/Bounds.js
+++ b/loleaflet/src/geometry/Bounds.js
@@ -165,6 +165,16 @@ L.Bounds.prototype = {
 
 	isValid: function () {
 		return !!(this.min && this.max);
+	},
+
+	intersectsAny: function (boundsArray) {
+		for (var i = 0; i < boundsArray.length; ++i) {
+			if (boundsArray[i].intersects(this)) {
+				return true;
+			}
+		}
+
+		return false;
 	}
 };
 
diff --git a/loleaflet/src/layer/SplitPanesContext.js b/loleaflet/src/layer/SplitPanesContext.js
index 5d4909298..45dd502e0 100644
--- a/loleaflet/src/layer/SplitPanesContext.js
+++ b/loleaflet/src/layer/SplitPanesContext.js
@@ -174,6 +174,17 @@ L.SplitPanesContext = L.Class.extend({
 		return boundList;
 	},
 
+	getTwipsBoundList: function (pxBounds) {
+		var bounds = this.getPxBoundList(pxBounds);
+		var docLayer = this._docLayer;
+		return bounds.map(function (bound) {
+			return new L.Bounds(
+				docLayer._pixelsToTwips(bound.min),
+				docLayer._pixelsToTwips(bound.max)
+			);
+		});
+	},
+
 	getClientVisibleArea: function () {
 		var pixelBounds = this._map.getPixelBounds();
 		var fullSize = pixelBounds.getSize();
diff --git a/loleaflet/src/layer/tile/CalcTileLayer.js b/loleaflet/src/layer/tile/CalcTileLayer.js
index 1dfd7811c..116a4231d 100644
--- a/loleaflet/src/layer/tile/CalcTileLayer.js
+++ b/loleaflet/src/layer/tile/CalcTileLayer.js
@@ -373,10 +373,17 @@ L.CalcTileLayer = L.TileLayer.extend({
 		if (this._debug) {
 			this._debugAddInvalidationRectangle(topLeftTwips, bottomRightTwips, textMsg);
 		}
+
 		var invalidBounds = new L.Bounds(topLeftTwips, bottomRightTwips);
-		var visibleTopLeft = this._latLngToTwips(this._map.getBounds().getNorthWest());
-		var visibleBottomRight = this._latLngToTwips(this._map.getBounds().getSouthEast());
-		var visibleArea = new L.Bounds(visibleTopLeft, visibleBottomRight);
+		var visibleArea, visiblePaneAreas;
+		if (this._splitPanesContext) {
+			visiblePaneAreas = this._splitPanesContext.getTwipsBoundList();
+		}
+		else {
+			var visibleTopLeft = this._latLngToTwips(this._map.getBounds().getNorthWest());
+			var visibleBottomRight = this._latLngToTwips(this._map.getBounds().getSouthEast());
+			visibleArea = new L.Bounds(visibleTopLeft, visibleBottomRight);
+		}
 
 		var needsNewTiles = false;
 		for (var key in this._tiles) {
@@ -391,7 +398,8 @@ L.CalcTileLayer = L.TileLayer.extend({
 				else {
 					this._tiles[key]._invalidCount = 1;
 				}
-				if (visibleArea.intersects(bounds)) {
+				var intersectsVisible = visibleArea ? visibleArea.intersects(bounds) : bounds.intersectsAny(visiblePaneAreas);
+				if (intersectsVisible) {
 					needsNewTiles = true;
 					if (this._debug) {
 						this._debugAddInvalidationData(this._tiles[key]);
commit 05b7357edec6af69b2ef81f62fd38313e7337428
Author:     Dennis Francis <dennis.francis at collabora.com>
AuthorDate: Tue Jul 7 22:20:37 2020 +0530
Commit:     Dennis Francis <dennis.francis at collabora.com>
CommitDate: Wed Jul 8 17:09:52 2020 +0200

    Make cursor positioning work for split-panes
    
    Change-Id: I603e0309399b7031ad69d6e31f2ebd6c1bfe5fd2
    Reviewed-on: https://gerrit.libreoffice.org/c/online/+/98360
    Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoffice at gmail.com>
    Tested-by: Jenkins
    Reviewed-by: Dennis Francis <dennis.francis at collabora.com>

diff --git a/loleaflet/src/layer/marker/Cursor.js b/loleaflet/src/layer/marker/Cursor.js
index b7a0ba1a9..638e971fd 100644
--- a/loleaflet/src/layer/marker/Cursor.js
+++ b/loleaflet/src/layer/marker/Cursor.js
@@ -62,7 +62,8 @@ L.Cursor = L.Layer.extend({
 
 	update: function () {
 		if (this._container && this._map) {
-			var pos = this._map.latLngToLayerPoint(this._latlng).round();
+			var pos = this._map.latLngToContainerPoint(this._latlng).round();
+			pos = this._map.containerPointToLayerPointIgnoreSplits(pos);
 			this._setSize();
 			this._setPos(pos);
 		}
commit 732e440018011491ddf34113335f6f0b979e8237
Author:     Dennis Francis <dennis.francis at collabora.com>
AuthorDate: Tue Jul 7 21:52:27 2020 +0530
Commit:     Dennis Francis <dennis.francis at collabora.com>
CommitDate: Wed Jul 8 17:06:01 2020 +0200

    make _onUpdateCursor work correctly for split-panes
    
    Change-Id: Ib81a4530a48686fb2e2e31aee9941aab654f0868
    Reviewed-on: https://gerrit.libreoffice.org/c/online/+/98359
    Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoffice at gmail.com>
    Tested-by: Jenkins
    Reviewed-by: Dennis Francis <dennis.francis at collabora.com>

diff --git a/loleaflet/src/geo/LatLngBounds.js b/loleaflet/src/geo/LatLngBounds.js
index 80e059750..1241da361 100644
--- a/loleaflet/src/geo/LatLngBounds.js
+++ b/loleaflet/src/geo/LatLngBounds.js
@@ -189,7 +189,20 @@ L.LatLngBounds.prototype = {
 
 	isValid: function () {
 		return !!(this._southWest && this._northEast);
-	}
+	},
+
+	isInAny: function (latLngBoundsArray) {
+		console.assert(Array.isArray(latLngBoundsArray), 'invalid argument type');
+
+		for (var i = 0; i < latLngBoundsArray.length; ++i) {
+			if (latLngBoundsArray[i].contains(this)) {
+				return true;
+			}
+		}
+
+		return false;
+	},
+
 };
 
 L.LatLngBounds.createDefault = function() {
diff --git a/loleaflet/src/layer/tile/CalcTileLayer.js b/loleaflet/src/layer/tile/CalcTileLayer.js
index 70fbe3dd0..1dfd7811c 100644
--- a/loleaflet/src/layer/tile/CalcTileLayer.js
+++ b/loleaflet/src/layer/tile/CalcTileLayer.js
@@ -931,7 +931,7 @@ L.CalcTileLayer = L.TileLayer.extend({
 		return this._twipsToPixels(this._cellCursorTwips.getTopLeft());
 	},
 
-	_calculateScrollForNewCursor: function () {
+	_calculateScrollForNewCellCursor: function () {
 
 		var scroll = new L.LatLng(0, 0);
 
@@ -940,28 +940,10 @@ L.CalcTileLayer = L.TileLayer.extend({
 		}
 
 		var map = this._map;
-		var paneRects = this._splitPanesContext ?
-			this._splitPanesContext.getPxBoundList() : undefined;
-
-		console.assert(paneRects === undefined || paneRects.length, 'number of panes cannot be zero!');
-
-		var paneRectsInLatLng = paneRects ? paneRects.map(function (pxBound) {
-			return new L.LatLngBounds(
-				map.unproject(pxBound.getTopLeft()),
-				map.unproject(pxBound.getBottomRight())
-			);
-		}) : [ map.getBounds() ];
-
-		var scrollNeeded = true;
-		for (var i = 0; i < paneRectsInLatLng.length; ++i) {
-			if (paneRectsInLatLng[i].contains(this._cellCursor)) {
-				scrollNeeded = false;
-				break;
-			}
-		}
+		var paneRectsInLatLng = this.getPaneLatLngRectangles();
 
-		if (!scrollNeeded) {
-			return scroll; // zero scroll.
+		if (this._cellCursor.isInAny(paneRectsInLatLng)) {
+			return scroll; // no scroll needed.
 		}
 
 		var freePaneBounds = paneRectsInLatLng[paneRectsInLatLng.length - 1];
diff --git a/loleaflet/src/layer/tile/TileLayer.js b/loleaflet/src/layer/tile/TileLayer.js
index e5f73a10c..438476c26 100644
--- a/loleaflet/src/layer/tile/TileLayer.js
+++ b/loleaflet/src/layer/tile/TileLayer.js
@@ -2250,18 +2250,22 @@ L.TileLayer = L.GridLayer.extend({
 
 		if (!zoom
 		&& scroll !== false
-		&& !this._map.getBounds().contains(this._visibleCursor)
 		&& this._map._isCursorVisible
 		&& (!this._map._clip || this._map._clip._selectionType !== 'complex')) {
 
-			var center = this._map.project(cursorPos);
-			center = center.subtract(this._map.getSize().divideBy(2));
-			center.x = Math.round(center.x < 0 ? 0 : center.x);
-			center.y = Math.round(center.y < 0 ? 0 : center.y);
-			if (!(this._selectionHandles.start && this._selectionHandles.start.isDragged) &&
-				!(this._selectionHandles.end && this._selectionHandles.end.isDragged) &&
-				!(docLayer._followEditor || docLayer._followUser)) {
-				this._map.fire('scrollto', {x: center.x, y: center.y, calledFromInvalidateCursorMsg: scroll !== undefined});
+			var paneRectsInLatLng = this.getPaneLatLngRectangles();
+
+			if (!this._visibleCursor.isInAny(paneRectsInLatLng)) {
+				var center = this._map.project(cursorPos);
+				center = center.subtract(this._map.getSize().divideBy(2));
+				center.x = Math.round(center.x < 0 ? 0 : center.x);
+				center.y = Math.round(center.y < 0 ? 0 : center.y);
+
+				if (!(this._selectionHandles.start && this._selectionHandles.start.isDragged) &&
+					!(this._selectionHandles.end && this._selectionHandles.end.isDragged) &&
+					!(docLayer._followEditor || docLayer._followUser)) {
+					this._map.fire('scrollto', {x: center.x, y: center.y, calledFromInvalidateCursorMsg: scroll !== undefined});
+				}
 			}
 		}
 
@@ -2963,8 +2967,8 @@ L.TileLayer = L.GridLayer.extend({
 			}
 			var mapBounds = this._map.getBounds();
 			if (!this._cellCursorXY.equals(this._prevCellCursorXY)) {
-				var scroll = this._calculateScrollForNewCursor();
-				console.assert(scroll instanceof L.LatLng, '_calculateScrollForNewCursor returned wrong type');
+				var scroll = this._calculateScrollForNewCellCursor();
+				console.assert(scroll instanceof L.LatLng, '_calculateScrollForNewCellCursor returned wrong type');
 				if (scroll.lng !== 0 || scroll.lat !== 0) {
 					var newCenter = mapBounds.getCenter();
 					newCenter.lng += scroll.lng;
@@ -3422,6 +3426,24 @@ L.TileLayer = L.GridLayer.extend({
 		return new L.Point(0, 0);
 	},
 
+	getPaneLatLngRectangles: function () {
+		var map = this._map;
+
+		if (!this._splitPanesContext) {
+			return [ map.getBounds() ];
+		}
+
+		var paneRects = this._splitPanesContext.getPxBoundList();
+		console.assert(paneRects.length, 'number of panes cannot be zero!');
+
+		return paneRects.map(function (pxBound) {
+			return new L.LatLngBounds(
+				map.unproject(pxBound.getTopLeft()),
+				map.unproject(pxBound.getBottomRight())
+			);
+		});
+	},
+
 	_debugGetTimeArray: function() {
 		return {count: 0, ms: 0, best: Number.MAX_SAFE_INTEGER, worst: 0, date: 0};
 	},
commit 8c1b87462fa759aafb864cc953acb87e963beabf
Author:     Dennis Francis <dennis.francis at collabora.com>
AuthorDate: Tue Jul 7 17:21:35 2020 +0530
Commit:     Dennis Francis <dennis.francis at collabora.com>
CommitDate: Wed Jul 8 17:04:38 2020 +0200

    integrate SplitPanesContext with CalcTileLayer...
    
    and take split-panes into account when switching views on the arrival
    of a new cursor message.
    
    Change-Id: If97b06ceefd15335555d77b4074fd6991e21b7ab
    Reviewed-on: https://gerrit.libreoffice.org/c/online/+/98358
    Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoffice at gmail.com>
    Tested-by: Jenkins
    Reviewed-by: Dennis Francis <dennis.francis at collabora.com>

diff --git a/loleaflet/src/layer/tile/CalcTileLayer.js b/loleaflet/src/layer/tile/CalcTileLayer.js
index 9d711b48f..70fbe3dd0 100644
--- a/loleaflet/src/layer/tile/CalcTileLayer.js
+++ b/loleaflet/src/layer/tile/CalcTileLayer.js
@@ -56,6 +56,7 @@ L.CalcTileLayer = L.TileLayer.extend({
 		map.on('AnnotationCancel', this._onAnnotationCancel, this);
 		map.on('AnnotationReply', this._onAnnotationReply, this);
 		map.on('AnnotationSave', this._onAnnotationSave, this);
+		map.on('splitposchanged', this._calcSplitCell, this);
 
 		map.uiManager.initializeSpecializedUI('spreadsheet');
 	},
@@ -68,6 +69,7 @@ L.CalcTileLayer = L.TileLayer.extend({
 	},
 
 	onAdd: function (map) {
+		this._switchSplitPanesContext();
 		map.addControl(L.control.tabs());
 		map.addControl(L.control.columnHeader());
 		map.addControl(L.control.rowHeader());
@@ -437,6 +439,7 @@ L.CalcTileLayer = L.TileLayer.extend({
 		var part = parseInt(textMsg.match(/\d+/g)[0]);
 		if (!this.isHiddenPart(part)) {
 			this._clearMsgReplayStore();
+			this._switchSplitPanesContext();
 			this.refreshViewData(undefined, false /* compatDataSrcOnly */, true /* sheetGeometryChanged */);
 		}
 	},
@@ -449,6 +452,7 @@ L.CalcTileLayer = L.TileLayer.extend({
 		}
 		this._restrictDocumentSize();
 		this._replayPrintTwipsMsgs();
+		this._updateSplitPos();
 		this._map.fire('zoomchanged');
 		this.refreshViewData();
 		this._map._socket.sendMessage('commandvalues command=.uno:ViewAnnotationsPosition');
@@ -721,9 +725,55 @@ L.CalcTileLayer = L.TileLayer.extend({
 		this._updateHeadersGridLines(undefined, true /* updateCols */,
 			true /* updateRows */);
 
+		this._updateSplitPos();
+
 		this._map.fire('sheetgeometrychanged');
 	},
 
+	_updateSplitPos: function () {
+		if (this._splitPanesContext) {
+			if (this._splitPanesContext._splitCell) {
+				var splitCell = this._splitPanesContext._splitCell;
+				var newSplitPos = this.sheetGeometry.getCellRect(splitCell.x, splitCell.y).min;
+				this._splitPanesContext.setSplitPos(newSplitPos.x, newSplitPos.y); // will update the splitters.
+			}
+			else {
+				// Can happen only on load.
+				this._splitPanesContext.alignSplitPos();
+				this._calcSplitCell();
+			}
+		}
+	},
+
+	_calcSplitCell: function () {
+
+		if (!this.sheetGeometry || !this._splitPanesContext) {
+			return;
+		}
+
+		this._splitPanesContext._splitCell =
+			this.sheetGeometry.getCellFromPos(this._splitPanesContext.getSplitPos(), 'csspixels');
+	},
+
+	_switchSplitPanesContext: function () {
+
+		if (!this.hasSplitPanesSupport()) {
+			return;
+		}
+
+		if (!this._splitPaneCache) {
+			this._splitPaneCache = {};
+		}
+
+		var spContext = this._splitPaneCache[this._selectedPart];
+		if (!spContext) {
+			spContext = new L.SplitPanesContext(this);
+		}
+
+		this._splitPanesContext = spContext;
+		this._map._splitPanesContext = spContext;
+	},
+
 	_onCommandValuesMsg: function (textMsg) {
 		var jsonIdx = textMsg.indexOf('{');
 		if (jsonIdx === -1)
@@ -881,6 +931,69 @@ L.CalcTileLayer = L.TileLayer.extend({
 		return this._twipsToPixels(this._cellCursorTwips.getTopLeft());
 	},
 
+	_calculateScrollForNewCursor: function () {
+
+		var scroll = new L.LatLng(0, 0);
+
+		if (!this._cellCursor || this._isEmptyRectangle(this._cellCursor)) {
+			return scroll;
+		}
+
+		var map = this._map;
+		var paneRects = this._splitPanesContext ?
+			this._splitPanesContext.getPxBoundList() : undefined;
+
+		console.assert(paneRects === undefined || paneRects.length, 'number of panes cannot be zero!');
+
+		var paneRectsInLatLng = paneRects ? paneRects.map(function (pxBound) {
+			return new L.LatLngBounds(
+				map.unproject(pxBound.getTopLeft()),
+				map.unproject(pxBound.getBottomRight())
+			);
+		}) : [ map.getBounds() ];
+
+		var scrollNeeded = true;
+		for (var i = 0; i < paneRectsInLatLng.length; ++i) {
+			if (paneRectsInLatLng[i].contains(this._cellCursor)) {
+				scrollNeeded = false;
+				break;
+			}
+		}
+
+		if (!scrollNeeded) {
+			return scroll; // zero scroll.
+		}
+
+		var freePaneBounds = paneRectsInLatLng[paneRectsInLatLng.length - 1];
+		var splitPoint = map.unproject(this._splitPanesContext ? this._splitPanesContext.getSplitPos() : new L.Point(0, 0));
+
+		if (this._cellCursor.getEast() > splitPoint.lng) {
+
+			var freePaneWidth = Math.abs(freePaneBounds.getEast() - freePaneBounds.getWest());
+			var cursorWidth = Math.abs(this._cellCursor.getEast() - this._cellCursor.getWest());
+			var spacingX = cursorWidth / 4.0;
+
+			if (this._cellCursor.getWest() < freePaneBounds.getWest()) {
+				scroll.lng = this._cellCursor.getWest() - freePaneBounds.getWest() - spacingX;
+			}
+			else if (cursorWidth < freePaneWidth && this._cellCursor.getEast() > freePaneBounds.getEast()) {
+				scroll.lng = this._cellCursor.getEast() - freePaneBounds.getEast() + spacingX;
+			}
+		}
+
+		if (this._cellCursor.getSouth() < splitPoint.lat) {
+
+			var spacingY = Math.abs((this._cellCursor.getSouth() - this._cellCursor.getNorth())) / 4.0;
+			if (this._cellCursor.getNorth() > freePaneBounds.getNorth()) {
+				scroll.lat = this._cellCursor.getNorth() - freePaneBounds.getNorth() + spacingY;
+			}
+			else if (this._cellCursor.getSouth() < freePaneBounds.getSouth()) {
+				scroll.lat = this._cellCursor.getSouth() - freePaneBounds.getSouth() - spacingY;
+			}
+		}
+
+		return scroll;
+	},
 });
 
 L.MessageStore = L.Class.extend({
diff --git a/loleaflet/src/layer/tile/TileLayer.js b/loleaflet/src/layer/tile/TileLayer.js
index 73d2da8a5..e5f73a10c 100644
--- a/loleaflet/src/layer/tile/TileLayer.js
+++ b/loleaflet/src/layer/tile/TileLayer.js
@@ -2962,40 +2962,13 @@ L.TileLayer = L.GridLayer.extend({
 				this._map.dialog._updateTextSelection(inputBarId);
 			}
 			var mapBounds = this._map.getBounds();
-			if (!mapBounds.contains(this._cellCursor) && !this._cellCursorXY.equals(this._prevCellCursorXY)) {
-				var scrollX = 0, scrollY = 0;
-				if (onPgUpDn) {
-					var mapHalfHeight = (mapBounds.getNorth() - mapBounds.getSouth()) / 2;
-					var cellCursorOnPgUpDn = (this._cellCursorOnPgUp) ? this._cellCursorOnPgUp : this._cellCursorOnPgDn;
-
-					scrollY = this._cellCursor.getNorth() - cellCursorOnPgUpDn.getNorth();
-					if (this._cellCursor.getNorth() > mapBounds.getNorth() + scrollY) {
-						scrollY = (this._cellCursor.getNorth() - mapBounds.getNorth()) + mapHalfHeight;
-					} else if (this._cellCursor.getSouth() < mapBounds.getSouth() + scrollY) {
-						scrollY = (this._cellCursor.getNorth() - mapBounds.getNorth()) + mapHalfHeight;
-					}
-				}
-				else if (horizontalDirection !== 0 || verticalDirection != 0) {
-					var mapX = Math.abs(mapBounds.getEast() - mapBounds.getWest());
-					var cursorX = Math.abs(this._cellCursor.getEast() - this._cellCursor.getWest());
-					var spacingX = cursorX / 4.0;
-					var spacingY = Math.abs((this._cellCursor.getSouth() - this._cellCursor.getNorth())) / 4.0;
-
-					if (this._cellCursor.getWest() < mapBounds.getWest()) {
-						scrollX = this._cellCursor.getWest() - mapBounds.getWest() - spacingX;
-					} else if (cursorX < mapX && this._cellCursor.getEast() > mapBounds.getEast()) {
-						scrollX = this._cellCursor.getEast() - mapBounds.getEast() + spacingX;
-					}
-					if (this._cellCursor.getNorth() > mapBounds.getNorth()) {
-						scrollY = this._cellCursor.getNorth() - mapBounds.getNorth() + spacingY;
-					} else if (this._cellCursor.getSouth() < mapBounds.getSouth()) {
-						scrollY = this._cellCursor.getSouth() - mapBounds.getSouth() - spacingY;
-					}
-				}
-				if (scrollX !== 0 || scrollY !== 0) {
+			if (!this._cellCursorXY.equals(this._prevCellCursorXY)) {
+				var scroll = this._calculateScrollForNewCursor();
+				console.assert(scroll instanceof L.LatLng, '_calculateScrollForNewCursor returned wrong type');
+				if (scroll.lng !== 0 || scroll.lat !== 0) {
 					var newCenter = mapBounds.getCenter();
-					newCenter.lng += scrollX;
-					newCenter.lat += scrollY;
+					newCenter.lng += scroll.lng;
+					newCenter.lat += scroll.lat;
 					var center = this._map.project(newCenter);
 					center = center.subtract(this._map.getSize().divideBy(2));
 					center.x = Math.round(center.x < 0 ? 0 : center.x);
commit 0b50166333219a5e111aa6e5344f940ead6ad628
Author:     Dennis Francis <dennis.francis at collabora.com>
AuthorDate: Tue Jul 7 15:42:47 2020 +0530
Commit:     Dennis Francis <dennis.francis at collabora.com>
CommitDate: Wed Jul 8 17:02:07 2020 +0200

    make row/col headers render correctly for split-panes
    
    For this to work, we need sheet-geometry data. GapTickMap is dropped
    with a replacement HeaderInfo class that is easier/less confusing to
    work with split-panes. All indices in HeaderInfo are 0-based and a
    column/row is referred to as an 'element' when things have to be
    generic.
    
    Change-Id: Ibddac8901d48cada554b715af70195ef9b9832e2
    Reviewed-on: https://gerrit.libreoffice.org/c/online/+/98357
    Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoffice at gmail.com>
    Tested-by: Jenkins
    Reviewed-by: Dennis Francis <dennis.francis at collabora.com>

diff --git a/loleaflet/src/control/Control.ColumnHeader.js b/loleaflet/src/control/Control.ColumnHeader.js
index 80559a20d..8a07f1e86 100644
--- a/loleaflet/src/control/Control.ColumnHeader.js
+++ b/loleaflet/src/control/Control.ColumnHeader.js
@@ -11,6 +11,7 @@ L.Control.ColumnHeader = L.Control.Header.extend({
 
 	onAdd: function (map) {
 		map.on('updatepermission', this._onUpdatePermission, this);
+		map.on('moveend zoomchanged sheetgeometrychanged splitposchanged', this._updateCanvas, this);
 		this._initialized = false;
 	},
 
@@ -159,6 +160,13 @@ L.Control.ColumnHeader = L.Control.Header.extend({
 		this._updateColumnHeader();
 	},
 
+	_updateCanvas: function () {
+		if (this._headerInfo) {
+			this._headerInfo.update();
+			this._redrawHeaders();
+		}
+	},
+
 	setScrollPosition: function (e) {
 		var position = -e.x;
 		this._position = Math.min(0, position);
@@ -186,7 +194,7 @@ L.Control.ColumnHeader = L.Control.Header.extend({
 	},
 
 	_onUpdateCurrentColumn: function (e) {
-		var x = e.curX;
+		var x = e.curX - 1; // 1-based to 0-based.
 		var w = this._twipsToPixels(e.width);
 		var slim = w <= 1;
 		this.updateCurrent(x, slim);
@@ -201,10 +209,10 @@ L.Control.ColumnHeader = L.Control.Header.extend({
 			return;
 
 		var ctx = this._canvasContext;
-		var content = this._colIndexToAlpha(entry.index);
+		var content = this._colIndexToAlpha(entry.index + 1);
 		var startOrt = this._canvasHeight - this._headerHeight;
-		var startPar = entry.pos - entry.size - this._startOffset;
-		var endPar = entry.pos - this._startOffset;
+		var startPar = entry.pos - entry.size;
+		var endPar = entry.pos;
 		var width = endPar - startPar;
 		var height = this._headerHeight;
 
@@ -218,7 +226,6 @@ L.Control.ColumnHeader = L.Control.Header.extend({
 		ctx.save();
 		var scale = L.getDpiScaleFactor();
 		ctx.scale(scale, scale);
-		ctx.translate(this._position + this._startOffset, 0);
 		// background gradient
 		var selectionBackgroundGradient = null;
 		if (isHighlighted) {
@@ -279,14 +286,13 @@ L.Control.ColumnHeader = L.Control.Header.extend({
 		var level = group.level;
 
 		var startOrt = spacing + (headSize + spacing) * level;
-		var startPar = group.startPos - this._startOffset;
+		var startPar = this._headerInfo.docToHeaderPos(group.startPos);
 		var height = group.endPos - group.startPos;
 
 		ctx.save();
 		var scale = L.getDpiScaleFactor();
 		ctx.scale(scale, scale);
 
-		ctx.translate(this._position + this._startOffset, 0);
 		// clip mask
 		ctx.beginPath();
 		ctx.rect(startPar, startOrt, height, headSize);
@@ -357,7 +363,7 @@ L.Control.ColumnHeader = L.Control.Header.extend({
 	getHeaderEntryBoundingClientRect: function (index) {
 		var entry = this._mouseOverEntry;
 		if (index) {
-			entry = this._tickMap.getGap(index);
+			entry = this._headerInfo.getColData(index);
 		}
 
 		if (!entry)
@@ -365,8 +371,8 @@ L.Control.ColumnHeader = L.Control.Header.extend({
 
 		var rect = this._canvas.getBoundingClientRect();
 
-		var colStart = entry.pos - entry.size + this._position;
-		var colEnd = entry.pos + this._position;
+		var colStart = entry.pos - entry.size;
+		var colEnd = entry.pos;
 
 		var left = rect.left + colStart;
 		var right = rect.left + colEnd;
@@ -408,11 +414,12 @@ L.Control.ColumnHeader = L.Control.Header.extend({
 		}
 
 		var sheetGeometry = this._map._docLayer.sheetGeometry;
-		var columnsGeometry = sheetGeometry ? sheetGeometry.getColumnsGeometry() : undefined;
 
-		// create data structure for column widths
-		this._tickMap = new L.Control.Header.GapTickMap(this._map, columns, columnsGeometry);
-		this._startOffset = this._tickMap.getStartOffset();
+		if (!this._headerInfo) {
+			// create data structure for column widths
+			this._headerInfo = new L.Control.Header.HeaderInfo(this._map, true /* isCol */);
+			this._map._colHdr = this._headerInfo;
+		}
 
 		// setup conversion routine
 		this.converter = L.Util.bind(converter, context);
@@ -437,13 +444,7 @@ L.Control.ColumnHeader = L.Control.Header.extend({
 			this.resize(this._headerHeight);
 		}
 
-		// Initial draw
-		this._tickMap.forEachGap(function(gap) {
-			this.drawHeaderEntry(gap, false);
-		}.bind(this));
-
-		// draw group controls
-		this.drawOutline();
+		this._redrawHeaders();
 
 		this.mouseInit(canvas);
 
@@ -452,6 +453,16 @@ L.Control.ColumnHeader = L.Control.Header.extend({
 		}
 	},
 
+	_redrawHeaders: function () {
+		this._canvasContext.clearRect(0, 0, this._canvas.width, this._canvas.height);
+		this._headerInfo.forEachElement(function(elemData) {
+			this.drawHeaderEntry(elemData, false);
+		}.bind(this));
+
+		// draw group controls
+		this.drawOutline();
+	},
+
 	_colAlphaToNumber: function(alpha) {
 		var res = 0;
 		var offset = 'A'.charCodeAt();
@@ -482,7 +493,7 @@ L.Control.ColumnHeader = L.Control.Header.extend({
 		var command = {
 			Col: {
 				type: 'unsigned short',
-				value: colNumber - 1
+				value: colNumber
 			},
 			Modifier: {
 				type: 'unsigned short',
@@ -549,11 +560,18 @@ L.Control.ColumnHeader = L.Control.Header.extend({
 	},
 
 	_getVertLatLng: function (start, offset, e) {
-		var limit = this._map.mouseEventToContainerPoint({clientX: start.x, clientY: start.y});
+		var size = this._map.getSize();
 		var drag = this._map.mouseEventToContainerPoint(e);
+		var entryStart = this._dragEntry.pos - this._dragEntry.size;
+		var xdocpos = this._headerInfo.headerToDocPos(Math.max(drag.x, entryStart));
+		var ymin = this._map.getPixelBounds().min.y;
+		var ymax = ymin + size.y;
+		if (this._headerInfo.hasSplits()) {
+			ymin = 0;
+		}
 		return [
-			this._map.containerPointToLatLng(new L.Point(Math.max(limit.x, drag.x + offset.x), 0)),
-			this._map.containerPointToLatLng(new L.Point(Math.max(limit.x, drag.x + offset.x), this._map.getSize().y))
+			this._map.unproject(new L.Point(xdocpos, ymin)),
+			this._map.unproject(new L.Point(xdocpos, ymax)),
 		];
 	},
 
@@ -583,8 +601,9 @@ L.Control.ColumnHeader = L.Control.Header.extend({
 			var width = clickedColumn.size;
 			var column = clickedColumn.index;
 
-			if (this._tickMap.isZeroSize(clickedColumn.index + 1)) {
-				column += 1;
+			var nextCol = this._headerInfo.getNextIndex(clickedColumn.index);
+			if (this._headerInfo.isZeroSize(nextCol)) {
+				column = nextCol;
 				width = 0;
 			}
 
@@ -596,7 +615,7 @@ L.Control.ColumnHeader = L.Control.Header.extend({
 					},
 					Column: {
 						type: 'unsigned short',
-						value: column
+						value: column + 1 // core expects 1-based index.
 					}
 				};
 
@@ -619,7 +638,7 @@ L.Control.ColumnHeader = L.Control.Header.extend({
 			var command = {
 				Col: {
 					type: 'unsigned short',
-					value: column - 1
+					value: column
 				},
 				Modifier: {
 					type: 'unsigned short',
diff --git a/loleaflet/src/control/Control.Header.js b/loleaflet/src/control/Control.Header.js
index 3ef4bb2e0..8dff63acc 100644
--- a/loleaflet/src/control/Control.Header.js
+++ b/loleaflet/src/control/Control.Header.js
@@ -172,17 +172,17 @@ L.Control.Header = L.Control.extend({
 	clearSelection: function () {
 		if (this._selection.start === -1 && this._selection.end === -1)
 			return;
-		var start = (this._selection.start < 1) ? 1 : this._selection.start;
-		var end = this._selection.end + 1;
+		var start = (this._selection.start < 1) ? 0 : this._selection.start;
+		var end = this._headerInfo.getNextIndex(this._selection.end);
 
-		for (var i=start; i<end; i++) {
+		for (var i = start; i < end; i = this._headerInfo.getNextIndex(i)) {
 			if (i === this._current) {
 				// after clearing selection, we need to select the header entry for the current cursor position,
 				// since we can't be sure that the selection clearing is due to click on a cell
 				// different from the one where the cursor is already placed
-				this.select(this._tickMap.getGap(i), true);
+				this.select(this._headerInfo.getElementData(i), true);
 			} else {
-				this.unselect(this._tickMap.getGap(i));
+				this.unselect(this._headerInfo.getElementData(i));
 			}
 		}
 
@@ -193,25 +193,28 @@ L.Control.Header = L.Control.extend({
 	// selects the new set of rows/cols.
 	// Start and end are given in pixels absolute to the document
 	updateSelection: function(start, end) {
-		if (!this._tickMap)
+		if (!this._headerInfo)
 			return;
 
+		start = this._headerInfo.docToHeaderPos(start);
+		end = this._headerInfo.docToHeaderPos(end);
+
 		var x0 = 0, x1 = 0;
 		var itStart = -1, itEnd = -1;
 
 		// if the start selection position is above/on the left of the first header entry,
 		// but the end selection position is below/on the right of it
 		// then we set the start selected entry to the first header entry.
-		var entry = this._tickMap.getGap(this._tickMap.getMinTickIdx() + 1);
+		var entry = this._headerInfo.getElementData(this._headerInfo.getMinIndex());
 		if (entry) {
 			x0 = entry.pos - entry.size;
 			if (start < x0 && end > x0) {
-				itStart = 1;
+				itStart = 0;
 			}
 		}
 
-		this._tickMap.forEachGap((function(entry) {
-			x0 = entry.start;
+		this._headerInfo.forEachElement((function(entry) {
+			x0 = entry.pos - entry.size;
 			x1 = entry.pos;
 			if (start < x1 && end > x0) {
 				this.select(entry, false);
@@ -221,7 +224,7 @@ L.Control.Header = L.Control.extend({
 			} else {
 				this.unselect(entry);
 				if (itStart !== -1 && itEnd === -1) {
-					itEnd = entry.index - 1;
+					itEnd = this._headerInfo.getPreviousIndex(entry.index);
 				}
 			}
 		}).bind(this));
@@ -229,7 +232,7 @@ L.Control.Header = L.Control.extend({
 		// if end is greater than the last fetched header position set itEnd to the max possible value
 		// without this hack selecting a whole row and then a whole column (or viceversa) leads to an incorrect selection
 		if (itStart !== -1 && itEnd === -1) {
-			itEnd = this._tickMap.getMaxTickIdx() - 1;
+			itEnd = this._headerInfo.getMaxIndex();
 		}
 
 		this._selection.start = itStart;
@@ -240,24 +243,24 @@ L.Control.Header = L.Control.extend({
 	// Called whenever the cell cursor is in a cell corresponding to the cursorPos-th
 	// column/row.
 	updateCurrent: function (cursorPos, slim) {
-		if (!this._tickMap) {return;}
+		if (!this._headerInfo) {return;}
 
 		if (cursorPos < 0) {
-			this.unselect(this._tickMap.getGap(this._current));
+			this.unselect(this._headerInfo.getElementData(this._current));
 			this._current = -1;
 			return;
 		}
 
-		var prevEntry = cursorPos > 0 ? this._tickMap.getGap(cursorPos - 1) : null;
+		var prevEntry = cursorPos > 0 ? this._headerInfo.getPreviousIndex(cursorPos) : null;
 		var zeroSizeEntry = slim && prevEntry && prevEntry.size === 0;
 
-		var entry = this._tickMap.getGap(cursorPos);
+		var entry = this._headerInfo.getElementData(cursorPos);
 		if (this._selection.start === -1 && this._selection.end === -1) {
 			// when a whole row (column) is selected the cell cursor is moved to the first column (row)
 			// but this action should not cause to select/unselect anything, on the contrary we end up
 			// with all column (row) header entries selected but the one where the cell cursor was
 			// previously placed
-			this.unselect(this._tickMap.getGap(this._current));
+			this.unselect(this._headerInfo.getElementData(this._current));
 			// no selection when the cell cursor is slim
 			if (entry && !zeroSizeEntry)
 				this.select(entry, true);
@@ -280,22 +283,23 @@ L.Control.Header = L.Control.extend({
 	},
 
 	_entryAtPoint: function(point) {
-		if (!this._tickMap)
+		if (!this._headerInfo)
 			return false;
 
 		var position = this._getParallelPos(point);
-		position = position - this._position;
 
 		var that = this;
 		var result = null;
-		this._tickMap.forEachGap(function(gap) {
-			if (position >= gap.start && position < gap.end) {
-				var resizeAreaStart = Math.max(gap.start, gap.end - 3);
-				if (that.isHeaderSelected(gap.index)) {
-					resizeAreaStart = gap.end - that._resizeHandleSize;
+		this._headerInfo.forEachElement(function(entry) {
+			var end = entry.pos;
+			var start = end - entry.size;
+			if (position >= start && position < end) {
+				var resizeAreaStart = Math.max(start, end - 3);
+				if (that.isHeaderSelected(entry.index)) {
+					resizeAreaStart = end - that._resizeHandleSize;
 				}
 				var isMouseOverResizeArea = (position > resizeAreaStart);
-				result = {entry: gap, hit: isMouseOverResizeArea};
+				result = {entry: entry, hit: isMouseOverResizeArea};
 				return true;
 			}
 		});
@@ -325,6 +329,7 @@ L.Control.Header = L.Control.extend({
 		this._start = new L.Point(rectangle.left, rectangle.top);
 		this._offset = new L.Point(rectangle.right - event.center.x, rectangle.bottom - event.center.y);
 		this._item = target;
+		this._dragEntry = result.entry;
 		this.onDragStart(this._item, this._start, this._offset, {clientX: event.center.x, clientY: event.center.y});
 	},
 
@@ -337,6 +342,7 @@ L.Control.Header = L.Control.extend({
 		L.DomUtil.enableTextSelection();
 
 		this.onDragEnd(this._item, this._start, this._offset, {clientX: event.center.x, clientY: event.center.y});
+		this._dragEntry = null;
 
 		this._mouseOverEntry = null;
 		this._item = this._start = this._offset = null;
@@ -402,7 +408,7 @@ L.Control.Header = L.Control.extend({
 			entry = result.entry;
 		}
 
-		if (mouseOverIndex && (!this._mouseOverEntry || mouseOverIndex !== this._mouseOverEntry.index)) {
+		if (typeof mouseOverIndex === 'number' && (!this._mouseOverEntry || mouseOverIndex !== this._mouseOverEntry.index)) {
 			var mouseOverIsCurrent = false;
 			if (this._mouseOverEntry != null) {
 				mouseOverIsCurrent = (this._mouseOverEntry.index == this._current);
@@ -456,8 +462,8 @@ L.Control.Header = L.Control.extend({
 		if (!group)
 			return false;
 
-		var pos = this._getParallelPos(this._mouseEventToCanvasPos(this._canvas, e));
-		pos = pos - this._position;
+		var pos = this._headerInfo.headerToDocPos(
+			this._getParallelPos(this._mouseEventToCanvasPos(this._canvas, e)));
 		if (group.startPos < pos && pos < group.startPos + this._groupHeadSize) {
 			this._updateOutlineState(/*isColumnOutline: */ this._isColumn, group);
 			return true;
@@ -474,8 +480,8 @@ L.Control.Header = L.Control.extend({
 		if (!group && !group.hidden)
 			return false;
 
-		var pos = this._getParallelPos(this._mouseEventToCanvasPos(this._canvas, e));
-		pos = pos - this._position;
+		var pos = this._headerInfo.headerToDocPos(
+			this._getParallelPos(this._mouseEventToCanvasPos(this._canvas, e)));
 		if (group.startPos + this._groupHeadSize < pos && pos < group.endPos) {
 			this._updateOutlineState(/*isColumnOutline: */ this._isColumn, group);
 			return true;
@@ -525,6 +531,7 @@ L.Control.Header = L.Control.extend({
 		this._start = new L.Point(rect.left, rect.top);
 		this._offset = new L.Point(rect.right - e.clientX, rect.bottom - e.clientY);
 		this._item = target;
+		this._dragEntry = this._mouseOverEntry;
 
 		this.onDragStart(this._item, this._start, this._offset, e);
 	},
@@ -561,6 +568,7 @@ L.Control.Header = L.Control.extend({
 
 		this._item = this._start = this._offset = null;
 		this._dragging = false;
+		this._dragEntry = null;
 	},
 
 	_twipsToPixels: function (twips) {
@@ -771,165 +779,208 @@ L.Control.Header = L.Control.extend({
 L.Control.Header.rowHeaderWidth = undefined;
 L.Control.Header.colHeaderHeight = undefined;
 
-/*
- * GapTickMap is a data structure for handling the dimensions of each
- * column/row in the column/row header.
- *
- * A "tick" is the position of the boundary between two cols/rows, a "gap" is
- * the space between two ticks - the position and width/height of a col/row.
- *
- * Data about ticks is 0-indexed: the first tick (top of row 1 / left of column A) is 0.
- *
- * Data about gaps is 1-indexed: The 1st gap (row 1 / column A) is 1, and spans
- * from tick 0 to tick 1.
- *
- * A GapTickMap is fed data coming from a 'viewrowcolumnheaders' UNO message.
- * That contains the position of some of the ticks. The rest of the tick positions
- * is extrapolated as follows:
- * - The first two ticks are assumed to be consecutive. This sets the size of
- *   the first gap.
- * - If the position of n-th tick is not explicit, then its position is the (n-1)-th tick plus
- *   the size of the last known gap.
- * - When a new tick position is defined, it resets the size of the last known gap
- *
- * All inputs received are given in tile pixels, and stored as such.
- * outputs are returned in CSS pixels.
- *
- * **NB.** CSS pixels are scaled (down) from the tile pixels by the a factor
- * from TileLayer - 2x for retina, 1x for non-retina.
- *
- * **NB.** twip to pixel mapping is made non-obvious by the need to ensure that
- * there are no cumulative rounding errors from TWIP heights to pixels. We have to
- * match the core here, so we just use pixels.
- */
-L.Control.Header.GapTickMap = L.Class.extend({
-
-	initialize: function (map, ticks, dimensionGeometry) {
-
-		if (dimensionGeometry) {
-			// Until .uno:ViewRowColumnHeaders is not phased out, we need to live with
-			// GapTickMap datastructure to avoid an invasive refactoring.
-			// L.SheetGeometry and L.SheetDimension datastructures can directly provide
-			// position/size of any row/column intuitively without using unnecessary
-			// terminologies like (1-based) Gap and (0-based) Tick.
-			var dimrange = dimensionGeometry.getViewElementRange();
-			var start = Math.max(0, dimrange.start - 2);
-			var startData = dimensionGeometry.getElementData(start);
-			var startText = start ? start + 1 : 0;
-			var endText = Math.min(dimensionGeometry.getMaxIndex(), dimrange.end + 2) + 1;
-
-			this._minTickIdx = startText;
-			this._maxTickIdx = endText;
-			this._startOffset = start ? startData.startpos + startData.size : 0;
-			this._tilePixelScale = 1; // We already have everything in css px.
-
-			ticks = start ? [] : [0];
-			dimensionGeometry.forEachInRange(start,
-				this._maxTickIdx - 1, function (idx, data) {
-					ticks[idx + 1] = data.startpos + data.size;
-				});
-
-			this._ticks = ticks;
-
-			return;
+L.Control.Header.HeaderInfo = L.Class.extend({
+
+	initialize: function (map, isCol) {
+		console.assert(map && isCol !== undefined, 'map and isCol required');
+		this._map = map;
+		this._isCol = isCol;
+		console.assert(this._map._docLayer.sheetGeometry, 'no sheet geometry data-structure found!');
+		var sheetGeom = this._map._docLayer.sheetGeometry;
+		this._dimGeom = this._isCol ? sheetGeom.getColumnsGeometry() : sheetGeom.getRowsGeometry();
+		this.update();
+	},
+
+	update: function () {
+		var bounds = this._map.getPixelBounds();
+		var startPx = this._isCol ? bounds.getTopLeft().x : bounds.getTopLeft().y;
+		this._docVisStart = startPx;
+		var endPx = this._isCol ? bounds.getBottomRight().x : bounds.getBottomRight().y;
+		var startIdx = this._dimGeom.getIndexFromPos(startPx, 'csspixels');
+		var endIdx = this._dimGeom.getIndexFromPos(endPx - 1, 'csspixels');
+		this._elements = [];
+
+		var splitPosContext = this._map.getSplitPanesContext();
+
+		this._hasSplits = false;
+		this._splitIndex = 0;
+		var splitPos = 0;
+
+		if (splitPosContext) {
+
+			splitPos = this._isCol ? splitPosContext.getSplitPos().x : splitPosContext.getSplitPos().y;
+			var splitIndex = this._dimGeom.getIndexFromPos(splitPos + 1, 'csspixels');
+
+			if (splitIndex) {
+				// Make sure splitPos is aligned to the cell boundary.
+				splitPos = this._dimGeom.getElementData(splitIndex).startpos;
+				this._splitPos = splitPos;
+				this._dimGeom.forEachInRange(0, splitIndex - 1,
+					function (idx, data) {
+						this._elements[idx] = {
+							index: idx,
+							pos: data.startpos + data.size, // end position on the header canvas
+							size: data.size,
+							origsize: data.size,
+						};
+					}.bind(this)
+				);
+
+				this._hasSplits = true;
+				this._splitIndex = splitIndex;
+
+				var freeStartPos = startPx + splitPos + 1;
+				var freeStartIndex = this._dimGeom.getIndexFromPos(freeStartPos + 1, 'csspixels');
+
+				startIdx = freeStartIndex;
+			}
 		}
 
-		var gapSize;
-		this._ticks = [];
+		// first free index
+		var dataFirstFree = this._dimGeom.getElementData(startIdx);
+		var firstFreeEnd = dataFirstFree.startpos + dataFirstFree.size - startPx;
+		var firstFreeStart = splitPos;
+		var firstFreeSize = Math.max(0, firstFreeEnd - firstFreeStart);
+		this._elements[startIdx] = {
+			index: startIdx,
+			pos: firstFreeEnd, // end position on the header canvas
+			size: firstFreeSize,
+			origsize: dataFirstFree.size,
+		};
 
-		// Sanitize input
-		var knownTicks = [];
-		for (var i in ticks) {
-			// The field in the input data struct is called "text" but it's the
-			// column/row index, as a string.
-			// Idem for "size": it's the tick position in pixels, as a string
-			knownTicks[ parseInt(ticks[i].text) ] = parseInt(ticks[i].size);
-		}
+		this._dimGeom.forEachInRange(startIdx + 1,
+			endIdx, function (idx, data) {
+				var startpos = data.startpos - startPx;
+				var endpos = startpos + data.size;
+				var size = endpos - startpos;
+				this._elements[idx] = {
+					index: idx,
+					pos: endpos, // end position on the header canvas
+					size: size,
+					origsize: size,
+				};
+			}.bind(this));
 
-		// This *assumes* the input is ordered - i.e. tick indexes only grow
-		this._minTickIdx = parseInt(ticks[0].text);
-		this._maxTickIdx = knownTicks.length - 1;
-		this._startOffset = parseInt(ticks[0].size);
-		this._tilePixelScale = map._docLayer._tilePixelScale;
+		this._startIndex = startIdx;
+		this._endIndex = endIdx;
+	},
 
-		for (var idx=this._minTickIdx; idx <= this._maxTickIdx; idx++) {
+	docToHeaderPos: function (docPos) {
 
-			if (idx in knownTicks) {
-				this._ticks[idx] = knownTicks[idx];
-				gapSize = this._ticks[idx] - this._ticks[idx - 1];
-			} else {
-				if (isNaN(gapSize) || gapSize < 0) {
-					// This should never happen, unless data from the UNO message
-					// is not in strictly increasing order, or the first two ticks
-					// are not consecutive.
-					throw new Error('Malformed data for column/row sizes.');
-				}
-				this._ticks[idx] = this._ticks[idx - 1] + gapSize;
-			}
+		if (!this._hasSplits) {
+			return docPos - this._docVisStart;
 		}
+
+		if (docPos <= this._splitPos) {
+			return docPos;
+		}
+
+		// max here is to prevent encroachment of the fixed pane-area.
+		return Math.max(docPos - this._docVisStart, this._splitPos);
 	},
 
-	// Gets the position of the i-th tick (or `undefined` if the index falls outside).
-	getTick: function getTick(i) {
-		// to get CSS pixels we need to adjust for DPI scale
-		// since we render at full native pixel resolution &
-		// account in those units.
-		return this._ticks[i] / this._tilePixelScale;
+	headerToDocPos: function (hdrPos) {
+		if (!this._hasSplits) {
+			return hdrPos + this._docVisStart;
+		}
+
+		if (hdrPos <= this._splitPos) {
+			return hdrPos;
+		}
+
+		return hdrPos + this._docVisStart;
 	},
 
 	getStartOffset: function() {
-		return this._startOffset / this._tilePixelScale;
+		return 0;
 	},
 
-	getMinTickIdx: function() {
-		return this._minTickIdx;
+	isZeroSize: function (i) {
+		var elem = this._elements[i];
+		console.assert(elem, 'queried a non existent row/col in the header : ' + i);
+		return elem.size === 0;
 	},
-	getMaxTickIdx: function() {
-		return this._maxTickIdx;
+
+	hasSplits: function () {
+		return this._hasSplits;
 	},
 
-	// Gets the start and size of the i-th gap.
-	// returns an Object of the form {index: i, pos: start, size: width/height },
-	// or `undefined` if the index falls outside.
-	getGap: function getGap(i) {
-		var start = this.getTick(i-1);
-		var end = this.getTick(i);
+	// Index after the split.
+	getSplitIndex: function () {
+		return this._splitIndex;
+	},
 
-		if (start === undefined || end === undefined || isNaN(start) || isNaN(end)) {
-			return undefined;
-		}
+	getStartIndex: function () {
+		return this._startIndex;
+	},
 
-		return {
-			index: i,
-			start: start,
-			end: end,
-			size: end - start,
-			pos: end,
-		};
+	getEndIndex: function () {
+		return this._endIndex;
 	},
 
-	// Returns true when the i-th gap has zero size.
-	isZeroSize: function isZeroSize(i) {
-		return this.getGap(i).size === 0;
+	getMinIndex: function () {
+		return this._hasSplits ? 0 : this._startIndex;
 	},
 
-	// Runs the given callback function for each tick
-	// The callback function receives two parameters: the tick index and the
-	// (interpolated) tick position
-	forEachTick: function forEachTick(callback) {
-		for (var idx=this._minTickIdx; idx <= this._maxTickIdx; idx++) {
-			if (callback(idx, this.getTick(idx)))
-				break;
+	getMaxIndex: function () {
+		return this._endIndex;
+	},
+
+	getElementData: function (index) {
+		return this._elements[index];
+	},
+
+	getRowData: function (index) {
+		console.assert(!this._isCol, 'this is a column header instance!');
+		return this.getElementData(index);
+	},
+
+	getColData: function (index) {
+		console.assert(this._isCol, 'this is a row header instance!');
+		return this.getElementData(index);
+	},
+
+	getPreviousIndex: function (index) {
+
+		var prevIndex;
+		if (this._splitIndex && index === this._startIndex) {
+			prevIndex = this._splitIndex - 1;
 		}
+		else {
+			prevIndex = index - 1;
+		}
+
+		return prevIndex;
 	},
 
-	// Runs the given callback function for each gap
-	// The callback receives one parameter, in the same format as the return value
-	// of getGap()
-	forEachGap: function forEachGap(callback) {
-		for (var idx=this._minTickIdx; idx < this._maxTickIdx; idx++) {
-			if (callback(this.getGap(idx+1)))
-				break;
+	getNextIndex: function (index) {
+
+		var nextIndex;
+		if (this._splitIndex && index === (this._splitIndex - 1)) {
+			nextIndex = this._startIndex;
+		}
+		else {
+			nextIndex = index + 1;
+		}
+
+		return nextIndex;
+	},
+
+	forEachElement: function (callback) {
+		var idx;
+		if (this._hasSplits) {
+			for (idx = 0; idx < this._splitIndex; ++idx) {
+				console.assert(this._elements[idx], 'forEachElement failed');
+				if (callback(this._elements[idx])) {
+					return;
+				}
+			}
+		}
+		for (idx = this._startIndex; idx <= this._endIndex; ++idx) {
+			console.assert(this._elements[idx], 'forEachElement failed');
+			if (callback(this._elements[idx])) {
+				return;
+			}
 		}
 	},
 
diff --git a/loleaflet/src/control/Control.RowHeader.js b/loleaflet/src/control/Control.RowHeader.js
index 820f2205b..2e55fc4b7 100644
--- a/loleaflet/src/control/Control.RowHeader.js
+++ b/loleaflet/src/control/Control.RowHeader.js
@@ -11,6 +11,7 @@ L.Control.RowHeader = L.Control.Header.extend({
 
 	onAdd: function (map) {
 		map.on('updatepermission', this._onUpdatePermission, this);
+		map.on('moveend zoomchanged sheetgeometrychanged splitposchanged', this._updateCanvas, this);
 		this._initialized = false;
 	},
 
@@ -152,6 +153,13 @@ L.Control.RowHeader = L.Control.Header.extend({
 		this._map.sendUnoCommand('.uno:ShowRow');
 	},
 
+	_updateCanvas: function () {
+		if (this._headerInfo) {
+			this._headerInfo.update();
+			this._redrawHeaders();
+		}
+	},
+
 	setScrollPosition: function (e) {
 		var position = -e.y;
 		this._position = Math.min(0, position);
@@ -179,7 +187,7 @@ L.Control.RowHeader = L.Control.Header.extend({
 	},
 
 	_onUpdateCurrentRow: function (e) {
-		var y = e.curY;
+		var y = e.curY - 1; // 1-based to 0-based.
 		var h = this._twipsToPixels(e.height);
 		var slim = h <= 1;
 		this.updateCurrent(y, slim);
@@ -194,10 +202,10 @@ L.Control.RowHeader = L.Control.Header.extend({
 			return;
 
 		var ctx = this._canvasContext;
-		var content = entry.index;
+		var content = entry.index + 1;
 		var startOrt = this._canvasWidth - this._headerWidth;
-		var startPar = entry.pos - entry.size - this._startOffset;
-		var endPar = entry.pos - this._startOffset;
+		var startPar = entry.pos - entry.size;
+		var endPar = entry.pos;
 		var height = endPar - startPar;
 		var width = this._headerWidth;
 
@@ -211,7 +219,6 @@ L.Control.RowHeader = L.Control.Header.extend({
 		ctx.save();
 		var scale = L.getDpiScaleFactor();
 		ctx.scale(scale, scale);
-		ctx.translate(0, this._position + this._startOffset);
 		// background gradient
 		var selectionBackgroundGradient = null;
 		if (isHighlighted) {
@@ -268,14 +275,13 @@ L.Control.RowHeader = L.Control.Header.extend({
 		var level = group.level;
 
 		var startOrt = spacing + (headSize + spacing) * level;
-		var startPar = group.startPos - this._startOffset;
+		var startPar = this._headerInfo.docToHeaderPos(group.startPos);
 		var height = group.endPos - group.startPos;
 
 		ctx.save();
 		var scale = L.getDpiScaleFactor();
 		ctx.scale(scale, scale);
 
-		ctx.translate(0, this._position + this._startOffset);
 		// clip mask
 		ctx.beginPath();
 		ctx.rect(startOrt, startPar, headSize, height);
@@ -347,15 +353,15 @@ L.Control.RowHeader = L.Control.Header.extend({
 		var entry = this._mouseOverEntry;
 
 		if (index)
-			entry = this._tickMap.getGap(index);
+			entry = this._headerInfo.getRowData(index);
 
 		if (!entry)
 			return;
 
 		var rect = this._canvas.getBoundingClientRect();
 
-		var rowStart = entry.pos - entry.size + this._position;
-		var rowEnd = entry.pos + this._position;
+		var rowStart = entry.pos - entry.size;
+		var rowEnd = entry.pos;
 
 		var left = rect.left;
 		var right = rect.right;
@@ -397,11 +403,12 @@ L.Control.RowHeader = L.Control.Header.extend({
 		}
 
 		var sheetGeometry = this._map._docLayer.sheetGeometry;
-		var rowsGeometry = sheetGeometry ? sheetGeometry.getRowsGeometry() : undefined;
 
-		// create data structure for row heights
-		this._tickMap = new L.Control.Header.GapTickMap(this._map, rows, rowsGeometry);
-		this._startOffset = this._tickMap.getStartOffset();
+		if (!this._headerInfo) {
+			// create data structure for row heights
+			this._headerInfo = new L.Control.Header.HeaderInfo(this._map, false /* isCol */);
+			this._map._rowHdr = this._headerInfo;
+		}
 
 		// setup conversion routine
 		this.converter = L.Util.bind(converter, context);
@@ -426,13 +433,7 @@ L.Control.RowHeader = L.Control.Header.extend({
 			this.resize(this._headerWidth);
 		}
 
-		// Initial draw
-		this._tickMap.forEachGap(function(gap) {
-			this.drawHeaderEntry(gap, false);
-		}.bind(this));
-
-		// draw group controls
-		this.drawOutline();
+		this._redrawHeaders();
 
 		this.mouseInit(canvas);
 
@@ -441,11 +442,21 @@ L.Control.RowHeader = L.Control.Header.extend({
 		}
 	},
 
+	_redrawHeaders: function () {
+		this._canvasContext.clearRect(0, 0, this._canvas.width, this._canvas.height);
+		this._headerInfo.forEachElement(function(elemData) {
+			this.drawHeaderEntry(elemData, false);
+		}.bind(this));
+
+		// draw group controls
+		this.drawOutline();
+	},
+
 	_selectRow: function(row, modifier) {
 		var command = {
 			Row: {
 				type: 'long',
-				value: row - 1
+				value: row
 			},
 			Modifier: {
 				type: 'unsigned short',
@@ -505,11 +516,18 @@ L.Control.RowHeader = L.Control.Header.extend({
 	},
 
 	_getHorzLatLng: function (start, offset, e) {
-		var limit = this._map.mouseEventToContainerPoint({clientX: start.x, clientY: start.y});
+		var size = this._map.getSize();
 		var drag = this._map.mouseEventToContainerPoint(e);
+		var entryStart = this._dragEntry.pos - this._dragEntry.size;
+		var ydocpos = this._headerInfo.headerToDocPos(Math.max(drag.y, entryStart));
+		var xmin = this._map.getPixelBounds().min.x;
+		var xmax = xmin + size.x;
+		if (this._headerInfo.hasSplits()) {
+			xmin = 0;
+		}
 		return [
-			this._map.containerPointToLatLng(new L.Point(0, Math.max(limit.y, drag.y + offset.y))),
-			this._map.containerPointToLatLng(new L.Point(this._map.getSize().x, Math.max(limit.y, drag.y + offset.y)))
+			this._map.unproject(new L.Point(xmin, ydocpos)),
+			this._map.unproject(new L.Point(xmax, ydocpos)),
 		];
 	},
 
@@ -539,8 +557,9 @@ L.Control.RowHeader = L.Control.Header.extend({
 			var height = clickedRow.size;
 			var row = clickedRow.index;
 
-			if (this._tickMap.isZeroSize(clickedRow.index + 1)) {
-				row += 1;
+			var nextRow = this._headerInfo.getNextIndex(clickedRow.index);
+			if (this._headerInfo.isZeroSize(nextRow)) {
+				row = nextRow;
 				height = 0;
 			}
 
@@ -552,7 +571,7 @@ L.Control.RowHeader = L.Control.Header.extend({
 					},
 					Row: {
 						type: 'long',
-						value: row
+						value: row + 1 // core expects 1-based index.
 					}
 				};
 
@@ -574,7 +593,7 @@ L.Control.RowHeader = L.Control.Header.extend({
 			var command = {
 				Row: {
 					type: 'long',
-					value: row - 1
+					value: row
 				},
 				Modifier: {
 					type: 'unsigned short',
diff --git a/loleaflet/src/layer/CalcGridLines.js b/loleaflet/src/layer/CalcGridLines.js
index 37492b606..fce802101 100644
--- a/loleaflet/src/layer/CalcGridLines.js
+++ b/loleaflet/src/layer/CalcGridLines.js
@@ -72,23 +72,17 @@ L.CalcGridLines = L.LayerGroup.extend({
 	// Redraw col/row lines whenever new information about them is available.
 	// One websocket message might have info about cols, rows, or both
 	onUpdate: function onUpdate(ev) {
-		var ticks;
+		var headerInfo, pos;
 
-		// Aux stuff to scale twips from the websocket message
-		// into map coordinate units
+		// Aux stuff to convert css pixels to map coordinate units
 		var pixelToMapUnitRatio = this._map.options.crs.scale(this._map.getZoom());
 
-		var colDataInEvent = ev.data && ev.data.columns && ev.data.columns.length;
-		var rowDataInEvent = ev.data && ev.data.rows && ev.data.rows.length;
-
-		if (colDataInEvent || ev.updatecolumns) {
-			var columnsData = colDataInEvent ? ev.data.columns : undefined;
-			var columnsGeometry = colDataInEvent ? undefined : this._map._docLayer.sheetGeometry.getColumnsGeometry();
-			ticks = new L.Control.Header.GapTickMap(this._map, columnsData, columnsGeometry);
+		if (ev.updatecolumns) {
+			headerInfo = new L.Control.Header.HeaderInfo(this._map, true /* isCol */);
 			this._colLines.clearLayers();
 
-			ticks.forEachTick(function(idx, pos) {
-				pos /= pixelToMapUnitRatio;
+			headerInfo.forEachElement(function(columnData) {
+				pos = headerInfo.headerToDocPos(columnData.pos) / pixelToMapUnitRatio;
 				this._colLines.addLayer(
 					L.polyline([[[ L.Util.MIN_SAFE_INTEGER, pos ],[ L.Util.MAX_SAFE_INTEGER, pos ]]],
 						this.options
@@ -97,14 +91,12 @@ L.CalcGridLines = L.LayerGroup.extend({
 			}.bind(this));
 		}
 
-		if (rowDataInEvent || ev.updaterows) {
-			var rowsData = rowDataInEvent ? ev.data.rows : undefined;
-			var rowsGeometry = rowDataInEvent ? undefined : this._map._docLayer.sheetGeometry.getRowsGeometry();
-			ticks = new L.Control.Header.GapTickMap(this._map, rowsData, rowsGeometry);
+		if (ev.updaterows) {
+			headerInfo = new L.Control.Header.HeaderInfo(this._map, false /* isCol */);
 			this._rowLines.clearLayers();
 
-			ticks.forEachTick(function(idx, pos) {
-				pos /= pixelToMapUnitRatio;
+			headerInfo.forEachElement(function(rowData) {
+				pos = headerInfo.headerToDocPos(rowData.pos) / pixelToMapUnitRatio;
 				this._rowLines.addLayer(
 					// Note that y-coordinates are inverted: Leaflet's CRS.Simple assumes
 					// down = negative latlngs, whereas loolkit assumes down = positive twips
diff --git a/loleaflet/src/layer/tile/CalcTileLayer.js b/loleaflet/src/layer/tile/CalcTileLayer.js
index 4ff7b51ba..9d711b48f 100644
--- a/loleaflet/src/layer/tile/CalcTileLayer.js
+++ b/loleaflet/src/layer/tile/CalcTileLayer.js
@@ -449,6 +449,7 @@ L.CalcTileLayer = L.TileLayer.extend({
 		}
 		this._restrictDocumentSize();
 		this._replayPrintTwipsMsgs();
+		this._map.fire('zoomchanged');
 		this.refreshViewData();
 		this._map._socket.sendMessage('commandvalues command=.uno:ViewAnnotationsPosition');
 	},
@@ -719,6 +720,8 @@ L.CalcTileLayer = L.TileLayer.extend({
 			this._pixelsToTwips(this._map.getSize()));
 		this._updateHeadersGridLines(undefined, true /* updateCols */,
 			true /* updateRows */);
+
+		this._map.fire('sheetgeometrychanged');
 	},
 
 	_onCommandValuesMsg: function (textMsg) {
commit b15f641297b44edc6e8dece4b61fd378a5170f76
Author:     Tamás Zolnai <tamas.zolnai at collabora.com>
AuthorDate: Tue Jul 7 12:28:33 2020 +0200
Commit:     Tamás Zolnai <tamas.zolnai at collabora.com>
CommitDate: Wed Jul 8 17:00:27 2020 +0200

    cypress: introduce clickOnIdle() and inputOnIdle() methods.
    
    This method waits until the item is idle,
    e.g. not detached for a while. Using this we can
    workaround false failures caused by GUI flickering.
    For example, mobile wizard is updated all the time
    which makes hard to test it reliably. We can use
    this clickOnIdle() method, which is slower than the
    simple click(), but is more reliable.
    
    Change-Id: I2f970eb0cf400382c8384c91ab7c84b1e02e63af
    Reviewed-on: https://gerrit.libreoffice.org/c/online/+/98373
    Tested-by: Jenkins
    Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoffice at gmail.com>
    Reviewed-by: Tamás Zolnai <tamas.zolnai at collabora.com>

diff --git a/cypress_test/integration_tests/common/helper.js b/cypress_test/integration_tests/common/helper.js
index baafe49fb..e23a9b40f 100644
--- a/cypress_test/integration_tests/common/helper.js
+++ b/cypress_test/integration_tests/common/helper.js
@@ -1,4 +1,6 @@
-/* global cy Cypress expect */
+/* global cy Cypress expect require */
+
+require('cypress-wait-until');
 
 function loadTestDoc(fileName, subFolder, mobile) {
 	cy.log('Loading test document - start.');
@@ -296,6 +298,94 @@ function imageShouldBeFullWhiteOrNot(selector, fullWhite = true) {
 		});
 }
 
+function waitUntilIdle(selector, content) {
+	cy.log('Waiting item to be idle - start.');
+
+	var item;
+	var waitingTime = 1000;
+	if (content) {
+		cy.contains(selector, content, { log: false })
+			.then(function(itemToIdle) {
+				item = itemToIdle;
+			});
+
+		cy.waitUntil(function() {
+			cy.wait(waitingTime);
+
+			return cy.contains(selector, content, { log: false })
+				.then(function(itemToIdle) {
+					if (Cypress.dom.isDetached(item[0])) {
+						cy.log('Item is detached.');
+						item = itemToIdle;
+						return false;
+					} else {
+						return true;
+					}
+				});
+		});
+	} else {
+		cy.get(selector, { log: false })
+			.then(function(itemToIdle) {
+				item = itemToIdle;
+			});
+
+		cy.waitUntil(function() {
+			cy.wait(waitingTime);
+
+			return cy.get(selector, { log: false })
+				.then(function(itemToIdle) {
+					if (Cypress.dom.isDetached(item[0])) {
+						cy.log('Item is detached.');
+						item = itemToIdle;
+						return false;
+					} else {
+						return true;
+					}
+				});
+		});
+	}
+
+	cy.log('Waiting item to be idle - end.');
+}
+
+// This is a workaround for avoid 'item detached from DOM'
+// failures caused by GUI flickering.
+// GUI flickering might mean bad design, but
+// until it's fixed we can use this method.
+// Known GUI flickering:
+// * mobile wizard
+// IMPORTANT: don't use this if there is no
+// flickering. Use simple click() instead. This method
+// is much slower.
+function clickOnIdle(selector, content) {
+	cy.log('Clicking on item when idle - start.');
+
+	waitUntilIdle(selector, content);
+	if (content) {
+		cy.contains(selector, content)
+			.click();
+	} else {
+		cy.get(selector)
+			.click();
+	}
+
+	cy.log('Clicking on item when idle - end.');
+}
+
+// See comments at clickOnIdle() method.
+function inputOnIdle(selector, input) {
+	cy.log('Type into an input item when idle - start.');
+
+	waitUntilIdle(selector);
+
+	cy.get(selector)
+		.clear()
+		.type(input)
+		.type('{enter}');
+
+	cy.log('Type into an input item when idle - end.');
+}
+
 module.exports.loadTestDoc = loadTestDoc;
 module.exports.assertCursorAndFocus = assertCursorAndFocus;
 module.exports.assertNoKeyboardInput = assertNoKeyboardInput;
@@ -314,3 +404,5 @@ module.exports.beforeAllDesktop = beforeAllDesktop;
 module.exports.typeText = typeText;
 module.exports.getLOVersion = getLOVersion;
 module.exports.imageShouldBeFullWhiteOrNot = imageShouldBeFullWhiteOrNot;
+module.exports.clickOnIdle = clickOnIdle;
+module.exports.inputOnIdle = inputOnIdle;
diff --git a/cypress_test/integration_tests/common/mobile_helper.js b/cypress_test/integration_tests/common/mobile_helper.js
index 054a87d41..b14113d02 100644
--- a/cypress_test/integration_tests/common/mobile_helper.js
+++ b/cypress_test/integration_tests/common/mobile_helper.js
@@ -114,12 +114,6 @@ function openMobileWizard() {
 	cy.get('#tb_actionbar_item_mobile_wizard table')
 		.should('have.class', 'checked');
 
-	// Mobile wizard is requested twice on opening
-	// The second request is sent after a 400 ms delay
-	// see _refreshSidebar() method. So let's just wait
-	// until mobile wizard gets it's final state.
-	cy.wait(1500);
-
 	cy.log('Opening mobile wizard - end.');
 }
 
diff --git a/cypress_test/integration_tests/mobile/calc/alignment_options_spec.js b/cypress_test/integration_tests/mobile/calc/alignment_options_spec.js
index 4ed008fe4..344ef8a36 100644
--- a/cypress_test/integration_tests/mobile/calc/alignment_options_spec.js
+++ b/cypress_test/integration_tests/mobile/calc/alignment_options_spec.js
@@ -44,8 +44,7 @@ describe('Change alignment settings.', function() {
 
 		mobileHelper.openMobileWizard();
 
-		cy.get('#ScAlignmentPropertyPanel')
-			.click();
+		helper.clickOnIdle('#ScAlignmentPropertyPanel');
 
 		cy.get('#AlignLeft')
 			.should('be.visible');
@@ -55,8 +54,7 @@ describe('Change alignment settings.', function() {
 		openAlignmentPaneForFirstCell();
 
 		// Set right aligment first
-		cy.get('#AlignRight')
-			.click();
+		helper.clickOnIdle('#AlignRight');
 
 		calcMobileHelper.selectAllMobile();
 
@@ -68,11 +66,9 @@ describe('Change alignment settings.', function() {
 
 		mobileHelper.openMobileWizard();
 
-		cy.get('#ScAlignmentPropertyPanel')
-			.click();
+		helper.clickOnIdle('#ScAlignmentPropertyPanel');
 
-		cy.get('#AlignLeft')
-			.click();
+		helper.clickOnIdle('#AlignLeft');
 
 		calcMobileHelper.selectAllMobile();
 
@@ -83,8 +79,7 @@ describe('Change alignment settings.', function() {
 	it('Align to center horizontally.', function() {
 		openAlignmentPaneForFirstCell();
 
-		cy.get('#AlignHorizontalCenter')
-			.click();
+		helper.clickOnIdle('#AlignHorizontalCenter');
 
 		calcMobileHelper.selectAllMobile();
 
@@ -95,8 +90,7 @@ describe('Change alignment settings.', function() {
 	it('Change to block alignment.', function() {
 		openAlignmentPaneForFirstCell();
 
-		cy.get('#AlignBlock')
-			.click();
+		helper.clickOnIdle('#AlignBlock');
 
 		calcMobileHelper.selectAllMobile();
 
@@ -107,16 +101,13 @@ describe('Change alignment settings.', function() {
 	it('Right-to-left and left-to-right writing mode.', function() {
 		openAlignmentPaneForFirstCell();
 
-		cy.get('#ParaRightToLeft')
-			.click();
+		helper.clickOnIdle('#ParaRightToLeft');
 
 		// TODO: we don't have a way of testing this
 		// copy container doesn't have info about this
 		cy.wait(500);
 
-		// Set right aligment first
-		cy.get('#ParaLeftToRight')
-			.click();
+		helper.clickOnIdle('#ParaLeftToRight');
 
 		cy.wait(500);
 	});
@@ -124,8 +115,7 @@ describe('Change alignment settings.', function() {
 	it('Align to the top and to bottom.', function() {
 		openAlignmentPaneForFirstCell();
 
-		cy.get('#AlignTop')
-			.click();
+		helper.clickOnIdle('#AlignTop');
 
 		calcMobileHelper.selectAllMobile();
 
@@ -137,11 +127,9 @@ describe('Change alignment settings.', function() {
 
 		mobileHelper.openMobileWizard();
 
-		cy.get('#ScAlignmentPropertyPanel')
-			.click();
+		helper.clickOnIdle('#ScAlignmentPropertyPanel');
 
-		cy.get('#AlignBottom')
-			.click();
+		helper.clickOnIdle('#AlignBottom');
 
 		calcMobileHelper.selectAllMobile();
 
@@ -152,8 +140,7 @@ describe('Change alignment settings.', function() {
 	it('Align to center vertically.', function() {
 		openAlignmentPaneForFirstCell();
 
-		cy.get('#AlignVCenter')
-			.click();
+		helper.clickOnIdle('#AlignVCenter');
 
 		calcMobileHelper.selectAllMobile();
 
@@ -175,8 +162,7 @@ describe('Change alignment settings.', function() {
 		openAlignmentPaneForFirstCell();
 
 		// Increase indent
-		cy.get('#IncrementIndent')
-			.click();
+		helper.clickOnIdle('#IncrementIndent');
 
 		// We use the text position as indicator
 		cy.get('body')
@@ -203,8 +189,7 @@ describe('Change alignment settings.', function() {
 		// Decrease indent
 		openAlignmentPaneForFirstCell();
 
-		cy.get('#DecrementIndent')
-			.click();
+		helper.clickOnIdle('#DecrementIndent');
 
 		// We use the text position as indicator
 		cy.get('body')
@@ -235,13 +220,11 @@ describe('Change alignment settings.', function() {
 		openAlignmentPaneForFirstCell();
 
 		// TODO: First we need to increase indent to make the input enabled
-		cy.get('#IncrementIndent')
-			.click();
+		helper.clickOnIdle('#IncrementIndent');
 
 		cy.wait(300);
 
-		cy.get('#IncrementIndent')
-			.click();
+		helper.clickOnIdle('#IncrementIndent');
 
 		calcMobileHelper.removeTextSelection();
 
@@ -285,8 +268,7 @@ describe('Change alignment settings.', function() {
 		cy.get('input#wraptext')
 			.should('not.have.prop', 'checked', true);
 
-		cy.get('input#wraptext')
-			.click();
+		helper.clickOnIdle('input#wraptext');
 
 		cy.get('input#wraptext')
 			.should('have.prop', 'checked', true);
@@ -312,8 +294,7 @@ describe('Change alignment settings.', function() {
 		cy.get('input#stacked')
 			.should('not.have.prop', 'checked', true);
 
-		cy.get('input#stacked')
-			.click();
+		helper.clickOnIdle('input#stacked');
 
 		cy.get('input#stacked')
 			.should('have.prop', 'checked', true);
@@ -333,8 +314,7 @@ describe('Change alignment settings.', function() {
 
 		mobileHelper.openMobileWizard();
 
-		cy.get('#ScAlignmentPropertyPanel')
-			.click();
+		helper.clickOnIdle('#ScAlignmentPropertyPanel');
 
 		cy.get('#AlignLeft')
 			.should('be.visible');
@@ -346,8 +326,7 @@ describe('Change alignment settings.', function() {
 		cy.get('input#mergecells')
 			.should('not.have.prop', 'checked', true);
 
-		cy.get('input#mergecells')
-			.click();
+		helper.clickOnIdle('input#mergecells');
 
 		cy.get('input#mergecells')
 			.should('have.prop', 'checked', true);
diff --git a/cypress_test/integration_tests/mobile/calc/apply_font_spec.js b/cypress_test/integration_tests/mobile/calc/apply_font_spec.js
index dbe8766bf..3f717edb3 100644
--- a/cypress_test/integration_tests/mobile/calc/apply_font_spec.js
+++ b/cypress_test/integration_tests/mobile/calc/apply_font_spec.js
@@ -23,8 +23,7 @@ describe('Apply font changes.', function() {
 		mobileHelper.openMobileWizard();
 
 		// Open character properties
-		cy.get('#TextPropertyPanel')
-			.click();
+		helper.clickOnIdle('#TextPropertyPanel');
 
 		cy.get('#Bold')
 			.should('be.visible');
@@ -35,8 +34,7 @@ describe('Apply font changes.', function() {
 	});
 
 	it('Apply bold.', function() {
-		cy.get('#Bold')
-			.click();
+		helper.clickOnIdle('#Bold');
 
 		calcMobileHelper.selectAllMobile();
 
@@ -45,8 +43,7 @@ describe('Apply font changes.', function() {
 	});
 
 	it('Apply italic.', function() {
-		cy.get('#Italic')
-			.click();
+		helper.clickOnIdle('#Italic');
 
 		calcMobileHelper.selectAllMobile();
 
@@ -55,8 +52,7 @@ describe('Apply font changes.', function() {
 	});
 
 	it('Apply underline.', function() {
-		cy.get('#Underline')
-			.click();
+		helper.clickOnIdle('#Underline');
 
 		calcMobileHelper.selectAllMobile();
 
@@ -65,8 +61,7 @@ describe('Apply font changes.', function() {
 	});
 
 	it('Apply strikeout.', function() {
-		cy.get('#Strikeout')
-			.click();
+		helper.clickOnIdle('#Strikeout');
 
 		calcMobileHelper.selectAllMobile();
 
@@ -75,8 +70,7 @@ describe('Apply font changes.', function() {
 	});
 
 	it('Apply shadowed.', function() {
-		cy.get('#Shadowed')
-			.click();
+		helper.clickOnIdle('#Shadowed');
 
 		calcMobileHelper.selectAllMobile();
 
@@ -85,17 +79,14 @@ describe('Apply font changes.', function() {
 
 	it('Apply font name.', function() {
 		// Change font name
-		cy.get('#fontnamecombobox')
-			.click();
+		helper.clickOnIdle('#fontnamecombobox');
 
-		cy.contains('.mobile-wizard.ui-combobox-text', 'Linux Libertine G')
-			.click();
+		helper.clickOnIdle('.mobile-wizard.ui-combobox-text', 'Linux Libertine G');
 
 		cy.get('.level-1[title="Font Name"] .mobile-wizard.ui-combobox-text.selected')
 			.should('have.text', 'Linux Libertine G');
 
-		cy.get('#mobile-wizard-back')
-			.click();
+		helper.clickOnIdle('#mobile-wizard-back');
 
 		// Combobox entry contains the selected font name
 		cy.get('#fontnamecombobox .ui-header-right .entry-value')
@@ -109,11 +100,9 @@ describe('Apply font changes.', function() {
 
 	it('Apply font size.', function() {
 		// Change font size
-		cy.get('#fontsizecombobox')
-			.click();
+		helper.clickOnIdle('#fontsizecombobox');
 
-		cy.contains('.mobile-wizard.ui-combobox-text', '14')
-			.click();
+		helper.clickOnIdle('.mobile-wizard.ui-combobox-text', '14');
 
 		if (helper.getLOVersion() === 'master')
 			cy.get('.level-1[title="Font Size"] .mobile-wizard.ui-combobox-text.selected')
@@ -122,8 +111,7 @@ describe('Apply font changes.', function() {
 			cy.get('.level-1[title="Font Size"] .mobile-wizard.ui-combobox-text.selected')
 				.should('have.text', '14');
 
-		cy.get('#mobile-wizard-back')
-			.click();
+		helper.clickOnIdle('#mobile-wizard-back');
 
 		// Combobox entry contains the selected font name
 		cy.get('#fontsizecombobox .ui-header-right .entry-value')
@@ -136,9 +124,7 @@ describe('Apply font changes.', function() {
 	});
 
 	it('Apply grow.', function() {
-		// Push grow
-		cy.get('#Grow')
-			.click();
+		helper.clickOnIdle('#Grow');
 
 		calcMobileHelper.selectAllMobile();
 
@@ -147,9 +133,7 @@ describe('Apply font changes.', function() {
 	});
 
 	it('Apply shrink.', function() {
-		// Push shrink
-		cy.get('#Shrink')
-			.click();
+		helper.clickOnIdle('#Shrink');
 
 		calcMobileHelper.selectAllMobile();
 
@@ -158,9 +142,7 @@ describe('Apply font changes.', function() {
 	});
 
 	it('Apply font color.', function() {
-		// Change font color
-		cy.get('#Color')
-			.click();
+		helper.clickOnIdle('#Color');
 
 		mobileHelper.selectFromColorPalette(0, 5);
 
diff --git a/cypress_test/integration_tests/mobile/calc/calc_mobile_helper.js b/cypress_test/integration_tests/mobile/calc/calc_mobile_helper.js
index ab21e53a2..229c0be18 100644
--- a/cypress_test/integration_tests/mobile/calc/calc_mobile_helper.js
+++ b/cypress_test/integration_tests/mobile/calc/calc_mobile_helper.js
@@ -20,9 +20,6 @@ function removeTextSelection() {
 		cy.get('.spreadsheet-cell-resize-marker')
 			.invoke('attr', 'style')
 			.should('contain', '-8px,');
-
-		cy.get('input#addressInput')
-			.should('have.prop', 'value', 'B1:B1048576');
 	}
 
 	cy.log('Removing text selection - end.');
diff --git a/cypress_test/integration_tests/mobile/calc/cell_appearance_spec.js b/cypress_test/integration_tests/mobile/calc/cell_appearance_spec.js
index 3ca11f1ff..b28013450 100644
--- a/cypress_test/integration_tests/mobile/calc/cell_appearance_spec.js
+++ b/cypress_test/integration_tests/mobile/calc/cell_appearance_spec.js
@@ -22,8 +22,7 @@ describe('Change cell appearance.', function() {
 	function openAppearencePanel() {
 		mobileHelper.openMobileWizard();
 
-		cy.get('#ScCellAppearancePropertyPanel')
-			.click();
+		helper.clickOnIdle('#ScCellAppearancePropertyPanel');
 
 		cy.contains('.menu-entry-with-icon', 'Background Color')
 			.should('be.visible');
@@ -44,9 +43,7 @@ describe('Change cell appearance.', function() {
 	it('Apply background color', function() {
 		openAppearencePanelOnFirtsCell();
 
-		// Select a new color
-		cy.get('#BackgroundColor')
-			.click();
+		helper.clickOnIdle('#BackgroundColor');
 
 		mobileHelper.selectFromColorPalette(1, 2);
 
@@ -63,8 +60,7 @@ describe('Change cell appearance.', function() {
 	it('Apply left border', function() {
 		openAppearencePanelOnFirtsCell();
 
-		cy.get('#border-2')
-			.click();
+		helper.clickOnIdle('#border-2');
 
 		calcMobileHelper.selectAllMobile();
 
@@ -76,8 +72,7 @@ describe('Change cell appearance.', function() {
 		openAppearencePanelOnFirtsCell();
 
 		// First add left border
-		cy.get('#border-2')
-			.click();
+		helper.clickOnIdle('#border-2');
 
 		calcMobileHelper.selectAllMobile();
 
@@ -87,8 +82,7 @@ describe('Change cell appearance.', function() {
 		// Then remove it
 		openAppearencePanelOnFirtsCell();
 
-		cy.get('#border-1')
-			.click();
+		helper.clickOnIdle('#border-1');
 
 		calcMobileHelper.selectAllMobile();
 
@@ -99,8 +93,7 @@ describe('Change cell appearance.', function() {
 	it('Apply right border', function() {
 		openAppearencePanelOnFirtsCell();
 
-		cy.get('#border-3')
-			.click();
+		helper.clickOnIdle('#border-3');
 
 		calcMobileHelper.selectAllMobile();
 
@@ -111,8 +104,7 @@ describe('Change cell appearance.', function() {
 	it('Apply left and right border', function() {
 		openAppearencePanelOnFirtsCell();
 
-		cy.get('#border-4')
-			.click();
+		helper.clickOnIdle('#border-4');
 
 		calcMobileHelper.selectAllMobile();
 
@@ -123,8 +115,7 @@ describe('Change cell appearance.', function() {
 	it('Apply top border', function() {
 		openAppearencePanelOnFirtsCell();
 
-		cy.get('#border-5')
-			.click();
+		helper.clickOnIdle('#border-5');
 
 		calcMobileHelper.selectAllMobile();
 
@@ -135,8 +126,7 @@ describe('Change cell appearance.', function() {
 	it('Apply bottom border', function() {
 		openAppearencePanelOnFirtsCell();
 
-		cy.get('#border-6')
-			.click();
+		helper.clickOnIdle('#border-6');
 
 		calcMobileHelper.selectAllMobile();
 
@@ -147,8 +137,7 @@ describe('Change cell appearance.', function() {
 	it('Apply top and bottom border', function() {
 		openAppearencePanelOnFirtsCell();
 
-		cy.get('#border-7')
-			.click();
+		helper.clickOnIdle('#border-7');
 
 		calcMobileHelper.selectAllMobile();
 
@@ -159,8 +148,7 @@ describe('Change cell appearance.', function() {
 	it('Apply border for all sides', function() {
 		openAppearencePanelOnFirtsCell();
 
-		cy.get('#border-8')
-			.click();
+		helper.clickOnIdle('#border-8');
 
 		calcMobileHelper.selectAllMobile();
 
@@ -171,8 +159,7 @@ describe('Change cell appearance.', function() {
 	it('Apply horizontal borders for multiple cells', function() {
 		openAppearencePanelOnAllCells();
 
-		cy.get('#border-9')
-			.click();
+		helper.clickOnIdle('#border-9');
 
 		calcMobileHelper.selectAllMobile();
 
@@ -188,8 +175,7 @@ describe('Change cell appearance.', function() {
 	it('Apply horizontal inner borders and vertical outer borders', function() {
 		openAppearencePanelOnAllCells();
 
-		cy.get('#border-10')
-			.click();
+		helper.clickOnIdle('#border-10');
 
 		calcMobileHelper.selectAllMobile();
 
@@ -212,8 +198,7 @@ describe('Change cell appearance.', function() {
 	it('Apply vertical inner borders and horizontal outer borders', function() {
 		openAppearencePanelOnAllCells();
 
-		cy.get('#border-11')
-			.click();
+		helper.clickOnIdle('#border-11');
 
 		// TODO
 		cy.wait(200);
@@ -239,8 +224,7 @@ describe('Change cell appearance.', function() {
 	it('Apply all inner and outer borders', function() {
 		openAppearencePanelOnAllCells();
 
-		cy.get('#border-12')
-			.click();
+		helper.clickOnIdle('#border-12');
 
 		calcMobileHelper.selectAllMobile();
 
@@ -264,12 +248,10 @@ describe('Change cell appearance.', function() {
 		openAppearencePanelOnFirtsCell();
 
 		// Apply left border first
-		cy.get('#border-2')
-			.click();
+		helper.clickOnIdle('#border-2');
 
 		// Then apply border color
-		cy.get('#FrameLineColor')
-			.click();
+		helper.clickOnIdle('#FrameLineColor');
 
 		mobileHelper.selectFromColorPalette(2, 3);
 
diff --git a/cypress_test/integration_tests/mobile/calc/hamburger_menu_spec.js b/cypress_test/integration_tests/mobile/calc/hamburger_menu_spec.js
index 7d631cc11..20f5fd986 100644
--- a/cypress_test/integration_tests/mobile/calc/hamburger_menu_spec.js
+++ b/cypress_test/integration_tests/mobile/calc/hamburger_menu_spec.js
@@ -866,17 +866,14 @@ describe('Trigger hamburger menu options.', function() {
 
 		mobileHelper.openMobileWizard();
 
-		cy.get('#ScCellAppearancePropertyPanel')
-			.click();
+		helper.clickOnIdle('#ScCellAppearancePropertyPanel');
 
 		cy.contains('.menu-entry-with-icon', 'Background Color')
 			.should('be.visible');
 
-		cy.get('#border-12')
-			.click();
+		helper.clickOnIdle('#border-12');
 
-		cy.get('#FrameLineColor')
-			.click();
+		helper.clickOnIdle('#FrameLineColor');
 
 		mobileHelper.selectFromColorPalette(2, 0, 7);
 
@@ -884,14 +881,12 @@ describe('Trigger hamburger menu options.', function() {
 
 		mobileHelper.openMobileWizard();
 
-		cy.get('#TextPropertyPanel')
-			.click();
+		helper.clickOnIdle('#TextPropertyPanel');
 
 		cy.get('#Bold')

... etc. - the rest is truncated


More information about the Libreoffice-commits mailing list