sw/inc/cmdid.h                      |    3 
 sw/qa/uibase/shells/shells.cxx      |  152 ++++++++++++++++++++++++++++++++++++
 sw/sdi/_basesh.sdi                  |    5 +
 sw/sdi/_textsh.sdi                  |   12 ++
 sw/sdi/swriter.sdi                  |   44 ++++++++++
 sw/source/uibase/shells/basesh.cxx  |  104 ++++++++++++++++++++++++
 sw/source/uibase/shells/textfld.cxx |   12 ++
 sw/source/uibase/shells/textsh1.cxx |  108 +++++++++++++++++++++++++
 8 files changed, 439 insertions(+), 1 deletion(-)

New commits:
commit 53828840843fec1d01dc14d71143fb1d70ab462e
Author:     Miklos Vajna <[email protected]>
AuthorDate: Mon Jan 16 16:34:40 2023 +0100
Commit:     Miklos Vajna <[email protected]>
CommitDate: Mon Feb 6 08:54:43 2023 +0100

    sw: .uno:TextFormField: add new Wrapper parameter
    
    Currently all fieldmarks are inserted into the document body
    unconditionally when this UNO command is dispatched.
    
    Inserting at the current cursor position makes sense, but some citation
    styles want to insert the actual citation as footnotes, and only have
    the footnote anchor at the cursor position.
    
    Fix the problem by adding a new Wrapper parameter to this UNO command:
    currently the only interesting value it may have is Footnote, if this is
    specified then first we insert a footnote and the footnote content will
    host the fieldmark, not the original body text.
    
    The same will be wanted for endnotes as well, but that's not yet done in
    this commit.
    
    (cherry picked from commit ceea8f3924f26d5f10adc41b9ea587c77c2fda74)
    
    Change-Id: I5c96c7dc9ddaace09b1dbc21b8f12005a2934d04

diff --git a/sw/qa/uibase/shells/shells.cxx b/sw/qa/uibase/shells/shells.cxx
index 45bbbfa6f746..98f371f90c45 100644
--- a/sw/qa/uibase/shells/shells.cxx
+++ b/sw/qa/uibase/shells/shells.cxx
@@ -39,6 +39,7 @@
 #include <drawdoc.hxx>
 #include <docsh.hxx>
 #include <ndtxt.hxx>
+#include <ftnidx.hxx>
 
 /// Covers sw/source/uibase/shells/ fixes.
 class SwUibaseShellsTest : public SwModelTestBase
@@ -926,6 +927,31 @@ CPPUNIT_TEST_FIXTURE(SwUibaseShellsTest, testDeleteFields)
     CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(0), pDoc->GetRefMarks());
 }
 
+CPPUNIT_TEST_FIXTURE(SwUibaseShellsTest, testInsertTextFormFieldFootnote)
+{
+    // Given an empty document:
+    createSwDoc();
+    SwDoc* pDoc = getSwDoc();
+
+    // When inserting an ODF_UNHANDLED fieldmark inside a footnote:
+    uno::Sequence<css::beans::PropertyValue> aArgs = {
+        comphelper::makePropertyValue("FieldType", 
uno::Any(OUString(ODF_UNHANDLED))),
+        comphelper::makePropertyValue("FieldCommand",
+                                      uno::Any(OUString("ADDIN ZOTERO_BIBL foo 
bar"))),
+        comphelper::makePropertyValue("FieldResult", 
uno::Any(OUString("result"))),
+        comphelper::makePropertyValue("Wrapper", 
uno::Any(OUString("Footnote"))),
+    };
+    dispatchCommand(mxComponent, ".uno:TextFormField", aArgs);
+
+    // Then make sure that the footnote is created:
+    SwFootnoteIdxs& rFootnotes = pDoc->GetFootnoteIdxs();
+    // Without the accompanying fix in place, this test would have failed with:
+    // - Expected: 1
+    // - Actual  : 0
+    // i.e. no footnote was created.
+    CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rFootnotes.size());
+}
+
 CPPUNIT_PLUGIN_IMPLEMENT();
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/sdi/swriter.sdi b/sw/sdi/swriter.sdi
index 9e6af301940c..8303ebec1da2 100644
--- a/sw/sdi/swriter.sdi
+++ b/sw/sdi/swriter.sdi
@@ -8354,7 +8354,7 @@ SfxBoolItem ShowInlineTooltips FN_SHOW_INLINETOOLTIPS
 ]
 
 SfxVoidItem TextFormField FN_INSERT_TEXT_FORMFIELD
-(SfxStringItem FieldType FN_PARAM_1, SfxStringItem FieldCommand FN_PARAM_2, 
SfxStringItem FieldResult FN_PARAM_3)
+(SfxStringItem FieldType FN_PARAM_1, SfxStringItem FieldCommand FN_PARAM_2, 
SfxStringItem FieldResult FN_PARAM_3, SfxStringItem Wrapper FN_PARAM_4)
 [
     AutoUpdate = TRUE,
     FastCall = FALSE,
diff --git a/sw/source/uibase/shells/textfld.cxx 
b/sw/source/uibase/shells/textfld.cxx
index 7e10b7a8673d..61e99dd71731 100644
--- a/sw/source/uibase/shells/textfld.cxx
+++ b/sw/source/uibase/shells/textfld.cxx
@@ -748,6 +748,18 @@ FIELD_INSERT:
                     aFieldResult = pFieldResult->GetValue();
                 }
 
+                const SfxStringItem* pWrapper = 
rReq.GetArg<SfxStringItem>(FN_PARAM_4);
+                if (pWrapper)
+                {
+                    // Wrap the fieldmark in the requested container instead 
of inserting it
+                    // directly at the cursor position.
+                    OUString aWrapper = pWrapper->GetValue();
+                    if (aWrapper == "Footnote")
+                    {
+                        rSh.InsertFootnote(OUString());
+                    }
+                }
+
                 // Split node to remember where the start position is.
                 bool bSuccess = 
rSh.GetDoc()->getIDocumentContentOperations().SplitNode(
                     *pCursorPos->GetPoint(), false);
commit 4159f73231a00c2f085b6df11c2107c8521c2515
Author:     Miklos Vajna <[email protected]>
AuthorDate: Mon Jan 16 08:10:16 2023 +0100
Commit:     Miklos Vajna <[email protected]>
CommitDate: Mon Feb 6 08:52:37 2023 +0100

    sw: add a new .uno:DeleteFields UNO command
    
    This is similar to 40753de837b9776dd8b33e830be0cceef83f024a (sw: add a
    new .uno:DeleteBookmarks UNO command, 2023-01-13), but that was about
    deleting bookmarks matching a given prefix with their name, and this one
    is about reference marks (fields in general), matching a certain type &
    prefix with their name.
    
    (cherry picked from commit 1d6593dd799ff4eb931ffbb5338e4856fb87f77f)
    
    Change-Id: Iec953034cd0e6875f173712b0fb10bfddf16ed3f

diff --git a/sw/inc/cmdid.h b/sw/inc/cmdid.h
index 73059db65620..c44745a0e861 100644
--- a/sw/inc/cmdid.h
+++ b/sw/inc/cmdid.h
@@ -328,6 +328,7 @@ class SwUINumRuleItem;
 #define FN_UPDATE_BOOKMARK (FN_INSERT2 + 37)
 #define FN_UPDATE_FIELD (FN_INSERT2 + 38)
 #define FN_DELETE_BOOKMARKS (FN_INSERT2 + 39)
+#define FN_DELETE_FIELDS (FN_INSERT2 + 40)
 
 // 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 f3faad673edd..45bbbfa6f746 100644
--- a/sw/qa/uibase/shells/shells.cxx
+++ b/sw/qa/uibase/shells/shells.cxx
@@ -889,6 +889,43 @@ CPPUNIT_TEST_FIXTURE(SwUibaseShellsTest, 
testDeleteBookmarks)
     CPPUNIT_ASSERT(it != pDoc->getIDocumentMarkAccess()->getAllMarksEnd());
 }
 
+CPPUNIT_TEST_FIXTURE(SwUibaseShellsTest, testDeleteFields)
+{
+    // Given a document with a refmark:
+    createSwDoc();
+    uno::Sequence<css::beans::PropertyValue> aArgs = {
+        comphelper::makePropertyValue("TypeName", 
uno::Any(OUString("SetRef"))),
+        comphelper::makePropertyValue(
+            "Name", uno::Any(OUString("ZOTERO_ITEM CSL_CITATION {} 
RNDpyJknp173F"))),
+        comphelper::makePropertyValue("Content", 
uno::Any(OUString("aaa<b>bbb</b>ccc"))),
+    };
+    dispatchCommand(mxComponent, ".uno:InsertField", aArgs);
+
+    // When deleting the refmarks:
+    std::vector<beans::PropertyValue> aArgsVec = 
comphelper::JsonToPropertyValues(R"json(
+{
+    "TypeName": {
+        "type": "string",
+        "value": "SetRef"
+    },
+    "NamePrefix": {
+        "type": "string",
+        "value": "ZOTERO_ITEM CSL_CITATION"
+    }
+}
+)json");
+    aArgs = comphelper::containerToSequence(aArgsVec);
+    dispatchCommand(mxComponent, ".uno:DeleteFields", aArgs);
+
+    // Then make sure that no refmark is kept:
+    SwDoc* pDoc = getSwDoc();
+    // Without the accompanying fix in place, this test would have failed with:
+    // - Expected: 0
+    // - Actual  : 1
+    // i.e. the refmark was not deleted.
+    CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(0), pDoc->GetRefMarks());
+}
+
 CPPUNIT_PLUGIN_IMPLEMENT();
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/sdi/_textsh.sdi b/sw/sdi/_textsh.sdi
index 5bee5038d725..88c77215fc40 100644
--- a/sw/sdi/_textsh.sdi
+++ b/sw/sdi/_textsh.sdi
@@ -1843,6 +1843,12 @@ interface BaseText
         DisableFlags="SfxDisableFlags::SwOnProtectedCursor";
     ]
 
+    FN_DELETE_FIELDS
+    [
+        ExecMethod = Execute ;
+        DisableFlags="SfxDisableFlags::SwOnProtectedCursor";
+    ]
+
     SID_FM_CTL_PROPERTIES
     [
         ExecMethod = Execute ;
diff --git a/sw/sdi/swriter.sdi b/sw/sdi/swriter.sdi
index 0789351ea4b9..9e6af301940c 100644
--- a/sw/sdi/swriter.sdi
+++ b/sw/sdi/swriter.sdi
@@ -2614,6 +2614,20 @@ SfxVoidItem DeleteBookmarks FN_DELETE_BOOKMARKS
     GroupId = SfxGroupId::Controls;
 ]
 
+SfxVoidItem DeleteFields FN_DELETE_FIELDS
+(SfxStringItem TypeName FN_PARAM_1, SfxStringItem NamePrefix FN_PARAM_2)
+[
+    AutoUpdate = TRUE,
+    FastCall = FALSE,
+    ReadOnlyDoc = FALSE,
+    Toggle = FALSE,
+    Container = FALSE,
+    RecordAbsolute = FALSE,
+    RecordPerSet;
+
+    GroupId = SfxGroupId::Controls;
+]
+
 SfxVoidItem UpdateBookmark FN_UPDATE_BOOKMARK
 (SfxStringItem BookmarkNamePrefix FN_PARAM_1, SfxUnoAnyItem Bookmark 
FN_PARAM_2)
 [
diff --git a/sw/source/uibase/shells/textsh1.cxx 
b/sw/source/uibase/shells/textsh1.cxx
index c7850117f7bc..c95a78f4d4be 100644
--- a/sw/source/uibase/shells/textsh1.cxx
+++ b/sw/source/uibase/shells/textsh1.cxx
@@ -116,6 +116,7 @@
 #include <IDocumentContentOperations.hxx>
 #include <IDocumentUndoRedo.hxx>
 #include <fmtcntnt.hxx>
+#include <fmtrfmrk.hxx>
 
 using namespace ::com::sun::star;
 using namespace com::sun::star::beans;
@@ -660,6 +661,53 @@ void DeleteBookmarks(SfxRequest& rReq, SwWrtShell& rWrtSh)
         pMarkAccess->deleteMark(pMark);
     }
 }
+
+void DeleteFields(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;
+    }
+
+    OUString aNamePrefix;
+    const SfxStringItem* pNamePrefix = rReq.GetArg<SfxStringItem>(FN_PARAM_2);
+    if (pNamePrefix)
+    {
+        aNamePrefix = pNamePrefix->GetValue();
+    }
+
+    SwDoc* pDoc = rWrtSh.GetDoc();
+    pDoc->GetIDocumentUndoRedo().StartUndo(SwUndoId::DELBOOKMARK, nullptr);
+    rWrtSh.StartAction();
+    comphelper::ScopeGuard g(
+        [&rWrtSh]
+        {
+            rWrtSh.EndAction();
+            
rWrtSh.GetDoc()->GetIDocumentUndoRedo().EndUndo(SwUndoId::DELBOOKMARK, nullptr);
+        });
+
+    std::vector<const SwFormatRefMark*> aRemovals;
+    for (sal_uInt16 i = 0; i < pDoc->GetRefMarks(); ++i)
+    {
+        const SwFormatRefMark* pRefMark = pDoc->GetRefMark(i);
+        if (!aNamePrefix.isEmpty())
+        {
+            if (!pRefMark->GetRefName().startsWith(aNamePrefix))
+            {
+                continue;
+            }
+        }
+
+        aRemovals.push_back(pRefMark);
+    }
+
+    for (const auto& pMark : aRemovals)
+    {
+        pDoc->DeleteFormatRefMark(pMark);
+    }
+}
 }
 
 void SwTextShell::Execute(SfxRequest &rReq)
@@ -1068,6 +1116,12 @@ void SwTextShell::Execute(SfxRequest &rReq)
             DeleteBookmarks(rReq, rWrtSh);
             break;
         }
+        case FN_DELETE_FIELDS:
+        {
+            // This deletes all fields in the document matching a specified 
type & prefix.
+            DeleteFields(rReq, rWrtSh);
+            break;
+        }
         case FN_UPDATE_SECTIONS:
         {
             UpdateSections(rReq, rWrtSh);
commit 3c8befe7d40cd1b25029e697d53d5fb94089847e
Author:     Miklos Vajna <[email protected]>
AuthorDate: Fri Jan 13 11:05:52 2023 +0100
Commit:     Miklos Vajna <[email protected]>
CommitDate: Mon Feb 6 08:44:52 2023 +0100

    sw: add a new .uno:DeleteBookmarks UNO command
    
    This is similar to commit c68d06dfa1498f862923eaddf3e5d247650a53d5 (sw:
    add a new .uno:DeleteTextFormFields UNO command, 2023-01-10), but that
    was for fieldmarks and this is for bookmarks.
    
    The primary use-case is to specify a prefix for the name, so e.g. all
    Zotero-related bookmarks can be removed, but it can be also used to
    remove all bookmarks when no prefix is specified.
    
    (cherry picked from commit 40753de837b9776dd8b33e830be0cceef83f024a)
    
    Change-Id: Ifc1f666f66d9fc3f6cd055f9263f0e4f949c191d

diff --git a/sw/inc/cmdid.h b/sw/inc/cmdid.h
index 96d935a3bd2e..73059db65620 100644
--- a/sw/inc/cmdid.h
+++ b/sw/inc/cmdid.h
@@ -327,6 +327,7 @@ class SwUINumRuleItem;
 #define FN_DELETE_TEXT_FORMFIELDS (FN_INSERT2 + 36)
 #define FN_UPDATE_BOOKMARK (FN_INSERT2 + 37)
 #define FN_UPDATE_FIELD (FN_INSERT2 + 38)
+#define FN_DELETE_BOOKMARKS (FN_INSERT2 + 39)
 
 // 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 52455dd4b02f..f3faad673edd 100644
--- a/sw/qa/uibase/shells/shells.cxx
+++ b/sw/qa/uibase/shells/shells.cxx
@@ -852,6 +852,43 @@ CPPUNIT_TEST_FIXTURE(SwUibaseShellsTest, testUpdateRefmark)
     CPPUNIT_ASSERT_EQUAL(OUString("new content"), pTextNode->GetText());
 }
 
+CPPUNIT_TEST_FIXTURE(SwUibaseShellsTest, testDeleteBookmarks)
+{
+    // Given a document with 2 bookmarks, first covering "B" and second 
covering "D":
+    createSwDoc();
+    SwDoc* pDoc = getSwDoc();
+    SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell();
+    pWrtShell->Insert("ABCDE");
+    pWrtShell->SttEndDoc(/*bStt=*/true);
+    pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/false, 1, 
/*bBasicCall=*/false);
+    pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/true, 1, 
/*bBasicCall=*/false);
+    pWrtShell->SetBookmark(vcl::KeyCode(), "ZOTERO_BREF_GiQ7DAWQYWLy");
+    pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/false, 1, 
/*bBasicCall=*/false);
+    pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/true, 1, 
/*bBasicCall=*/false);
+    pWrtShell->SetBookmark(vcl::KeyCode(), "other");
+
+    // When deleting 1 matching bookmark:
+    pWrtShell->SttEndDoc(/*bStt=*/true);
+    std::vector<beans::PropertyValue> aArgsVec = 
comphelper::JsonToPropertyValues(R"json(
+{
+    "BookmarkNamePrefix": {
+        "type": "string",
+        "value": "ZOTERO_BREF_"
+    }
+}
+)json");
+    uno::Sequence<beans::PropertyValue> aArgs = 
comphelper::containerToSequence(aArgsVec);
+    dispatchCommand(mxComponent, ".uno:DeleteBookmarks", aArgs);
+
+    // Then make sure that only the other bookmark is kept:
+    auto it = 
pDoc->getIDocumentMarkAccess()->findMark("ZOTERO_BREF_GiQ7DAWQYWLy");
+    // Without the accompanying fix in place, this test would have failed, the 
matching bookmark was
+    // not removed.
+    CPPUNIT_ASSERT(bool(it == 
pDoc->getIDocumentMarkAccess()->getAllMarksEnd()));
+    it = pDoc->getIDocumentMarkAccess()->findMark("other");
+    CPPUNIT_ASSERT(it != pDoc->getIDocumentMarkAccess()->getAllMarksEnd());
+}
+
 CPPUNIT_PLUGIN_IMPLEMENT();
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/sdi/_textsh.sdi b/sw/sdi/_textsh.sdi
index 1098a9f14278..5bee5038d725 100644
--- a/sw/sdi/_textsh.sdi
+++ b/sw/sdi/_textsh.sdi
@@ -1837,6 +1837,12 @@ interface BaseText
         StateMethod = GetState ;
     ]
 
+    FN_DELETE_BOOKMARKS
+    [
+        ExecMethod = Execute ;
+        DisableFlags="SfxDisableFlags::SwOnProtectedCursor";
+    ]
+
     SID_FM_CTL_PROPERTIES
     [
         ExecMethod = Execute ;
diff --git a/sw/sdi/swriter.sdi b/sw/sdi/swriter.sdi
index 1e1831391292..0789351ea4b9 100644
--- a/sw/sdi/swriter.sdi
+++ b/sw/sdi/swriter.sdi
@@ -2600,6 +2600,20 @@ SfxVoidItem UpdateBookmarks FN_UPDATE_BOOKMARKS
     GroupId = SfxGroupId::Insert;
 ]
 
+SfxVoidItem DeleteBookmarks FN_DELETE_BOOKMARKS
+(SfxStringItem BookmarkNamePrefix FN_PARAM_1)
+[
+    AutoUpdate = TRUE,
+    FastCall = FALSE,
+    ReadOnlyDoc = FALSE,
+    Toggle = FALSE,
+    Container = FALSE,
+    RecordAbsolute = FALSE,
+    RecordPerSet;
+
+    GroupId = SfxGroupId::Controls;
+]
+
 SfxVoidItem UpdateBookmark FN_UPDATE_BOOKMARK
 (SfxStringItem BookmarkNamePrefix FN_PARAM_1, SfxUnoAnyItem Bookmark 
FN_PARAM_2)
 [
diff --git a/sw/source/uibase/shells/textsh1.cxx 
b/sw/source/uibase/shells/textsh1.cxx
index 313b34ca6265..c7850117f7bc 100644
--- a/sw/source/uibase/shells/textsh1.cxx
+++ b/sw/source/uibase/shells/textsh1.cxx
@@ -613,6 +613,53 @@ void UpdateBookmark(SfxRequest& rReq, SwWrtShell& rWrtSh)
     rIDCO.DeleteAndJoin(aEndMarker);
     rIDMA.assureSortedMarkContainers();
 }
+
+void DeleteBookmarks(SfxRequest& rReq, SwWrtShell& rWrtSh)
+{
+    if 
(rWrtSh.getIDocumentSettingAccess().get(DocumentSettingId::PROTECT_BOOKMARKS))
+    {
+        return;
+    }
+
+    OUString aBookmarkNamePrefix;
+    const SfxStringItem* pBookmarkNamePrefix = 
rReq.GetArg<SfxStringItem>(FN_PARAM_1);
+    if (pBookmarkNamePrefix)
+    {
+        aBookmarkNamePrefix = pBookmarkNamePrefix->GetValue();
+    }
+
+    rWrtSh.GetDoc()->GetIDocumentUndoRedo().StartUndo(SwUndoId::DELBOOKMARK, 
nullptr);
+    rWrtSh.StartAction();
+    comphelper::ScopeGuard g(
+        [&rWrtSh]
+        {
+            rWrtSh.EndAction();
+            
rWrtSh.GetDoc()->GetIDocumentUndoRedo().EndUndo(SwUndoId::DELBOOKMARK, nullptr);
+        });
+
+    IDocumentMarkAccess* pMarkAccess = 
rWrtSh.GetDoc()->getIDocumentMarkAccess();
+    std::vector<sw::mark::IMark*> aRemovals;
+    for (auto it = pMarkAccess->getBookmarksBegin(); it != 
pMarkAccess->getBookmarksEnd(); ++it)
+    {
+        auto pBookmark = dynamic_cast<sw::mark::Bookmark*>(*it);
+        assert(pBookmark);
+
+        if (!aBookmarkNamePrefix.isEmpty())
+        {
+            if (!pBookmark->GetName().startsWith(aBookmarkNamePrefix))
+            {
+                continue;
+            }
+        }
+
+        aRemovals.push_back(pBookmark);
+    }
+
+    for (const auto& pMark : aRemovals)
+    {
+        pMarkAccess->deleteMark(pMark);
+    }
+}
 }
 
 void SwTextShell::Execute(SfxRequest &rReq)
@@ -1007,6 +1054,7 @@ void SwTextShell::Execute(SfxRequest &rReq)
         }
         case FN_DELETE_BOOKMARK:
         {
+            // This deletes a bookmark with the specified name.
             if (pItem && 
!rWrtSh.getIDocumentSettingAccess().get(DocumentSettingId::PROTECT_BOOKMARKS))
             {
                 IDocumentMarkAccess* const pMarkAccess = 
rWrtSh.getIDocumentMarkAccess();
@@ -1014,6 +1062,12 @@ void SwTextShell::Execute(SfxRequest &rReq)
             }
             break;
         }
+        case FN_DELETE_BOOKMARKS:
+        {
+            // This deletes all bookmarks in the document matching a specified 
prefix.
+            DeleteBookmarks(rReq, rWrtSh);
+            break;
+        }
         case FN_UPDATE_SECTIONS:
         {
             UpdateSections(rReq, rWrtSh);
commit 2b515f36bf19d02a8b645a15c8d6d1a9cdd94907
Author:     Miklos Vajna <[email protected]>
AuthorDate: Fri Jan 13 08:20:48 2023 +0100
Commit:     Miklos Vajna <[email protected]>
CommitDate: Mon Feb 6 08:43:10 2023 +0100

    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).
    
    (cherry picked from commit 402ab3d145a1e8e123caabf4567aef7b6631fc3c)
    
    Change-Id: I3e547b668361898b7ed734ea325fdf1d74e5dbb2

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 7bbfd83b6c06..52455dd4b02f 100644
--- a/sw/qa/uibase/shells/shells.cxx
+++ b/sw/qa/uibase/shells/shells.cxx
@@ -800,6 +800,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 dea7f3be4afd..a1468ffac8b1 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 23699a4e457a..1e1831391292 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 af3673ef8a87..e23d2414c305 100644
--- a/sw/source/uibase/shells/basesh.cxx
+++ b/sw/source/uibase/shells/basesh.cxx
@@ -868,6 +868,105 @@ bool UpdateFieldConents(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
@@ -912,6 +1011,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