sw/qa/uibase/docvw/docvw.cxx       |   99 +++++++++++++++++++++++++++++++++++++
 sw/source/uibase/docvw/edtwin2.cxx |   30 ++++++++++-
 2 files changed, 126 insertions(+), 3 deletions(-)

New commits:
commit ad895723d7d326dad709410618a1a5af9716c95e
Author:     Miklos Vajna <[email protected]>
AuthorDate: Fri Feb 20 10:26:51 2026 +0100
Commit:     Caolán McNamara <[email protected]>
CommitDate: Fri Feb 20 12:14:14 2026 +0100

    cool#13988 sw redline tooltip LOK API: expose anchor range
    
    Open a document with a tracked change in a LOK client, hover your mouse
    over the tracked change, LOK_CALLBACK_TOOLTIP gets triggered, but no
    info is sent about the redline itself.
    
    Something similar already happens for content controls, there
    LOK_CALLBACK_CONTENT_CONTROL has a 'rectangles' key to describe the
    range (rectangles) of the content control.
    
    Fix the problem in a similar way: let SwEditWin::RequestHelp() check if
    the content under the cursor is a redline and if so, emit the redline
    range's rectangles as a new property of the tooltip.
    
    This time do it as a JSON array, e.g.
    { "type": "generaltooltip", "text": "Deleted: ...", "rectangle": "...", 
"anchorRectangles": [ "1418, 1966, 2390, 264", "2159, 1701, 9231, 264"]}
    to do less string parsing on the LOK client side. And finally expose the
    redline type in a similar way, so the LOK client can color the highlight
    accordingly.
    
    Change-Id: I6b72d5ff38d2a2b4fab0bda3564dd5b2770779d6
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/199801
    Tested-by: Jenkins CollaboraOffice <[email protected]>
    Tested-by: Caolán McNamara <[email protected]>
    Reviewed-by: Caolán McNamara <[email protected]>

diff --git a/sw/qa/uibase/docvw/docvw.cxx b/sw/qa/uibase/docvw/docvw.cxx
index bc9b45d0396a..e32daae90288 100644
--- a/sw/qa/uibase/docvw/docvw.cxx
+++ b/sw/qa/uibase/docvw/docvw.cxx
@@ -9,15 +9,25 @@
 
 #include <swmodeltestbase.hxx>
 
+#include <boost/property_tree/json_parser.hpp>
+
 #include <com/sun/star/text/XTextDocument.hpp>
 
+#include <LibreOfficeKit/LibreOfficeKitEnums.h>
+#include <comphelper/lok.hxx>
+#include <sfx2/lokhelper.hxx>
+#include <test/lokcallback.hxx>
 #include <vcl/event.hxx>
+#include <vcl/scheduler.hxx>
 
 #include <docsh.hxx>
 #include <edtwin.hxx>
 #include <flyfrm.hxx>
 #include <frameformats.hxx>
+#include <IDocumentRedlineAccess.hxx>
+#include <unotxdoc.hxx>
 #include <view.hxx>
+#include <viscrs.hxx>
 #include <wrtsh.hxx>
 
 namespace
@@ -187,6 +197,95 @@ CPPUNIT_TEST_FIXTURE(Test, testShiftDoubleClickOnImage)
     CPPUNIT_ASSERT_GREATER(0, nGraphicDialogs);
 }
 
+namespace
+{
+/// Test LOK callback, handling just LOK_CALLBACK_TOOLTIP.
+struct TooltipCallback
+{
+    std::string rect;
+    std::string anchorRectangles;
+    std::string redlineType;
+
+    static void callback(int nType, const char* pPayload, void* pData);
+    void callbackImpl(int nType, const char* pPayload);
+};
+
+void TooltipCallback::callback(int nType, const char* pPayload, void* pData)
+{
+    static_cast<TooltipCallback*>(pData)->callbackImpl(nType, pPayload);
+}
+
+void TooltipCallback::callbackImpl(int nType, const char* pPayload)
+{
+    if (nType == LOK_CALLBACK_TOOLTIP)
+    {
+        std::stringstream aStream(pPayload);
+        boost::property_tree::ptree aTree;
+        boost::property_tree::read_json(aStream, aTree);
+        rect = aTree.get_child("rectangle").get_value<std::string>();
+        auto it = aTree.find("anchorRectangles");
+        if (it != aTree.not_found())
+        {
+            std::vector<std::string> aRects;
+            for (const auto& rRect : it->second)
+                aRects.push_back(rRect.second.get_value<std::string>());
+            std::stringstream aRectStream;
+            for (size_t i = 0; i < aRects.size(); ++i)
+            {
+                if (i > 0)
+                    aRectStream << "; ";
+                aRectStream << aRects[i];
+            }
+            anchorRectangles = aRectStream.str();
+        }
+        auto itType = aTree.find("redlineType");
+        if (itType != aTree.not_found())
+            redlineType = itType->second.get_value<std::string>();
+    }
+}
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testRedlineTooltipAnchorRectangles)
+{
+    // Set up LOK:
+    comphelper::LibreOfficeKit::setActive(true);
+
+    // Given a document with a redline:
+    createSwDoc();
+    
getSwTextDoc()->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
+    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
+    TooltipCallback aCallback;
+    TestLokCallbackWrapper aCallbackWrapper(&TooltipCallback::callback, 
&aCallback);
+    
pWrtShell->GetSfxViewShell()->setLibreOfficeKitViewCallback(&aCallbackWrapper);
+    
aCallbackWrapper.setLOKViewId(SfxLokHelper::getView(*pWrtShell->GetSfxViewShell()));
+    pWrtShell->SetRedlineFlagsAndCheckInsMode(RedlineFlags::On | 
RedlineFlags::ShowMask);
+    pWrtShell->Insert(u"test"_ustr);
+
+    // When moving the mouse over the redline:
+    SwShellCursor* pShellCursor = pWrtShell->getShellCursor(false);
+    CPPUNIT_ASSERT(pShellCursor);
+    pWrtShell->EndOfSection(/*bSelect=*/false);
+    Point aEnd = pShellCursor->GetSttPos();
+    pWrtShell->StartOfSection(/*bSelect=*/false);
+    Point aStart = pShellCursor->GetSttPos();
+    Point aMiddle((aStart.getX() + aEnd.getX()) / 2, (aStart.getY() + 
aEnd.getY()) / 2);
+    getSwTextDoc()->postMouseEvent(LOK_MOUSEEVENT_MOUSEMOVE, aMiddle.getX(), 
aMiddle.getY(), 1, 0,
+                                   0);
+    Scheduler::ProcessEventsToIdle();
+
+    // Then make sure the tooltip callback has redlineType and 
anchorRectangles:
+    // Without the accompanying fix in place, this test would have failed, no 
anchor rectangles were
+    // emitted.
+    CPPUNIT_ASSERT(!aCallback.anchorRectangles.empty());
+    CPPUNIT_ASSERT_EQUAL(std::string("Insert"), aCallback.redlineType);
+
+    // Tear down LOK:
+    pWrtShell->GetSfxViewShell()->setLibreOfficeKitViewCallback(nullptr);
+    mxComponent->dispose();
+    mxComponent.clear();
+    comphelper::LibreOfficeKit::setActive(false);
+}
+
 CPPUNIT_PLUGIN_IMPLEMENT();
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/uibase/docvw/edtwin2.cxx 
b/sw/source/uibase/docvw/edtwin2.cxx
index f5ca79d55df6..703e4acd495c 100644
--- a/sw/source/uibase/docvw/edtwin2.cxx
+++ b/sw/source/uibase/docvw/edtwin2.cxx
@@ -65,6 +65,7 @@
 #include <unomap.hxx>
 #include <com/sun/star/style/XStyleFamiliesSupplier.hpp>
 #include <LibreOfficeKit/LibreOfficeKitEnums.h>
+#include <viscrs.hxx>
 
 namespace {
 
@@ -312,13 +313,31 @@ OUString SwEditWin::ClipLongToolTip(const OUString& rText)
     return sDisplayText;
 }
 
-static OString getTooltipPayload(const OUString& tooltip, const SwRect& rect)
+static OString getTooltipPayload(const OUString& tooltip, const SwRect& rect,
+                                 SwWrtShell& rSh,
+                                 const SwRangeRedline* pRedline = nullptr)
 {
     tools::JsonWriter writer;
     {
         writer.put("type", "generaltooltip");
         writer.put("text", tooltip);
         writer.put("rectangle", rect.SVRect().toString());
+
+        if (pRedline)
+        {
+            writer.put("redlineType",
+                       
SwRedlineTypeToOUString(pRedline->GetRedlineData().GetType()));
+
+            SwShellCursor aCursor(rSh, *pRedline->Start());
+            aCursor.SetMark();
+            *aCursor.GetMark() = *pRedline->End();
+            aCursor.FillRects();
+            auto aArray = writer.startArray("anchorRectangles");
+            for (const auto& rRect : aCursor)
+            {
+                
writer.putSimpleValue(OUString::fromUtf8(rRect.SVRect().toString()));
+            }
+        }
     }
     return writer.finishAndGetAsOString();
 }
@@ -659,8 +678,13 @@ void SwEditWin::RequestHelp(const HelpEvent &rEvt)
             {
                 if (comphelper::LibreOfficeKit::isActive())
                 {
-                    m_rView.libreOfficeKitViewCallback(
-                        LOK_CALLBACK_TOOLTIP, getTooltipPayload(sText, 
aFieldRect));
+                    const SwRangeRedline* pRedline = nullptr;
+                    if (aContentAtPos.eContentAtPos == IsAttrAtPos::Redline)
+                    {
+                        pRedline = aContentAtPos.aFnd.pRedl;
+                    }
+                    OString aPayload = getTooltipPayload(sText, aFieldRect, 
rSh, pRedline);
+                    m_rView.libreOfficeKitViewCallback(LOK_CALLBACK_TOOLTIP, 
aPayload);
                 }
                 else
                 {

Reply via email to