sw edit win: fix read-only selections while handling ExtTextInput

When typing into a protected section (or other read-only area), the code
goes through SwEditWin::KeyInput(), which checks for HasReadonlySel(),
and calls into sw::DocumentContentOperationsManager::InsertString() in
the read-write case.

When typing via ExtTextInput (e.g. Online), then SwEditWin::Command()
called into sw::DocumentContentOperationsManager::InsertString() without
such a check.

The convention is to do a read-only check in the first Writer function
called by vcl, so handle this in SwEditWin::Command(), to have exactly 1
read-only popup on typing a character.

Change-Id: I7d002e7d76bffeb6f16750de735c5bbf13a7bba9
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/109186
Tested-by: Jenkins
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
diff --git a/sw/qa/extras/tiledrendering/tiledrendering.cxx b/sw/qa/extras/tiledrendering/tiledrendering.cxx
index 93606675..10d54fc 100644
--- a/sw/qa/extras/tiledrendering/tiledrendering.cxx
+++ b/sw/qa/extras/tiledrendering/tiledrendering.cxx
@@ -17,6 +17,7 @@
#include <com/sun/star/frame/XDispatchResultListener.hpp>
#include <com/sun/star/frame/XStorable.hpp>
#include <com/sun/star/frame/Desktop.hpp>
#include <com/sun/star/text/XTextViewCursorSupplier.hpp>

#include <test/helper/transferable.hxx>
#include <LibreOfficeKit/LibreOfficeKitEnums.h>
@@ -146,6 +147,7 @@ public:
    void testDropDownFormFieldButtonNoItem();
    void testTablePaintInvalidate();
    void testSpellOnlineRenderParameter();
    void testExtTextInputReadOnly();

    CPPUNIT_TEST_SUITE(SwTiledRenderingTest);
    CPPUNIT_TEST(testRegisterCallback);
@@ -219,6 +221,7 @@ public:
    CPPUNIT_TEST(testDropDownFormFieldButtonNoItem);
    CPPUNIT_TEST(testTablePaintInvalidate);
    CPPUNIT_TEST(testSpellOnlineRenderParameter);
    CPPUNIT_TEST(testExtTextInputReadOnly);
    CPPUNIT_TEST_SUITE_END();

private:
@@ -2886,6 +2889,46 @@ void SwTiledRenderingTest::testSpellOnlineRenderParameter()
    CPPUNIT_ASSERT_EQUAL(!bSet, pOpt->IsOnlineSpell());
}

void SwTiledRenderingTest::testExtTextInputReadOnly()
{
    // Create a document with a protected section + a normal paragraph after it.
    SwXTextDocument* pXTextDocument = createDoc();
    uno::Reference<text::XTextViewCursorSupplier> xController(
        pXTextDocument->getCurrentController(), uno::UNO_QUERY);
    uno::Reference<text::XTextViewCursor> xCursor = xController->getViewCursor();
    uno::Reference<text::XText> xText = xCursor->getText();
    uno::Reference<text::XTextContent> xSection(
        pXTextDocument->createInstance("com.sun.star.text.TextSection"), uno::UNO_QUERY);
    uno::Reference<beans::XPropertySet> xSectionProps(xSection, uno::UNO_QUERY);
    xSectionProps->setPropertyValue("IsProtected", uno::Any(true));
    xText->insertTextContent(xCursor, xSection, /*bAbsorb=*/true);

    // First paragraph is the protected section, is it empty?
    VclPtr<vcl::Window> pEditWin = pXTextDocument->getDocWindow();
    CPPUNIT_ASSERT(pEditWin);
    CPPUNIT_ASSERT(getParagraph(1)->getString().isEmpty());

    // Try to type into the protected section, is it still empty?
    SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell();
    pWrtShell->SttEndDoc(/*bStt=*/true);
    SfxLokHelper::postExtTextEventAsync(pEditWin, LOK_EXT_TEXTINPUT, "x");
    SfxLokHelper::postExtTextEventAsync(pEditWin, LOK_EXT_TEXTINPUT_END, "x");
    Scheduler::ProcessEventsToIdle();
    // Without the accompanying fix in place, this test would have failed, as it was possible to
    // type into the protected section.
    CPPUNIT_ASSERT(getParagraph(1)->getString().isEmpty());

    // Second paragraph is a normal paragraph, is it empty?
    pWrtShell->Down(/*bSelect=*/false);
    CPPUNIT_ASSERT(getParagraph(2)->getString().isEmpty());

    // Try to type into the protected section, does it have the typed content?
    SfxLokHelper::postExtTextEventAsync(pEditWin, LOK_EXT_TEXTINPUT, "x");
    SfxLokHelper::postExtTextEventAsync(pEditWin, LOK_EXT_TEXTINPUT_END, "x");
    Scheduler::ProcessEventsToIdle();
    CPPUNIT_ASSERT_EQUAL(OUString("x"), getParagraph(2)->getString());
}

CPPUNIT_TEST_SUITE_REGISTRATION(SwTiledRenderingTest);

CPPUNIT_PLUGIN_IMPLEMENT();
diff --git a/sw/source/uibase/docvw/edtwin.cxx b/sw/source/uibase/docvw/edtwin.cxx
index 7edaa5e..2bb3281 100644
--- a/sw/source/uibase/docvw/edtwin.cxx
+++ b/sw/source/uibase/docvw/edtwin.cxx
@@ -5556,7 +5556,7 @@ void SwEditWin::Command( const CommandEvent& rCEvt )
    {
        bool bIsDocReadOnly = m_rView.GetDocShell()->IsReadOnly() &&
                              rSh.IsCursorReadonly();
        if(!bIsDocReadOnly)
        if (!bIsDocReadOnly && !rSh.HasReadonlySel())
        {
            if( m_pQuickHlpData->m_bIsDisplayed )
                m_pQuickHlpData->Stop( rSh );
@@ -5588,6 +5588,14 @@ void SwEditWin::Command( const CommandEvent& rCEvt )
                }
            }
        }

        if (rSh.HasReadonlySel())
        {
            // Inform the user that the request has been ignored.
            auto xInfo = std::make_shared<weld::GenericDialogController>(
                GetFrameWeld(), "modules/swriter/ui/inforeadonlydialog.ui", "InfoReadonlyDialog");
            weld::DialogController::runAsync(xInfo, [](sal_Int32 /*nResult*/) {});
        }
    }
    break;
    case CommandEventId::CursorPos: