sc, out of order undo: allow multiple actions from other view

this is the calc equivalent of
    commit c88c2d40d1a4aebc46b25368b80c02bc2f136658
    Date:   Fri Nov 12 08:39:35 2021 +0100
    sw, out of order undo: allow multiple actions from other views

Change-Id: I5acbd1e1cacef7c2e2a549f4d2270e961f576a65
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/137652
Tested-by: Jenkins
Reviewed-by: Noel Grandin <noel.grandin@collabora.co.uk>
diff --git a/sc/qa/unit/tiledrendering/tiledrendering.cxx b/sc/qa/unit/tiledrendering/tiledrendering.cxx
index 9ed5cf4..559769c 100644
--- a/sc/qa/unit/tiledrendering/tiledrendering.cxx
+++ b/sc/qa/unit/tiledrendering/tiledrendering.cxx
@@ -130,6 +130,7 @@ public:
    void testInvalidEntrySave();
    void testUndoReordering();
    void testUndoReorderingRedo();
    void testUndoReorderingMulti();

    CPPUNIT_TEST_SUITE(ScTiledRenderingTest);
    CPPUNIT_TEST(testRowColumnHeaders);
@@ -189,6 +190,7 @@ public:
    CPPUNIT_TEST(testInvalidEntrySave);
    CPPUNIT_TEST(testUndoReordering);
    CPPUNIT_TEST(testUndoReorderingRedo);
    CPPUNIT_TEST(testUndoReorderingMulti);
    CPPUNIT_TEST_SUITE_END();

private:
@@ -3127,6 +3129,76 @@ void ScTiledRenderingTest::testUndoReorderingRedo()
    CPPUNIT_ASSERT_EQUAL(OUString("CC"), pDoc->GetString(ScAddress(0, 2, 0)));
}

void ScTiledRenderingTest::testUndoReorderingMulti()
{
    ScModelObj* pModelObj = createDoc("empty.ods");
    CPPUNIT_ASSERT(pModelObj);
    ScDocument* pDoc = pModelObj->GetDocument();
    CPPUNIT_ASSERT(pDoc);
    ScUndoManager* pUndoManager = pDoc->GetUndoManager();
    CPPUNIT_ASSERT(pUndoManager);
    CPPUNIT_ASSERT_EQUAL(std::size_t(0), pUndoManager->GetUndoActionCount());

    // view #1
    int nView1 = SfxLokHelper::getView();
    ViewCallback aView1;

    // view #2
    SfxLokHelper::createView();
    int nView2 = SfxLokHelper::getView();
    pModelObj->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
    ViewCallback aView2;

    // text edit a cell in view #1
    SfxLokHelper::setView(nView1);
    pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'x', 0);
    pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 'x', 0);
    pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'x', 0);
    pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 'x', 0);
    pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::RETURN);
    pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::RETURN);
    Scheduler::ProcessEventsToIdle();
    CPPUNIT_ASSERT_EQUAL(std::size_t(1), pUndoManager->GetUndoActionCount());

    // text edit a different cell in view #2
    SfxLokHelper::setView(nView2);
    ScTabViewShell* pView2 = dynamic_cast<ScTabViewShell*>(SfxViewShell::Current());
    pView2->SetCursor(0, 2);
    pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'C', 0);
    pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 'C', 0);
    pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'C', 0);
    pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 'C', 0);
    pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::RETURN);
    pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::RETURN);
    Scheduler::ProcessEventsToIdle();
    CPPUNIT_ASSERT_EQUAL(std::size_t(2), pUndoManager->GetUndoActionCount());
    CPPUNIT_ASSERT_EQUAL(OUString("xx"), pDoc->GetString(ScAddress(0, 0, 0)));
    CPPUNIT_ASSERT_EQUAL(OUString("CC"), pDoc->GetString(ScAddress(0, 2, 0)));

    // and another cell in view #2
    pView2->SetCursor(0, 3);
    pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'D', 0);
    pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 'D', 0);
    pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'D', 0);
    pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 'D', 0);
    pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::RETURN);
    pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::RETURN);
    Scheduler::ProcessEventsToIdle();
    CPPUNIT_ASSERT_EQUAL(std::size_t(3), pUndoManager->GetUndoActionCount());
    CPPUNIT_ASSERT_EQUAL(OUString("xx"), pDoc->GetString(ScAddress(0, 0, 0)));
    CPPUNIT_ASSERT_EQUAL(OUString("CC"), pDoc->GetString(ScAddress(0, 2, 0)));
    CPPUNIT_ASSERT_EQUAL(OUString("DD"), pDoc->GetString(ScAddress(0, 3, 0)));

    // View 1 presses undo
    SfxLokHelper::setView(nView1);
    dispatchCommand(mxComponent, ".uno:Undo", {});
    Scheduler::ProcessEventsToIdle();
    CPPUNIT_ASSERT_EQUAL(std::size_t(2), pUndoManager->GetUndoActionCount());
    CPPUNIT_ASSERT_EQUAL(OUString(""), pDoc->GetString(ScAddress(0, 0, 0)));
    CPPUNIT_ASSERT_EQUAL(OUString("CC"), pDoc->GetString(ScAddress(0, 2, 0)));
    CPPUNIT_ASSERT_EQUAL(OUString("DD"), pDoc->GetString(ScAddress(0, 3, 0)));
}

}

CPPUNIT_TEST_SUITE_REGISTRATION(ScTiledRenderingTest);
diff --git a/sc/source/ui/inc/undomanager.hxx b/sc/source/ui/inc/undomanager.hxx
index da254a0..7ec3db6 100644
--- a/sc/source/ui/inc/undomanager.hxx
+++ b/sc/source/ui/inc/undomanager.hxx
@@ -20,9 +20,9 @@ public:

    /**
     * Checks if the topmost undo action owned by pView is independent from the topmost action undo
     * action.
     * action. Sets rOffset to the offset of that independent undo action on success.
     */
    bool IsViewUndoActionIndependent(const SfxViewShell* pView) const;
    bool IsViewUndoActionIndependent(const SfxViewShell* pView, sal_uInt16& rOffset) const;

    /// Make these public
    using SdrUndoManager::UndoWithContext;
diff --git a/sc/source/ui/undo/undobase.cxx b/sc/source/ui/undo/undobase.cxx
index 250c786..31f1e41 100644
--- a/sc/source/ui/undo/undobase.cxx
+++ b/sc/source/ui/undo/undobase.cxx
@@ -621,7 +621,7 @@ ScUndoManager::~ScUndoManager() {}
 * Checks if the topmost undo action owned by pView is independent from the topmost action undo
 * action.
 */
bool ScUndoManager::IsViewUndoActionIndependent(const SfxViewShell* pView) const
bool ScUndoManager::IsViewUndoActionIndependent(const SfxViewShell* pView, sal_uInt16& rOffset) const
{
    if (GetUndoActionCount() <= 1)
    {
@@ -641,10 +641,16 @@ bool ScUndoManager::IsViewUndoActionIndependent(const SfxViewShell* pView) const

    // Earlier undo action that belongs to the view, but is not the top one.
    const SfxUndoAction* pViewAction = nullptr;
    const SfxUndoAction* pAction = GetUndoAction(1);
    if (pAction->GetViewShellId() == nViewId)
    size_t nOffset = 0;
    for (size_t i = 0; i < GetUndoActionCount(); ++i)
    {
        pViewAction = pAction;
        const SfxUndoAction* pAction = GetUndoAction(i);
        if (pAction->GetViewShellId() == nViewId)
        {
            pViewAction = pAction;
            nOffset = i;
            break;
        }
    }

    if (!pViewAction)
@@ -679,6 +685,7 @@ bool ScUndoManager::IsViewUndoActionIndependent(const SfxViewShell* pView) const
        }
    }

    rOffset = nOffset;
    return true;
}

diff --git a/sc/source/ui/view/tabvwshb.cxx b/sc/source/ui/view/tabvwshb.cxx
index 02ea57f..7052820 100644
--- a/sc/source/ui/view/tabvwshb.cxx
+++ b/sc/source/ui/view/tabvwshb.cxx
@@ -756,11 +756,12 @@ void ScTabViewShell::ExecuteUndo(SfxRequest& rReq)
                        ViewShellId nViewShellId = GetViewShellId();
                        if (pAction->GetViewShellId() != nViewShellId)
                        {
                            if (pUndoManager->IsViewUndoActionIndependent(this))
                            sal_uInt16 nOffset = 0;
                            if (pUndoManager->IsViewUndoActionIndependent(this, nOffset))
                            {
                                // Execute the undo with an offset: don't undo the top action, but an
                                // earlier one, since it's independent and that belongs to our view.
                                nUndoOffset = 1;
                                nUndoOffset += nOffset;
                            }
                            else
                            {