sw/inc/cmdid.h                     |    1 
 sw/qa/uibase/shells/shells.cxx     |   52 ++++++++++++++++++
 sw/sdi/_basesh.sdi                 |    5 +
 sw/sdi/swriter.sdi                 |   14 ++++
 sw/source/uibase/shells/basesh.cxx |  104 +++++++++++++++++++++++++++++++++++++
 5 files changed, 176 insertions(+)

New commits:
commit 402ab3d145a1e8e123caabf4567aef7b6631fc3c
Author:     Miklos Vajna <vmik...@collabora.com>
AuthorDate: Fri Jan 13 08:20:48 2023 +0100
Commit:     Miklos Vajna <vmik...@collabora.com>
CommitDate: Fri Jan 13 09:55:20 2023 +0000

    sw: add a new .uno:UpdateField UNO command
    
    This is similar to commit ea208f6004770eb4b81d28e6930cd0c7bd5d8f12 (sw:
    add a new .uno:UpdateBookmark UNO command, 2023-01-11, but that was for
    the bookmark under cursor, and this is for fields (refmarks as a start).
    
    Change-Id: I3e547b668361898b7ed734ea325fdf1d74e5dbb2
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/145427
    Reviewed-by: Miklos Vajna <vmik...@collabora.com>
    Tested-by: Jenkins

diff --git a/sw/inc/cmdid.h b/sw/inc/cmdid.h
index 3baa2f050ecc..96d935a3bd2e 100644
--- a/sw/inc/cmdid.h
+++ b/sw/inc/cmdid.h
@@ -326,6 +326,7 @@ class SwUINumRuleItem;
 #define FN_UPDATE_SECTIONS (FN_INSERT2 + 35)
 #define FN_DELETE_TEXT_FORMFIELDS (FN_INSERT2 + 36)
 #define FN_UPDATE_BOOKMARK (FN_INSERT2 + 37)
+#define FN_UPDATE_FIELD (FN_INSERT2 + 38)
 
 // Region: Format
 #define FN_AUTOFORMAT_APPLY     (FN_FORMAT + 1 ) /* apply autoformat options */
diff --git a/sw/qa/uibase/shells/shells.cxx b/sw/qa/uibase/shells/shells.cxx
index 8a7d7fce4f87..22cd709378cb 100644
--- a/sw/qa/uibase/shells/shells.cxx
+++ b/sw/qa/uibase/shells/shells.cxx
@@ -809,6 +809,58 @@ CPPUNIT_TEST_FIXTURE(SwUibaseShellsTest, 
testUpdateBookmark)
     CPPUNIT_ASSERT(it != pDoc->getIDocumentMarkAccess()->getAllMarksEnd());
 }
 
+CPPUNIT_TEST_FIXTURE(SwUibaseShellsTest, testUpdateRefmark)
+{
+    // Given a document with a refmark:
+    createSwDoc();
+    SwDoc* pDoc = getSwDoc();
+    uno::Sequence<css::beans::PropertyValue> aArgs = {
+        comphelper::makePropertyValue("TypeName", 
uno::Any(OUString("SetRef"))),
+        comphelper::makePropertyValue(
+            "Name", uno::Any(OUString("ZOTERO_ITEM CSL_CITATION {} old 
refmark"))),
+        comphelper::makePropertyValue("Content", uno::Any(OUString("old 
content"))),
+    };
+    dispatchCommand(mxComponent, ".uno:InsertField", aArgs);
+
+    // When updating that refmark:
+    std::vector<beans::PropertyValue> aArgsVec = 
comphelper::JsonToPropertyValues(R"json(
+{
+    "TypeName": {
+        "type": "string",
+        "value": "SetRef"
+    },
+    "NamePrefix": {
+        "type": "string",
+        "value": "ZOTERO_ITEM CSL_CITATION"
+    },
+    "Field": {
+        "type": "[]com.sun.star.beans.PropertyValue",
+        "value": {
+            "Name": {
+                "type": "string",
+                "value": "ZOTERO_ITEM CSL_CITATION {} new refmark"
+            },
+            "Content": {
+                "type": "string",
+                "value": "new content"
+            }
+        }
+    }
+}
+)json");
+    aArgs = comphelper::containerToSequence(aArgsVec);
+    dispatchCommand(mxComponent, ".uno:UpdateField", aArgs);
+
+    // Then make sure that the document text features the new content:
+    SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell();
+    SwTextNode* pTextNode = 
pWrtShell->GetCursor()->GetPointNode().GetTextNode();
+    // Without the accompanying fix in place, this test would have failed with:
+    // - Expected: new content
+    // - Actual  : old content
+    // i.e. the content was not updated.
+    CPPUNIT_ASSERT_EQUAL(OUString("new content"), pTextNode->GetText());
+}
+
 CPPUNIT_PLUGIN_IMPLEMENT();
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/sdi/_basesh.sdi b/sw/sdi/_basesh.sdi
index dd195496d1a8..5ac4cb001eb3 100644
--- a/sw/sdi/_basesh.sdi
+++ b/sw/sdi/_basesh.sdi
@@ -154,6 +154,11 @@ interface BaseTextSelection
         StateMethod = NoState ;
     ]
 
+    FN_UPDATE_FIELD // status(final|play)
+    [
+        ExecMethod = Execute ;
+    ]
+
     FN_UPDATE_CHARTS // status(final|play)
     [
         ExecMethod = Execute ;
diff --git a/sw/sdi/swriter.sdi b/sw/sdi/swriter.sdi
index ebded3b24b72..0a0073ea5020 100644
--- a/sw/sdi/swriter.sdi
+++ b/sw/sdi/swriter.sdi
@@ -6576,6 +6576,20 @@ SfxVoidItem UpdateFields FN_UPDATE_FIELDS
     GroupId = SfxGroupId::Edit;
 ]
 
+SfxVoidItem UpdateField FN_UPDATE_FIELD
+(SfxStringItem TypeName FN_PARAM_1, SfxStringItem NamePrefix FN_PARAM_2, 
SfxUnoAnyItem Field FN_PARAM_3)
+[
+    AutoUpdate = FALSE,
+    FastCall = TRUE,
+    ReadOnlyDoc = FALSE,
+    Toggle = FALSE,
+    Container = FALSE,
+    RecordAbsolute = FALSE,
+    RecordPerSet;
+
+    GroupId = SfxGroupId::Edit;
+]
+
 SfxVoidItem UpdateInputFields FN_UPDATE_INPUTFIELDS
 ()
 [
diff --git a/sw/source/uibase/shells/basesh.cxx 
b/sw/source/uibase/shells/basesh.cxx
index d6d4ac2ff8bb..2e7489fda6de 100644
--- a/sw/source/uibase/shells/basesh.cxx
+++ b/sw/source/uibase/shells/basesh.cxx
@@ -882,6 +882,105 @@ bool UpdateFieldContents(SfxRequest& rReq, SwWrtShell& 
rWrtSh)
     pDoc->GetIDocumentUndoRedo().EndUndo(SwUndoId::INSBOOKMARK, nullptr);
     return true;
 }
+
+/// Searches for the specified field type and field name prefix under cursor 
and update the matching
+/// field to have the provided new name and content.
+void UpdateFieldContent(SfxRequest& rReq, SwWrtShell& rWrtSh)
+{
+    const SfxStringItem* pTypeName = rReq.GetArg<SfxStringItem>(FN_PARAM_1);
+    if (!pTypeName || pTypeName->GetValue() != "SetRef")
+    {
+        // This is implemented so far only for reference marks.
+        return;
+    }
+
+    const SfxStringItem* pNamePrefix = rReq.GetArg<SfxStringItem>(FN_PARAM_2);
+    if (!pNamePrefix)
+    {
+        return;
+    }
+    const OUString& rNamePrefix = pNamePrefix->GetValue();
+
+    const SfxUnoAnyItem* pField = rReq.GetArg<SfxUnoAnyItem>(FN_PARAM_3);
+    if (!pField)
+    {
+        return;
+    }
+    uno::Sequence<beans::PropertyValue> aField;
+    pField->GetValue() >>= aField;
+
+    SwDoc* pDoc = rWrtSh.GetDoc();
+    pDoc->GetIDocumentUndoRedo().StartUndo(SwUndoId::INSBOOKMARK, nullptr);
+    rWrtSh.StartAction();
+    comphelper::ScopeGuard g(
+        [&rWrtSh]
+        {
+            rWrtSh.EndAction();
+            
rWrtSh.GetDoc()->GetIDocumentUndoRedo().EndUndo(SwUndoId::INSBOOKMARK, nullptr);
+        });
+
+    SwPosition& rCursor = *rWrtSh.GetCursor()->GetPoint();
+    SwTextNode* pTextNode = rCursor.GetNode().GetTextNode();
+    std::vector<SwTextAttr*> aAttrs
+        = pTextNode->GetTextAttrsAt(rCursor.GetContentIndex(), 
RES_TXTATR_REFMARK);
+    if (aAttrs.empty())
+    {
+        return;
+    }
+
+    auto& rRefmark = const_cast<SwFormatRefMark&>(aAttrs[0]->GetRefMark());
+    if (!rRefmark.GetRefName().startsWith(rNamePrefix))
+    {
+        return;
+    }
+
+    comphelper::SequenceAsHashMap aMap(aField);
+    auto aName = aMap["Name"].get<OUString>();
+    rRefmark.GetRefName() = aName;
+
+    OUString aContent = aMap["Content"].get<OUString>();
+    auto pTextRefMark = const_cast<SwTextRefMark*>(rRefmark.GetTextRefMark());
+    if (!pTextRefMark->End())
+    {
+        return;
+    }
+
+    // Insert markers to remember where the paste positions are.
+    const SwTextNode& rTextNode = pTextRefMark->GetTextNode();
+    SwPaM aMarkers(SwPosition(rTextNode, *pTextRefMark->End()));
+    IDocumentContentOperations& rIDCO = pDoc->getIDocumentContentOperations();
+    pTextRefMark->SetDontExpand(false);
+    if (!rIDCO.InsertString(aMarkers, "XY"))
+    {
+        return;
+    }
+
+    SwPaM aPasteEnd(SwPosition(rTextNode, *pTextRefMark->End()));
+    aPasteEnd.Move(fnMoveBackward, GoInContent);
+
+    // Paste HTML content.
+    SwPaM* pCursorPos = rWrtSh.GetCursor();
+    *pCursorPos = aPasteEnd;
+    SwTranslateHelper::PasteHTMLToPaM(rWrtSh, pCursorPos, aContent.toUtf8(), 
true);
+
+    // Update the refmark to point to the new content.
+    sal_Int32 nOldStart = pTextRefMark->GetStart();
+    sal_Int32 nNewStart = *pTextRefMark->End();
+    // First grow it to include text till the end of the paste position.
+    pTextRefMark->SetEnd(aPasteEnd.GetPoint()->GetContentIndex());
+    // Then shrink it to only start at the paste start: we know that the 
refmark was
+    // truncated to the paste start, as the refmark has to stay inside a 
single text node
+    pTextRefMark->SetStart(nNewStart);
+    rTextNode.GetSwpHints().SortIfNeedBe();
+    SwPaM aEndMarker(*aPasteEnd.GetPoint());
+    aEndMarker.SetMark();
+    aEndMarker.GetMark()->AdjustContent(1);
+    SwPaM aStartMarker(SwPosition(rTextNode, nOldStart), SwPosition(rTextNode, 
nNewStart));
+
+    // Remove markers. The start marker includes the old content as well.
+    rIDCO.DeleteAndJoin(aStartMarker);
+    rIDCO.DeleteAndJoin(aEndMarker);
+}
 }
 
 // Evaluate respectively dispatching the slot Id
@@ -926,6 +1025,11 @@ void SwBaseShell::Execute(SfxRequest &rReq)
                 }
             }
             break;
+        case FN_UPDATE_FIELD:
+            {
+                UpdateFieldContent(rReq, rSh);
+            }
+            break;
         case FN_UPDATE_CHARTS:
             {
                 SwWait aWait( *m_rView.GetDocShell(), true );

Reply via email to