sfx2/source/view/frame.cxx          |    5 +-
 sw/inc/cmdid.h                      |    2 
 sw/qa/uibase/shells/shells.cxx      |   63 +++++++++++++++++++++++++++++++
 sw/sdi/_textsh.sdi                  |    6 ++
 sw/sdi/swriter.sdi                  |   18 ++++++++
 sw/source/uibase/shells/textfld.cxx |   73 ++++++++++++++++++++++++++++++++++++
 6 files changed, 166 insertions(+), 1 deletion(-)

New commits:
commit 7765b442e13048f857fd7ee49ced1731caee297e
Author:     Miklos Vajna <vmik...@collabora.com>
AuthorDate: Mon Nov 28 09:06:23 2022 +0100
Commit:     Miklos Vajna <vmik...@collabora.com>
CommitDate: Mon Nov 28 11:18:51 2022 +0100

    sw: add a new .uno:TextFormFields UNO command
    
    Currently .uno:TextFormField can be used to insert a new fieldmark, but
    then there is no way to update it. Also, there is no way to update
    several fieldmarks at the same time.
    
    Given that the "ADDON" field type in a field mark can be used by
    extensions like Zotero, it's useful in case these fieldmarks can be not
    only inserted, but they can be also updated. This works by (in the LOK
    case) calling getCommandValues() with .uno:TextFormFields, and then once
    the client generates the new expanded values, there is no way to insert
    these updated expanded values into the document currently.
    
    Fix this by adding a new .uno:TextFormFields UNO command that can update
    all fieldmarks matching a certain prefix (looking at their field
    command).  This allows e.g. updating all Zotero citations or the
    bibliography, but the API is generic to support any kind of ADDON
    fields. Similar to insertion, the content can be multi-paragraph,
    formatted HTML.
    
    This required adjusting SfxUnoAnyItem::CreateDefault(), this way an UNO
    command parameter can be an array of beans::PropertyValues, which is how
    the client can provide details of multiple fieldmarks.
    
    Change-Id: I44a1b1495ead79b92ccd0c9f6412a34cbec5d68b
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/143361
    Reviewed-by: Miklos Vajna <vmik...@collabora.com>
    Tested-by: Jenkins

diff --git a/sfx2/source/view/frame.cxx b/sfx2/source/view/frame.cxx
index eda62d4a29e9..f65d958e3fc4 100644
--- a/sfx2/source/view/frame.cxx
+++ b/sfx2/source/view/frame.cxx
@@ -58,7 +58,10 @@ using namespace ::com::sun::star::util;
 using namespace ::com::sun::star::frame;
 using namespace ::com::sun::star::container;
 
-SfxPoolItem* SfxUnoAnyItem::CreateDefault() { SAL_WARN( "sfx", "No 
SfxUnoAnyItem factory available"); return nullptr; }
+SfxPoolItem* SfxUnoAnyItem::CreateDefault()
+{
+    return new SfxUnoAnyItem(0, uno::Any());
+}
 
 SfxPoolItem* SfxUnoFrameItem::CreateDefault()
 {
diff --git a/sw/inc/cmdid.h b/sw/inc/cmdid.h
index c0d31883c3e0..838ccde78c83 100644
--- a/sw/inc/cmdid.h
+++ b/sw/inc/cmdid.h
@@ -310,6 +310,8 @@ class SwUINumRuleItem;
 #define FN_PROTECT_FIELDS               (FN_INSERT2 + 26)
 #define FN_PROTECT_BOOKMARKS            (FN_INSERT2 + 27)
 
+#define FN_UPDATE_TEXT_FORMFIELDS       (FN_INSERT2 + 28)
+
 // clipboard table content
 #define FN_PASTE_NESTED_TABLE       (FN_INSERT2 + 30)  /* instead of the 
cell-by-cell copy between source and target tables */
 #define FN_TABLE_PASTE_ROW_BEFORE   (FN_INSERT2 + 31)  /* paste table as new 
table rows */
diff --git a/sw/qa/uibase/shells/shells.cxx b/sw/qa/uibase/shells/shells.cxx
index 4361920b3e87..01be5fda10b3 100644
--- a/sw/qa/uibase/shells/shells.cxx
+++ b/sw/qa/uibase/shells/shells.cxx
@@ -26,6 +26,7 @@
 #include <comphelper/propertyvalue.hxx>
 #include <unotools/ucbstreamhelper.hxx>
 #include <xmloff/odffields.hxx>
+#include <comphelper/string.hxx>
 
 #include <IDocumentContentOperations.hxx>
 #include <cmdid.h>
@@ -35,6 +36,7 @@
 #include <IDocumentDrawModelAccess.hxx>
 #include <drawdoc.hxx>
 #include <docsh.hxx>
+#include <ndtxt.hxx>
 
 /// Covers sw/source/uibase/shells/ fixes.
 class SwUibaseShellsTest : public SwModelTestBase
@@ -314,6 +316,67 @@ CPPUNIT_TEST_FIXTURE(SwUibaseShellsTest, 
testInsertTextFormField)
     CPPUNIT_ASSERT_EQUAL(OUString("aaa\nbbb"), aActualResult);
 }
 
+CPPUNIT_TEST_FIXTURE(SwUibaseShellsTest, testUpdateFieldmarks)
+{
+    // Given a document with 2 fieldmarks:
+    createSwDoc();
+    {
+        uno::Sequence<css::beans::PropertyValue> aArgs = {
+            comphelper::makePropertyValue("FieldType", 
uno::Any(OUString(ODF_UNHANDLED))),
+            comphelper::makePropertyValue("FieldCommand",
+                                          uno::Any(OUString("ADDIN ZOTERO_ITEM 
old command 1"))),
+            comphelper::makePropertyValue("FieldResult", 
uno::Any(OUString("old result 1"))),
+        };
+        dispatchCommand(mxComponent, ".uno:TextFormField", aArgs);
+    }
+    {
+        uno::Sequence<css::beans::PropertyValue> aArgs = {
+            comphelper::makePropertyValue("FieldType", 
uno::Any(OUString(ODF_UNHANDLED))),
+            comphelper::makePropertyValue("FieldCommand",
+                                          uno::Any(OUString("ADDIN ZOTERO_ITEM 
old command 2"))),
+            comphelper::makePropertyValue("FieldResult", 
uno::Any(OUString("old result 2"))),
+        };
+        dispatchCommand(mxComponent, ".uno:TextFormField", aArgs);
+    }
+
+    // When updating those fieldmarks:
+    uno::Sequence<css::beans::PropertyValue> aField1{
+        comphelper::makePropertyValue("FieldType", 
uno::Any(OUString(ODF_UNHANDLED))),
+        comphelper::makePropertyValue("FieldCommand",
+                                      uno::Any(OUString("ADDIN ZOTERO_ITEM new 
command 1"))),
+        comphelper::makePropertyValue("FieldResult", uno::Any(OUString("new 
result 1"))),
+    };
+    uno::Sequence<css::beans::PropertyValue> aField2{
+        comphelper::makePropertyValue("FieldType", 
uno::Any(OUString(ODF_UNHANDLED))),
+        comphelper::makePropertyValue("FieldCommand",
+                                      uno::Any(OUString("ADDIN ZOTERO_ITEM new 
command 2"))),
+        comphelper::makePropertyValue("FieldResult", uno::Any(OUString("new 
result 2"))),
+    };
+    uno::Sequence<uno::Sequence<css::beans::PropertyValue>> aFields = { 
aField1, aField2 };
+    uno::Sequence<css::beans::PropertyValue> aArgs = {
+        comphelper::makePropertyValue("FieldType", 
uno::Any(OUString(ODF_UNHANDLED))),
+        comphelper::makePropertyValue("FieldCommandPrefix",
+                                      uno::Any(OUString("ADDIN ZOTERO_ITEM"))),
+        comphelper::makePropertyValue("Fields", uno::Any(aFields)),
+    };
+    dispatchCommand(mxComponent, ".uno:TextFormFields", aArgs);
+
+    // Then make sure that the document text contains the new field results:
+    SwDoc* pDoc = getSwDoc();
+    SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell();
+    pWrtShell->SttEndDoc(/*bStt=*/true);
+    SwCursor* pCursor = pWrtShell->GetCursor();
+    OUString aActual = pCursor->Start()->GetNode().GetTextNode()->GetText();
+    static sal_Unicode const aForbidden[]
+        = { CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FIELDSEP, CH_TXT_ATR_FIELDEND, 0 
};
+    aActual = comphelper::string::removeAny(aActual, aForbidden);
+    // Without the accompanying fix in place, this test would have failed with:
+    // - Expected: new result 1new result 2
+    // - Actual  : old result 1old result 2
+    // i.e. the fieldmarks were not updated.
+    CPPUNIT_ASSERT_EQUAL(OUString("new result 1new result 2"), aActual);
+}
+
 CPPUNIT_PLUGIN_IMPLEMENT();
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/sdi/_textsh.sdi b/sw/sdi/_textsh.sdi
index 6830a248a24b..312462f5b474 100644
--- a/sw/sdi/_textsh.sdi
+++ b/sw/sdi/_textsh.sdi
@@ -1778,6 +1778,12 @@ interface BaseText
         StateMethod = StateField ;
     ]
 
+    FN_UPDATE_TEXT_FORMFIELDS
+    [
+        ExecMethod = ExecField ;
+        StateMethod = StateField ;
+    ]
+
     FN_PROTECT_FIELDS
     [
         ExecMethod = Execute ;
diff --git a/sw/sdi/swriter.sdi b/sw/sdi/swriter.sdi
index 738f42a9c6ab..60ca3faa4437 100644
--- a/sw/sdi/swriter.sdi
+++ b/sw/sdi/swriter.sdi
@@ -8255,6 +8255,24 @@ SfxVoidItem TextFormField FN_INSERT_TEXT_FORMFIELD
     GroupId = SfxGroupId::Controls;
 ]
 
+SfxVoidItem TextFormFields FN_UPDATE_TEXT_FORMFIELDS
+(SfxStringItem FieldType FN_PARAM_1, SfxStringItem FieldCommandPrefix 
FN_PARAM_2, SfxUnoAnyItem Fields FN_PARAM_3)
+[
+    AutoUpdate = TRUE,
+    FastCall = FALSE,
+    ReadOnlyDoc = FALSE,
+    Toggle = FALSE,
+    Container = FALSE,
+    RecordAbsolute = FALSE,
+    RecordPerSet;
+
+
+    AccelConfig = TRUE,
+    MenuConfig = TRUE,
+    ToolBoxConfig = TRUE,
+    GroupId = SfxGroupId::Controls;
+]
+
 SfxVoidItem CheckBoxFormField FN_INSERT_CHECKBOX_FORMFIELD
 
 [
diff --git a/sw/source/uibase/shells/textfld.cxx 
b/sw/source/uibase/shells/textfld.cxx
index 762705c90b66..90a620b1959f 100644
--- a/sw/source/uibase/shells/textfld.cxx
+++ b/sw/source/uibase/shells/textfld.cxx
@@ -17,6 +17,7 @@
  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
  */
 
+#include <com/sun/star/beans/PropertyValues.hpp>
 #include <AnnotationWin.hxx>
 #include <comphelper/lok.hxx>
 #include <hintids.hxx>
@@ -60,6 +61,7 @@
 #include <IDocumentUndoRedo.hxx>
 #include <svl/zforlist.hxx>
 #include <svl/zformat.hxx>
+#include <comphelper/sequenceashashmap.hxx>
 #include <IMark.hxx>
 #include <officecfg/Office/Compatibility.hxx>
 #include <ndtxt.hxx>
@@ -832,6 +834,77 @@ FIELD_INSERT:
         
rSh.GetDoc()->GetIDocumentUndoRedo().EndUndo(SwUndoId::INSERT_FORM_FIELD, 
nullptr);
         rSh.GetView().GetViewFrame()->GetBindings().Invalidate( SID_UNDO );
     }
+    break;
+    case FN_UPDATE_TEXT_FORMFIELDS:
+    {
+        OUString aFieldType;
+        const SfxStringItem* pFieldType = 
rReq.GetArg<SfxStringItem>(FN_PARAM_1);
+        if (pFieldType)
+        {
+            aFieldType = pFieldType->GetValue();
+        }
+        OUString aFieldCommandPrefix;
+        const SfxStringItem* pFieldCommandPrefix = 
rReq.GetArg<SfxStringItem>(FN_PARAM_2);
+        if (pFieldCommandPrefix)
+        {
+            aFieldCommandPrefix = pFieldCommandPrefix->GetValue();
+        }
+        uno::Sequence<beans::PropertyValues> aFields;
+        const SfxUnoAnyItem* pFields = rReq.GetArg<SfxUnoAnyItem>(FN_PARAM_3);
+        if (pFields)
+        {
+            pFields->GetValue() >>= aFields;
+        }
+
+        
rSh.GetDoc()->GetIDocumentUndoRedo().StartUndo(SwUndoId::INSERT_FORM_FIELD, 
nullptr);
+        rSh.StartAction();
+
+        IDocumentMarkAccess* pMarkAccess = 
rSh.GetDoc()->getIDocumentMarkAccess();
+        sal_Int32 nFieldIndex = 0;
+        for (auto it = pMarkAccess->getFieldmarksBegin(); it != 
pMarkAccess->getFieldmarksEnd(); ++it)
+        {
+            auto pFieldmark = dynamic_cast<sw::mark::IFieldmark*>(*it);
+            assert(pFieldmark);
+            if (pFieldmark->GetFieldname() != aFieldType)
+            {
+                continue;
+            }
+
+            auto itParam = pFieldmark->GetParameters()->find(ODF_CODE_PARAM);
+            if (itParam == pFieldmark->GetParameters()->end())
+            {
+                continue;
+            }
+
+            OUString aCommand;
+            itParam->second >>= aCommand;
+            if (!aCommand.startsWith(aFieldCommandPrefix))
+            {
+                continue;
+            }
+
+            if (aFields.getLength() <= nFieldIndex)
+            {
+                continue;
+            }
+
+            comphelper::SequenceAsHashMap aMap(aFields[nFieldIndex++]);
+            itParam->second = aMap["FieldCommand"];
+            SwPaM aPaM(pFieldmark->GetMarkPos(), 
pFieldmark->GetOtherMarkPos());
+            aPaM.Normalize();
+            // Skip field start & separator.
+            aPaM.GetPoint()->AdjustContent(2);
+            // Skip field end.
+            aPaM.GetMark()->AdjustContent(-1);
+            rSh.GetDoc()->getIDocumentContentOperations().DeleteAndJoin(aPaM);
+            OUString aFieldResult;
+            aMap["FieldResult"] >>= aFieldResult;
+            SwTranslateHelper::PasteHTMLToPaM(rSh, &aPaM, 
aFieldResult.toUtf8(), true);
+        }
+
+        rSh.EndAction();
+        
rSh.GetDoc()->GetIDocumentUndoRedo().EndUndo(SwUndoId::INSERT_FORM_FIELD, 
nullptr);
+    }
     break;
         default:
             OSL_FAIL("wrong dispatcher");

Reply via email to