include/LibreOfficeKit/LibreOfficeKitEnums.h         |   18 ++++++
 libreofficekit/source/gtk/lokdocview.cxx             |   55 +++++++++++++++++++
 sw/qa/extras/tiledrendering/data/content-control.odt |binary
 sw/qa/extras/tiledrendering/tiledrendering.cxx       |   44 +++++++++++++++
 sw/source/core/crsr/viscrs.cxx                       |   22 +++++++
 5 files changed, 139 insertions(+)

New commits:
commit c1d784ab4078fe9ea478bb16cad687827c7f2551
Author:     Miklos Vajna <vmik...@collabora.com>
AuthorDate: Fri Apr 22 12:25:31 2022 +0200
Commit:     Miklos Vajna <vmik...@collabora.com>
CommitDate: Mon Apr 25 08:34:15 2022 +0200

    sw content controls: add LOK API
    
    This is somewhat similar to LOK_CALLBACK_FORM_FIELD_BUTTON: if the
    cursor enters or leaves a content control, then we send this message, so
    the LOK client can render some kind of shading and/or border around the
    content control to indicate the boundaries of the object.
    
    Similar to selections, this can be multiple rectangles in case the
    string is long enough that the layout breaks it into multiple lines.
    
    (cherry picked from commit e4d1731ba3e8bac2801d1b76cfb66bf7f9795468)
    
    Conflicts:
            sw/source/core/crsr/viscrs.cxx
    
    Change-Id: I0641a19503b7a1d4cade8fe9b510605cab49f258
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/133332
    Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoff...@gmail.com>
    Reviewed-by: Miklos Vajna <vmik...@collabora.com>

diff --git a/include/LibreOfficeKit/LibreOfficeKitEnums.h 
b/include/LibreOfficeKit/LibreOfficeKitEnums.h
index 006713447aed..0393e4315e31 100644
--- a/include/LibreOfficeKit/LibreOfficeKitEnums.h
+++ b/include/LibreOfficeKit/LibreOfficeKitEnums.h
@@ -795,6 +795,22 @@ typedef enum
      * Rectangle format is the same as LOK_CALLBACK_INVALIDATE_TILES.
      */
     LOK_CALLBACK_SC_FOLLOW_JUMP = 54,
+
+    /**
+     * Sends all information for displaying metadata for a text based content 
control.
+     *
+     * The payload example:
+     * {
+     *      "action": "show",
+     *      "rectangles": "1418, 1694, 720, 551; 10291, 1418, 1099, 275"
+     * }
+     *
+     * or
+     * {
+     *      "action": "hide"
+     * }
+     */
+    LOK_CALLBACK_CONTENT_CONTROL = 55,
 }
 LibreOfficeKitCallbackType;
 
@@ -933,6 +949,8 @@ static inline const char* lokCallbackTypeToString(int nType)
         return "LOK_COMMAND_BLOCKED";
     case LOK_CALLBACK_SC_FOLLOW_JUMP:
         return "LOK_CALLBACK_SC_FOLLOW_JUMP";
+    case LOK_CALLBACK_CONTENT_CONTROL:
+        return "LOK_CALLBACK_CONTENT_CONTROL";
     }
 
     assert(!"Unknown LibreOfficeKitCallbackType type.");
diff --git a/libreofficekit/source/gtk/lokdocview.cxx 
b/libreofficekit/source/gtk/lokdocview.cxx
index 015995016a36..4d090dcc0de5 100644
--- a/libreofficekit/source/gtk/lokdocview.cxx
+++ b/libreofficekit/source/gtk/lokdocview.cxx
@@ -124,6 +124,8 @@ struct LOKDocViewPrivateImpl
     guint32 m_nKeyModifier;
     /// Rectangles of the current text selection.
     std::vector<GdkRectangle> m_aTextSelectionRectangles;
+    /// Rectangles of the current content control.
+    std::vector<GdkRectangle> m_aContentControlRectangles;
     /// Rectangles of view selections. The current view can only see
     /// them, can't modify them. Key is the view id.
     std::map<int, ViewRectangles> m_aTextViewSelectionRectangles;
@@ -281,6 +283,7 @@ enum
     ADDRESS_CHANGED,
     FORMULA_CHANGED,
     TEXT_SELECTION,
+    CONTENT_CONTROL,
     PASSWORD_REQUIRED,
     COMMENT,
     RULER,
@@ -1394,6 +1397,27 @@ callback (gpointer pData)
         break;
     }
 
+    case LOK_CALLBACK_CONTENT_CONTROL:
+    {
+        std::stringstream aStream(pCallback->m_aPayload);
+        boost::property_tree::ptree aTree;
+        boost::property_tree::read_json(aStream, aTree);
+        auto aAction = aTree.get<std::string>("action");
+        if (aAction == "show")
+        {
+            auto aRectangles = aTree.get<std::string>("rectangles");
+            priv->m_aContentControlRectangles = payloadToRectangles(pDocView, 
aRectangles.c_str());
+        }
+        else if (aAction == "hide")
+        {
+            priv->m_aContentControlRectangles.clear();
+        }
+        bool bIsTextSelected = !priv->m_aContentControlRectangles.empty();
+        g_signal_emit(pDocView, doc_view_signals[CONTENT_CONTROL], 0, 
bIsTextSelected);
+        gtk_widget_queue_draw(GTK_WIDGET(pDocView));
+    }
+    break;
+
     case LOK_CALLBACK_STATUS_INDICATOR_START:
     case LOK_CALLBACK_STATUS_INDICATOR_SET_VALUE:
     case LOK_CALLBACK_STATUS_INDICATOR_FINISH:
@@ -1811,6 +1835,21 @@ renderOverlay(LOKDocView* pDocView, cairo_t* pCairo)
         }
     }
 
+    if (!priv->m_aContentControlRectangles.empty())
+    {
+        for (const GdkRectangle& rRectangle : 
priv->m_aContentControlRectangles)
+        {
+            // Black with 75% transparency.
+            cairo_set_source_rgba(pCairo, (double(0x7f))/255, 
(double(0x7f))/255, (double(0x7f))/255, 0.25);
+            cairo_rectangle(pCairo,
+                            twipToPixel(rRectangle.x, priv->m_fZoom),
+                            twipToPixel(rRectangle.y, priv->m_fZoom),
+                            twipToPixel(rRectangle.width, priv->m_fZoom),
+                            twipToPixel(rRectangle.height, priv->m_fZoom));
+            cairo_fill(pCairo);
+        }
+    }
+
     // Selections of other views.
     for (const auto& rPair : priv->m_aTextViewSelectionRectangles)
     {
@@ -3284,6 +3323,21 @@ static void lok_doc_view_class_init (LOKDocViewClass* 
pClass)
                      G_TYPE_NONE, 1,
                      G_TYPE_BOOLEAN);
 
+    /**
+     * LOKDocView::content-control:
+     * @pDocView: the #LOKDocView on which the signal is emitted
+     * @bIsTextSelected: whether current content control is non-null
+     */
+    doc_view_signals[CONTENT_CONTROL] =
+        g_signal_new("content-control",
+                     G_TYPE_FROM_CLASS(pGObjectClass),
+                     G_SIGNAL_RUN_FIRST,
+                     0,
+                     nullptr, nullptr,
+                     g_cclosure_marshal_VOID__BOOLEAN,
+                     G_TYPE_NONE, 1,
+                     G_TYPE_BOOLEAN);
+
     /**
      * LOKDocView::password-required:
      * @pDocView: the #LOKDocView on which the signal is emitted
@@ -3728,6 +3782,7 @@ lok_doc_view_reset_view(LOKDocView* pDocView)
     priv->m_nLastButtonPressTime = 0;
     priv->m_nLastButtonReleaseTime = 0;
     priv->m_aTextSelectionRectangles.clear();
+    priv->m_aContentControlRectangles.clear();
 
     memset(&priv->m_aTextSelectionStart, 0, 
sizeof(priv->m_aTextSelectionStart));
     memset(&priv->m_aTextSelectionEnd, 0, sizeof(priv->m_aTextSelectionEnd));
diff --git a/sw/qa/extras/tiledrendering/data/content-control.odt 
b/sw/qa/extras/tiledrendering/data/content-control.odt
new file mode 100644
index 000000000000..624063fbd606
Binary files /dev/null and 
b/sw/qa/extras/tiledrendering/data/content-control.odt differ
diff --git a/sw/qa/extras/tiledrendering/tiledrendering.cxx 
b/sw/qa/extras/tiledrendering/tiledrendering.cxx
index e59c8cccacf4..ee85fd75a6f7 100644
--- a/sw/qa/extras/tiledrendering/tiledrendering.cxx
+++ b/sw/qa/extras/tiledrendering/tiledrendering.cxx
@@ -167,6 +167,7 @@ public:
     void testCondCollCopy();
     void testMoveShapeHandle();
     void testRedlinePortions();
+    void testContentControl();
 
     CPPUNIT_TEST_SUITE(SwTiledRenderingTest);
     CPPUNIT_TEST(testRegisterCallback);
@@ -254,6 +255,7 @@ public:
     CPPUNIT_TEST(testCondCollCopy);
     CPPUNIT_TEST(testMoveShapeHandle);
     CPPUNIT_TEST(testRedlinePortions);
+    CPPUNIT_TEST(testContentControl);
     CPPUNIT_TEST_SUITE_END();
 
 private:
@@ -279,6 +281,7 @@ private:
     OString m_sHyperlinkText;
     OString m_sHyperlinkLink;
     OString m_aFormFieldButton;
+    OString m_aContentControl;
     OString m_ShapeSelection;
     TestLokCallbackWrapper m_callbackWrapper;
 };
@@ -448,6 +451,11 @@ void SwTiledRenderingTest::callbackImpl(int nType, const 
char* pPayload)
                 m_aFormFieldButton = OString(pPayload);
             }
             break;
+        case LOK_CALLBACK_CONTENT_CONTROL:
+            {
+                m_aContentControl = OString(pPayload);
+            }
+            break;
         case LOK_CALLBACK_GRAPHIC_SELECTION:
             {
                 m_ShapeSelection = OString(pPayload);
@@ -3600,6 +3608,42 @@ void SwTiledRenderingTest::testRedlinePortions()
     assertXPath(pXmlDoc, "//Text[4]", "Portion", " after");
 }
 
+void SwTiledRenderingTest::testContentControl()
+{
+    // Given a document with a content control:
+    SwXTextDocument* pXTextDocument = createDoc("content-control.odt");
+    SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell();
+    setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell());
+    pWrtShell->SttEndDoc(/*bStt=*/true);
+    m_aContentControl.clear();
+
+    // When entering that content control (chars 2-7 are the content control):
+    pWrtShell->Right(CRSR_SKIP_CHARS, /*bSelect=*/false, /*nCount=*/5, 
/*bBasicCall=*/false);
+
+    // Then make sure that the callback is emitted:
+    // Without the accompanying fix in place, this test would have failed, no 
callback was emitted.
+    CPPUNIT_ASSERT(!m_aContentControl.isEmpty());
+    {
+        std::stringstream aStream(m_aContentControl.getStr());
+        boost::property_tree::ptree aTree;
+        boost::property_tree::read_json(aStream, aTree);
+        OString sAction = 
aTree.get_child("action").get_value<std::string>().c_str();
+        CPPUNIT_ASSERT_EQUAL(OString("show"), sAction);
+        OString sRectangles = 
aTree.get_child("rectangles").get_value<std::string>().c_str();
+        CPPUNIT_ASSERT(!sRectangles.isEmpty());
+    }
+
+    // And when leaving that content control:
+    pWrtShell->SttEndDoc(/*bStt=*/true);
+
+    // Then make sure that the callback is emitted again:
+    std::stringstream aStream(m_aContentControl.getStr());
+    boost::property_tree::ptree aTree;
+    boost::property_tree::read_json(aStream, aTree);
+    OString sAction = 
aTree.get_child("action").get_value<std::string>().c_str();
+    CPPUNIT_ASSERT_EQUAL(OString("hide"), sAction);
+}
+
 CPPUNIT_TEST_SUITE_REGISTRATION(SwTiledRenderingTest);
 
 CPPUNIT_PLUGIN_IMPLEMENT();
diff --git a/sw/source/core/crsr/viscrs.cxx b/sw/source/core/crsr/viscrs.cxx
index d72289bdbb36..1f91ae612c54 100644
--- a/sw/source/core/crsr/viscrs.cxx
+++ b/sw/source/core/crsr/viscrs.cxx
@@ -57,6 +57,7 @@
 #include <SwGrammarMarkUp.hxx>
 #include <docsh.hxx>
 #include <svtools/optionsdrawinglayer.hxx>
+#include <tools/json_writer.hxx>
 #include <cellfrm.hxx>
 #include <wrtsh.hxx>
 #include <textcontentcontrol.hxx>
@@ -634,6 +635,7 @@ void SwSelPaintRects::HighlightInputField()
 void SwSelPaintRects::HighlightContentControl()
 {
     std::vector<basegfx::B2DRange> aContentControlRanges;
+    std::vector<OString> aLOKRectangles;
 
     if (m_bShowContentControlOverlay)
     {
@@ -668,12 +670,25 @@ void SwSelPaintRects::HighlightContentControl()
 
                 aContentControlRanges.emplace_back(aRect.Left(), aRect.Top(), 
aRect.Right() + 1,
                                                    aRect.Bottom() + 1);
+                if (comphelper::LibreOfficeKit::isActive())
+                {
+                    aLOKRectangles.push_back(aRect.toString());
+                }
             }
         }
     }
 
     if (!aContentControlRanges.empty())
     {
+        if (comphelper::LibreOfficeKit::isActive())
+        {
+            OString aPayload = comphelper::string::join("; ", aLOKRectangles);
+            tools::JsonWriter aJson;
+            aJson.put("action", "show");
+            aJson.put("rectangles", aPayload);
+            std::unique_ptr<char, o3tl::free_delete> 
pJson(aJson.extractData());
+            
GetShell()->GetSfxViewShell()->libreOfficeKitViewCallback(LOK_CALLBACK_CONTENT_CONTROL,
 pJson.get());
+        }
         if (m_pContentControlOverlay)
         {
             
m_pContentControlOverlay->setRanges(std::move(aContentControlRanges));
@@ -699,6 +714,13 @@ void SwSelPaintRects::HighlightContentControl()
     }
     else
     {
+        if (comphelper::LibreOfficeKit::isActive() && m_pContentControlOverlay)
+        {
+            tools::JsonWriter aJson;
+            aJson.put("action", "hide");
+            std::unique_ptr<char, o3tl::free_delete> 
pJson(aJson.extractData());
+            
GetShell()->GetSfxViewShell()->libreOfficeKitViewCallback(LOK_CALLBACK_CONTENT_CONTROL,
 pJson.get());
+        }
         m_pContentControlOverlay.reset();
     }
 }

Reply via email to