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 );
