sw/qa/uibase/shells/shells.cxx | 62 +++++++++++++++++++++ sw/sdi/swriter.sdi | 2 sw/source/uibase/shells/basesh.cxx | 109 +++++++++++++++++++++++++++++++++++++ 3 files changed, 172 insertions(+), 1 deletion(-)
New commits: commit 87add1240a011b05f7501b902ae4edfa8fbe7367 Author: Miklos Vajna <vmik...@collabora.com> AuthorDate: Wed Jan 4 08:56:04 2023 +0100 Commit: Miklos Vajna <vmik...@collabora.com> CommitDate: Fri Jan 6 14:55:50 2023 +0000 sw, UpdateFields: add new TypeName, NamePrefix and Fields parameters Currently the .uno:InsertField command allows inserting a refmark with a provided name & content, but existing refmarks can't be updated similarly. This is a problem in case Zotero citations are to be modeled with refmarks. Another trouble is that refmarks don't have dummy characters and have to stay inside a single paragraph, so we need to be careful to replace the content in a way that keeps the refmark alive, a naive delete + insert will delete the refmark as well. Fix the problem by extending the existing .uno:UpdateFields command with 3 new optional parameters, somewhat similar to what commit 724180ec495a696c79332653cb6fb52ecfbccc29 (sw: add a new .uno:UpdateBookmarks UNO command, 2022-12-14) did. As usual, the provided new text is meant to be HTML, which allows formatted content. (cherry picked from commit babba472391d26aed68d7ac31c7a918c08e65256) Also contains: sw update of refmarks: fix handling of ignored refmarks As pointed out at <https://gerrit.libreoffice.org/c/core/+/145036/1#message-165c0282ace92160592c896c6867095d3558ee96>, it's not correct that UpdateFieldConents() uses a single index into both the provided fields/refmarks array and into the document's all refmarks array. We need a new index that counts the input fields/refmarks we got and which is not incremented for ignored refmarks. Extend the testcase to have 2 paragraphs in the document: the first paragraph has a refmark that is to be ignored and the second para has a refmark that is interesting to us. This makes the test fail. Then fix up UpdateFieldConents() to properly count how we iterate through all refmarks and the provided refmarks in parallel. (cherry picked from commit 471804e251b4e15b37a10920bd4b88b40f97b227) Conflicts: sw/qa/uibase/shells/shells.cxx Also contains: sw: UpdateFieldContents: fix typos - The function name itself had a typo, spotted at <https://gerrit.libreoffice.org/c/core/+/145036/2#message-d8cb4de7de483866e0c86c8919cdf47f84b6037e>. - Also, if the # of provided fields and # of fields we find in the document don't match, we can give up, so no need to continue, the same condition would fail again, anyway. (cherry picked from commit f83c1353b94fc7dec79d05ac45c11f40f497261d) Change-Id: Ib0951aa1a39e1b47bcf8b47bc9d65c89e0853e96 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/145036 Tested-by: Miklos Vajna <vmik...@collabora.com> Reviewed-by: Justin Luth <jl...@mail.com> Reviewed-by: Miklos Vajna <vmik...@collabora.com> diff --git a/sw/qa/uibase/shells/shells.cxx b/sw/qa/uibase/shells/shells.cxx index 88324d12327e..ced0157284ae 100644 --- a/sw/qa/uibase/shells/shells.cxx +++ b/sw/qa/uibase/shells/shells.cxx @@ -469,6 +469,68 @@ CPPUNIT_TEST_FIXTURE(SwUibaseShellsTest, testInsertFieldmarkReadonly) CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), nActual); } +CPPUNIT_TEST_FIXTURE(SwUibaseShellsTest, testUpdateRefmarks) +{ + // Given a document with two refmarks, one is not interesting the other is a citation: + SwDoc* pDoc = createSwDoc(); + uno::Sequence<css::beans::PropertyValue> aArgs = { + comphelper::makePropertyValue("TypeName", uno::Any(OUString("SetRef"))), + comphelper::makePropertyValue("Name", uno::Any(OUString("some other old refmark"))), + comphelper::makePropertyValue("Content", uno::Any(OUString("some other old content"))), + }; + dispatchCommand(mxComponent, ".uno:InsertField", aArgs); + SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + pWrtShell->SttEndDoc(/*bStt=*/false); + pWrtShell->SplitNode(); + pWrtShell->SttEndDoc(/*bStt=*/false); + 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" + }, + "Fields": { + "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:UpdateFields", aArgs); + + // Then make sure that the document text features the new content: + SwTextNode* pTextNode = pWrtShell->GetCursor()->GetNode().GetTextNode(); + // Without the accompanying fix in place, this test would have failed with: + // - Expected: new content + // - Actual : old content + // i.e. the doc 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/swriter.sdi b/sw/sdi/swriter.sdi index 11ecc7c465e1..2c644638443e 100644 --- a/sw/sdi/swriter.sdi +++ b/sw/sdi/swriter.sdi @@ -6482,7 +6482,7 @@ SfxVoidItem UpdateCurIndex FN_UPDATE_CUR_TOX ] SfxVoidItem UpdateFields FN_UPDATE_FIELDS -() +(SfxStringItem TypeName FN_PARAM_1, SfxStringItem NamePrefix FN_PARAM_2, SfxUnoAnyItem Fields FN_PARAM_3) [ AutoUpdate = FALSE, FastCall = TRUE, diff --git a/sw/source/uibase/shells/basesh.cxx b/sw/source/uibase/shells/basesh.cxx index a5ea0e6b1012..0e2f6769167c 100644 --- a/sw/source/uibase/shells/basesh.cxx +++ b/sw/source/uibase/shells/basesh.cxx @@ -89,6 +89,7 @@ #include <svx/galleryitem.hxx> #include <sfx2/devtools/DevelopmentToolChildWindow.hxx> #include <com/sun/star/gallery/GalleryItemType.hpp> +#include <com/sun/star/beans/PropertyValues.hpp> #include <memory> #include <svx/unobrushitemhelper.hxx> @@ -97,12 +98,16 @@ #include <osl/diagnose.h> #include <svx/svxdlg.hxx> +#include <comphelper/sequenceashashmap.hxx> #include <shellres.hxx> #include <UndoTable.hxx> #include <ndtxt.hxx> #include <UndoManager.hxx> +#include <fmtrfmrk.hxx> +#include <txtrfmrk.hxx> +#include <translatehelper.hxx> FlyMode SwBaseShell::eFrameMode = FLY_DRAG_END; @@ -763,6 +768,103 @@ void SwBaseShell::StateUndo(SfxItemSet &rSet) } } +namespace +{ +/// Searches for the specified field type and field name prefix and update the matching fields to +/// have the provided new name and content. +bool UpdateFieldContents(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 false; + } + + const SfxStringItem* pNamePrefix = rReq.GetArg<SfxStringItem>(FN_PARAM_2); + if (!pNamePrefix) + { + return false; + } + const OUString& rNamePrefix = pNamePrefix->GetValue(); + + const SfxUnoAnyItem* pFields = rReq.GetArg<SfxUnoAnyItem>(FN_PARAM_3); + if (!pFields) + { + return false; + } + uno::Sequence<beans::PropertyValues> aFields; + pFields->GetValue() >>= aFields; + + SwDoc* pDoc = rWrtSh.GetDoc(); + pDoc->GetIDocumentUndoRedo().StartUndo(SwUndoId::INSBOOKMARK, nullptr); + rWrtSh.StartAction(); + sal_uInt16 nFieldIndex = 0; + for (sal_uInt16 nRefMark = 0; nRefMark < pDoc->GetRefMarks(); ++nRefMark) + { + auto pRefMark = const_cast<SwFormatRefMark*>(pDoc->GetRefMark(nRefMark)); + if (!pRefMark->GetRefName().startsWith(rNamePrefix)) + { + continue; + } + + if (nFieldIndex >= aFields.getLength()) + { + break; + } + comphelper::SequenceAsHashMap aMap(aFields[nFieldIndex++]); + auto aName = aMap["Name"].get<OUString>(); + pRefMark->GetRefName() = aName; + + OUString aContent = aMap["Content"].get<OUString>(); + auto pTextRefMark = const_cast<SwTextRefMark*>(pRefMark->GetTextRefMark()); + if (!pTextRefMark->End()) + { + continue; + } + + // Insert markers to remember where the paste positions are. + const SwTextNode& rTextNode = pTextRefMark->GetTextNode(); + SwPaM aMarkers(SwPosition(const_cast<SwTextNode&>(rTextNode), *pTextRefMark->End())); + IDocumentContentOperations& rIDCO = pDoc->getIDocumentContentOperations(); + pTextRefMark->SetDontExpand(false); + bool bSuccess = rIDCO.InsertString(aMarkers, "XY"); + if (bSuccess) + { + SwPaM aPasteEnd(SwPosition(const_cast<SwTextNode&>(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()->nContent.GetIndex()); + // 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()->nContent += 1; + SwPaM aStartMarker(SwPosition(const_cast<SwTextNode&>(rTextNode), nOldStart), SwPosition(const_cast<SwTextNode&>(rTextNode), nNewStart)); + + // Remove markers. The start marker includes the old content as well. + rIDCO.DeleteAndJoin(aStartMarker); + rIDCO.DeleteAndJoin(aEndMarker); + } + } + + rWrtSh.EndAction(); + pDoc->GetIDocumentUndoRedo().EndUndo(SwUndoId::INSBOOKMARK, nullptr); + return true; +} +} + // Evaluate respectively dispatching the slot Id void SwBaseShell::Execute(SfxRequest &rReq) @@ -785,6 +887,13 @@ void SwBaseShell::Execute(SfxRequest &rReq) break; case FN_UPDATE_FIELDS: { + if (UpdateFieldContents(rReq, rSh)) + { + // Parameters indicated that the name / content of fields has to be updated to + // the provided values, don't do an actual fields update. + break; + } + rSh.UpdateDocStat(); rSh.EndAllTableBoxEdit(); rSh.SwViewShell::UpdateFields(true);