sw/inc/cmdid.h                      |    1 
 sw/qa/uibase/shells/shells.cxx      |   51 +++++++++++++++++++++
 sw/sdi/_textsh.sdi                  |    5 ++
 sw/sdi/swriter.sdi                  |   14 +++++
 sw/source/uibase/shells/textsh1.cxx |   86 ++++++++++++++++++++++++++++++++++++
 5 files changed, 157 insertions(+)

New commits:
commit ea208f6004770eb4b81d28e6930cd0c7bd5d8f12
Author:     Miklos Vajna <vmik...@collabora.com>
AuthorDate: Wed Jan 11 15:56:31 2023 +0100
Commit:     Miklos Vajna <vmik...@collabora.com>
CommitDate: Wed Jan 11 22:54:33 2023 +0000

    sw: add a new .uno:UpdateBookmark UNO command
    
    It is possible to update all bookmarks (having a certain name prefix)
    and their contet, but one can't update the bookmark under the cursor,
    which is needed for Zotero citation clusters.
    
    Fix the problem by adding a new .uno:UpdateBookmark UNO command that can
    update the (innermost) bookmark under the current cursor.
    
    This can be implemented on top of the recently added
    IDocumentMarkAccess::getBookmarkFor().
    
    The UNO command is intentionally hidden from the customize dialog since
    it only makes sense to invoke it from a macro / API with parameters, not
    interactively.
    
    Change-Id: I3e750dfb637f50716be1155a94bc986131b84f20
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/145351
    Reviewed-by: Miklos Vajna <vmik...@collabora.com>
    Tested-by: Jenkins

diff --git a/sw/inc/cmdid.h b/sw/inc/cmdid.h
index befe8e26d607..3baa2f050ecc 100644
--- a/sw/inc/cmdid.h
+++ b/sw/inc/cmdid.h
@@ -325,6 +325,7 @@ class SwUINumRuleItem;
 #define FN_UPDATE_BOOKMARKS (FN_INSERT2 + 34)
 #define FN_UPDATE_SECTIONS (FN_INSERT2 + 35)
 #define FN_DELETE_TEXT_FORMFIELDS (FN_INSERT2 + 36)
+#define FN_UPDATE_BOOKMARK (FN_INSERT2 + 37)
 
 // 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 edfe255f4ad7..b8f466b63a8e 100644
--- a/sw/qa/uibase/shells/shells.cxx
+++ b/sw/qa/uibase/shells/shells.cxx
@@ -758,6 +758,57 @@ CPPUNIT_TEST_FIXTURE(SwUibaseShellsTest, 
testDeleteFieldmarks)
     CPPUNIT_ASSERT_EQUAL(OUString("result 1result 2"), aActual);
 }
 
+CPPUNIT_TEST_FIXTURE(SwUibaseShellsTest, testUpdateBookmark)
+{
+    // Given a document with a bookmarks, covering "BC":
+    createSwDoc();
+    SwDoc* pDoc = getSwDoc();
+    SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell();
+    pWrtShell->Insert("ABCD");
+    pWrtShell->SttEndDoc(/*bStt=*/true);
+    pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/false, 1, 
/*bBasicCall=*/false);
+    pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/true, 2, 
/*bBasicCall=*/false);
+    pWrtShell->SetBookmark(vcl::KeyCode(), "ZOTERO_BREF_old");
+
+    // When updating the content of the bookmark under the cursor:
+    pWrtShell->SttEndDoc(/*bStt=*/true);
+    pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/false, 2, 
/*bBasicCall=*/false);
+    std::vector<beans::PropertyValue> aArgsVec = 
comphelper::JsonToPropertyValues(R"json(
+{
+    "BookmarkNamePrefix": {
+        "type": "string",
+        "value": "ZOTERO_BREF_"
+    },
+    "Bookmark": {
+        "type": "[]com.sun.star.beans.PropertyValue",
+        "value": {
+            "Bookmark": {
+                "type": "string",
+                "value": "ZOTERO_BREF_new"
+            },
+            "BookmarkText": {
+                "type": "string",
+                "value": "new result"
+            }
+        }
+    }
+}
+)json");
+    uno::Sequence<beans::PropertyValue> aArgs = 
comphelper::containerToSequence(aArgsVec);
+    dispatchCommand(mxComponent, ".uno:UpdateBookmark", 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 resultD
+    // - Actual  : ABCD
+    // i.e. it was not possible to update just the bookmark under cursor.
+    CPPUNIT_ASSERT_EQUAL(OUString("Anew resultD"), aActual);
+    auto it = pDoc->getIDocumentMarkAccess()->findMark("ZOTERO_BREF_new");
+    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 99f50c8e3b27..f08e0b21b675 100644
--- a/sw/sdi/_textsh.sdi
+++ b/sw/sdi/_textsh.sdi
@@ -152,6 +152,11 @@ interface BaseText
         StateMethod = GetState ;
         DisableFlags="SfxDisableFlags::SwOnProtectedCursor";
     ]
+    FN_UPDATE_BOOKMARK
+    [
+        ExecMethod = Execute ;
+        DisableFlags="SfxDisableFlags::SwOnProtectedCursor";
+    ]
     FN_UPDATE_SECTIONS
     [
         ExecMethod = Execute ;
diff --git a/sw/sdi/swriter.sdi b/sw/sdi/swriter.sdi
index 76939d94c03a..ebded3b24b72 100644
--- a/sw/sdi/swriter.sdi
+++ b/sw/sdi/swriter.sdi
@@ -2600,6 +2600,20 @@ SfxVoidItem UpdateBookmarks FN_UPDATE_BOOKMARKS
     GroupId = SfxGroupId::Insert;
 ]
 
+SfxVoidItem UpdateBookmark FN_UPDATE_BOOKMARK
+(SfxStringItem BookmarkNamePrefix FN_PARAM_1, SfxUnoAnyItem Bookmark 
FN_PARAM_2)
+[
+    AutoUpdate = FALSE,
+    FastCall = FALSE,
+    ReadOnlyDoc = FALSE,
+    Toggle = FALSE,
+    Container = FALSE,
+    RecordAbsolute = FALSE,
+    RecordPerSet;
+
+    GroupId = SfxGroupId::Insert;
+]
+
 SfxVoidItem UpdateSections FN_UPDATE_SECTIONS
 (SfxStringItem SectionNamePrefix FN_PARAM_1, SfxUnoAnyItem Sections FN_PARAM_2)
 [
diff --git a/sw/source/uibase/shells/textsh1.cxx 
b/sw/source/uibase/shells/textsh1.cxx
index 3a5a706c901f..c7fe4fc9c190 100644
--- a/sw/source/uibase/shells/textsh1.cxx
+++ b/sw/source/uibase/shells/textsh1.cxx
@@ -103,6 +103,7 @@
 #include <bookmark.hxx>
 #include <linguistic/misc.hxx>
 #include <comphelper/sequenceashashmap.hxx>
+#include <comphelper/scopeguard.hxx>
 #include <authfld.hxx>
 #include <config_wasm_strip.h>
 #if !ENABLE_WASM_STRIP_EXTRA
@@ -535,6 +536,83 @@ void UpdateBookmarks(SfxRequest& rReq, SwWrtShell& rWrtSh)
     rWrtSh.EndAction();
     rWrtSh.GetDoc()->GetIDocumentUndoRedo().EndUndo(SwUndoId::INSBOOKMARK, 
nullptr);
 }
+
+void UpdateBookmark(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::PropertyValue> aBookmark;
+    const SfxUnoAnyItem* pBookmarks = rReq.GetArg<SfxUnoAnyItem>(FN_PARAM_2);
+    if (pBookmarks)
+    {
+        pBookmarks->GetValue() >>= aBookmark;
+    }
+
+    rWrtSh.GetDoc()->GetIDocumentUndoRedo().StartUndo(SwUndoId::INSBOOKMARK, 
nullptr);
+    rWrtSh.StartAction();
+    comphelper::ScopeGuard g(
+        [&rWrtSh]
+        {
+            rWrtSh.EndAction();
+            
rWrtSh.GetDoc()->GetIDocumentUndoRedo().EndUndo(SwUndoId::INSBOOKMARK, nullptr);
+        });
+
+    IDocumentMarkAccess& rIDMA = *rWrtSh.GetDoc()->getIDocumentMarkAccess();
+    SwPosition& rCursor = *rWrtSh.GetCursor()->GetPoint();
+    auto pBookmark = 
dynamic_cast<sw::mark::Bookmark*>(rIDMA.getBookmarkFor(rCursor));
+    if (!pBookmark || !pBookmark->GetName().startsWith(aBookmarkNamePrefix))
+    {
+        return;
+    }
+
+    comphelper::SequenceAsHashMap aMap(aBookmark);
+    if (aMap["Bookmark"].get<OUString>() != pBookmark->GetName())
+    {
+        rIDMA.renameMark(pBookmark, aMap["Bookmark"].get<OUString>());
+    }
+
+    OUString aBookmarkText = aMap["BookmarkText"].get<OUString>();
+
+    // Insert markers to remember where the paste positions are.
+    SwPaM aMarkers(pBookmark->GetMarkEnd());
+    IDocumentContentOperations& rIDCO = 
rWrtSh.GetDoc()->getIDocumentContentOperations();
+    if (!rIDCO.InsertString(aMarkers, "XY"))
+    {
+        return;
+    }
+
+    SwPaM aPasteEnd(pBookmark->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(pBookmark->GetMarkEnd());
+    aPasteStart.Move(fnMoveForward, GoInContent);
+    SwPaM aStartMarker(pBookmark->GetMarkStart(), *aPasteStart.GetPoint());
+    SwPaM aEndMarker(*aPasteEnd.GetPoint(), *aPasteEnd.GetPoint());
+    aEndMarker.GetMark()->AdjustContent(1);
+    pBookmark->SetMarkPos(*aPasteStart.GetPoint());
+    pBookmark->SetOtherMarkPos(*aPasteEnd.GetPoint());
+
+    // Remove markers. the start marker includes the old content as well.
+    rIDCO.DeleteAndJoin(aStartMarker);
+    rIDCO.DeleteAndJoin(aEndMarker);
+    rIDMA.assureSortedMarkContainers();
+}
 }
 
 void SwTextShell::Execute(SfxRequest &rReq)
@@ -916,9 +994,17 @@ void SwTextShell::Execute(SfxRequest &rReq)
         }
         case FN_UPDATE_BOOKMARKS:
         {
+            // This updates all bookmarks in the document that match the 
conditions specified in
+            // rReq.
             UpdateBookmarks(rReq, rWrtSh);
             break;
         }
+        case FN_UPDATE_BOOKMARK:
+        {
+            // This updates the bookmark under the cursor.
+            UpdateBookmark(rReq, rWrtSh);
+            break;
+        }
         case FN_DELETE_BOOKMARK:
         {
             if (pItem && 
!rWrtSh.getIDocumentSettingAccess().get(DocumentSettingId::PROTECT_BOOKMARKS))

Reply via email to