sw/inc/cmdid.h | 2 sw/qa/uibase/shells/shells.cxx | 65 +++++++++++++++++++++++++ sw/sdi/_textsh.sdi | 6 ++ sw/sdi/swriter.sdi | 14 +++++ sw/source/uibase/shells/textsh1.cxx | 93 ++++++++++++++++++++++++++++++++++++ 5 files changed, 180 insertions(+)
New commits: commit 724180ec495a696c79332653cb6fb52ecfbccc29 Author: Miklos Vajna <vmik...@collabora.com> AuthorDate: Wed Dec 14 16:06:37 2022 +0100 Commit: Miklos Vajna <vmik...@collabora.com> CommitDate: Wed Dec 14 19:17:56 2022 +0000 sw: add a new .uno:UpdateBookmarks UNO command Current the .uno:InsertBookmark command allows inserting a bookmark with a provided content into the document, but an existing bookmark can't be updated similarly. This is a problem in case Zotero citations are to be modeled with bookmarks. Another trouble is that bookmarks don't have dummy characters, so we need to be careful to replace the content in a way that maintains these bookmarks, a naive delete + insert will collapse them. Fix the problem by introducing a new .uno:UpdateBookmarks command, somewhat similar to what commit 7765b442e13048f857fd7ee49ced1731caee297e (sw: add a new .uno:TextFormFields UNO command, 2022-11-28) did. As usual, the provided new text is meant to be HTML, which allows multi-paragraph, formatted content. Change-Id: Id5bb3fd2fd9e147d98709b5a7e0c322e73c6d283 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/144167 Reviewed-by: Miklos Vajna <vmik...@collabora.com> Tested-by: Jenkins diff --git a/sw/inc/cmdid.h b/sw/inc/cmdid.h index cb21ddb5b441..004a40d8d89f 100644 --- a/sw/inc/cmdid.h +++ b/sw/inc/cmdid.h @@ -203,6 +203,7 @@ class SwUINumRuleItem; // Region: Insert #define FN_INSERT_BOOKMARK (FN_INSERT + 2 ) /* Bookmark */ +// FN_INSERT + 3 is FN_INSERT_BREAK #define FN_INSERT_BREAK_DLG (FN_INSERT + 4 ) /* Break */ #define FN_INSERT_COLUMN_BREAK (FN_INSERT + 5 ) /* Column break */ @@ -319,6 +320,7 @@ class SwUINumRuleItem; #define FN_TABLE_PASTE_COL_BEFORE (FN_INSERT2 + 32) /* paste table as new table columns */ #define FN_EDIT_BOOKMARK (FN_INSERT2 + 33 ) /* Bookmark */ +#define FN_UPDATE_BOOKMARKS (FN_INSERT2 + 34) // 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 82ad67b8022c..a6603984afbb 100644 --- a/sw/qa/uibase/shells/shells.cxx +++ b/sw/qa/uibase/shells/shells.cxx @@ -27,6 +27,8 @@ #include <unotools/ucbstreamhelper.hxx> #include <xmloff/odffields.hxx> #include <comphelper/string.hxx> +#include <comphelper/propertysequence.hxx> +#include <comphelper/sequence.hxx> #include <IDocumentContentOperations.hxx> #include <cmdid.h> @@ -436,6 +438,69 @@ CPPUNIT_TEST_FIXTURE(SwUibaseShellsTest, testGotoMark) CPPUNIT_ASSERT_EQUAL(nExpected, nActual); } +CPPUNIT_TEST_FIXTURE(SwUibaseShellsTest, testUpdateBookmarks) +{ + // 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(), "ZOTERO_BREF_PRxDGUb4SWXF"); + + // When updating the content of bookmarks: + pWrtShell->SttEndDoc(/*bStt=*/true); + std::vector<beans::PropertyValue> aArgsVec = comphelper::JsonToPropertyValues(R"json( +{ + "BookmarkNamePrefix": { + "type": "string", + "value": "ZOTERO_BREF_" + }, + "Bookmarks": { + "type": "[][]com.sun.star.beans.PropertyValue", + "value": [ + { + "Bookmark": { + "type": "string", + "value": "ZOTERO_BREF_GiQ7DAWQYWLy" + }, + "BookmarkText": { + "type": "string", + "value": "new result 1" + } + }, + { + "Bookmark": { + "type": "string", + "value": "ZOTERO_BREF_PRxDGUb4SWXF" + }, + "BookmarkText": { + "type": "string", + "value": "new result 2" + } + } + ] + } +} +)json"); + uno::Sequence<beans::PropertyValue> aArgs = comphelper::containerToSequence(aArgsVec); + dispatchCommand(mxComponent, ".uno:UpdateBookmarks", aArgs); + + // Then make sure that the only paragraph is updated correctly: + SwCursor* pCursor = pWrtShell->GetCursor(); + OUString aActual = pCursor->GetPointNode().GetTextNode()->GetText(); + // Without the accompanying fix in place, this test would have failed with: + // - Expected: Anew result 1Cnew result 2E + // - Actual : ABCDE + // i.e. the content was not updated. + CPPUNIT_ASSERT_EQUAL(OUString("Anew result 1Cnew result 2E"), aActual); +} + CPPUNIT_PLUGIN_IMPLEMENT(); /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/sdi/_textsh.sdi b/sw/sdi/_textsh.sdi index 98e32d3d58dc..f9b3c84cd207 100644 --- a/sw/sdi/_textsh.sdi +++ b/sw/sdi/_textsh.sdi @@ -146,6 +146,12 @@ interface BaseText StateMethod = GetState ; DisableFlags="SfxDisableFlags::SwOnProtectedCursor"; ] + FN_UPDATE_BOOKMARKS + [ + ExecMethod = Execute ; + StateMethod = GetState ; + DisableFlags="SfxDisableFlags::SwOnProtectedCursor"; + ] FN_SET_REMINDER [ ExecMethod = Execute ; diff --git a/sw/sdi/swriter.sdi b/sw/sdi/swriter.sdi index 2471dfcd9fb6..37cb0d36e029 100644 --- a/sw/sdi/swriter.sdi +++ b/sw/sdi/swriter.sdi @@ -2586,6 +2586,20 @@ SfxVoidItem EditBookmark FN_EDIT_BOOKMARK GroupId = SfxGroupId::Insert; ] +SfxVoidItem UpdateBookmarks FN_UPDATE_BOOKMARKS +(SfxStringItem BookmarkNamePrefix FN_PARAM_1, SfxUnoAnyItem Bookmarks FN_PARAM_2) +[ + AutoUpdate = FALSE, + FastCall = FALSE, + ReadOnlyDoc = FALSE, + Toggle = FALSE, + Container = FALSE, + RecordAbsolute = FALSE, + RecordPerSet; + + GroupId = SfxGroupId::Insert; +] + SfxVoidItem SetReminder FN_SET_REMINDER [ diff --git a/sw/source/uibase/shells/textsh1.cxx b/sw/source/uibase/shells/textsh1.cxx index 51d663e33715..42ef624e225c 100644 --- a/sw/source/uibase/shells/textsh1.cxx +++ b/sw/source/uibase/shells/textsh1.cxx @@ -102,6 +102,7 @@ #include <xmloff/odffields.hxx> #include <bookmark.hxx> #include <linguistic/misc.hxx> +#include <comphelper/sequenceashashmap.hxx> #include <authfld.hxx> #include <config_wasm_strip.h> #if !ENABLE_WASM_STRIP_EXTRA @@ -110,6 +111,7 @@ #endif // ENABLE_WASM_STRIP_EXTRA #include <translatehelper.hxx> #include <IDocumentContentOperations.hxx> +#include <IDocumentUndoRedo.hxx> using namespace ::com::sun::star; using namespace com::sun::star::beans; @@ -377,6 +379,92 @@ OUString GetLocalURL(const SwWrtShell& rSh) return rLocalURL; } +void UpdateBookmarks(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(); + } + + uno::Sequence<beans::PropertyValues> aBookmarks; + const SfxUnoAnyItem* pBookmarks = rReq.GetArg<SfxUnoAnyItem>(FN_PARAM_2); + if (pBookmarks) + { + pBookmarks->GetValue() >>= aBookmarks; + } + + rWrtSh.GetDoc()->GetIDocumentUndoRedo().StartUndo(SwUndoId::INSBOOKMARK, nullptr); + rWrtSh.StartAction(); + + IDocumentMarkAccess& rIDMA = *rWrtSh.GetDoc()->getIDocumentMarkAccess(); + sal_Int32 nBookmarkIndex = 0; + bool bSortMarks = false; + for (auto it = rIDMA.getBookmarksBegin(); it != rIDMA.getBookmarksEnd(); ++it) + { + auto pMark = dynamic_cast<sw::mark::Bookmark*>(*it); + assert(pMark); + if (!pMark->GetName().startsWith(aBookmarkNamePrefix)) + { + continue; + } + + if (aBookmarks.getLength() <= nBookmarkIndex) + { + continue; + } + + comphelper::SequenceAsHashMap aMap(aBookmarks[nBookmarkIndex++]); + if (aMap["Bookmark"].get<OUString>() != pMark->GetName()) + { + continue; + } + + OUString aBookmarkText = aMap["BookmarkText"].get<OUString>(); + + // Insert markers to remember where the paste positions are. + SwPaM aMarkers(pMark->GetMarkEnd()); + IDocumentContentOperations& rIDCO = rWrtSh.GetDoc()->getIDocumentContentOperations(); + bool bSuccess = rIDCO.InsertString(aMarkers, "XY"); + if (bSuccess) + { + SwPaM aPasteEnd(pMark->GetMarkEnd()); + aPasteEnd.Move(fnMoveForward, GoInContent); + + // Paste HTML content. + SwPaM* pCursorPos = rWrtSh.GetCursor(); + *pCursorPos = aPasteEnd; + SwTranslateHelper::PasteHTMLToPaM(rWrtSh, pCursorPos, aBookmarkText.toUtf8(), true); + + // Update the bookmark to point to the new content. + SwPaM aPasteStart(pMark->GetMarkEnd()); + aPasteStart.Move(fnMoveForward, GoInContent); + SwPaM aStartMarker(pMark->GetMarkStart(), *aPasteStart.GetPoint()); + SwPaM aEndMarker(*aPasteEnd.GetPoint(), *aPasteEnd.GetPoint()); + aEndMarker.GetMark()->AdjustContent(1); + pMark->SetMarkPos(*aPasteStart.GetPoint()); + pMark->SetOtherMarkPos(*aPasteEnd.GetPoint()); + bSortMarks = true; + + // Remove markers. the start marker includes the old content as well. + rIDCO.DeleteAndJoin(aStartMarker); + rIDCO.DeleteAndJoin(aEndMarker); + } + } + if (bSortMarks) + { + rIDMA.assureSortedMarkContainers(); + } + + rWrtSh.EndAction(); + rWrtSh.GetDoc()->GetIDocumentUndoRedo().EndUndo(SwUndoId::INSBOOKMARK, nullptr); +} } void SwTextShell::Execute(SfxRequest &rReq) @@ -756,6 +844,11 @@ void SwTextShell::Execute(SfxRequest &rReq) break; } + case FN_UPDATE_BOOKMARKS: + { + UpdateBookmarks(rReq, rWrtSh); + break; + } case FN_DELETE_BOOKMARK: { if (pItem && !rWrtSh.getIDocumentSettingAccess().get(DocumentSettingId::PROTECT_BOOKMARKS))