sfx2/source/view/frame.cxx | 5 +- sw/inc/cmdid.h | 2 sw/qa/uibase/shells/shells.cxx | 61 ++++++++++++++++++++++++++++++ sw/sdi/_textsh.sdi | 6 ++ sw/sdi/swriter.sdi | 18 ++++++++ sw/source/uibase/shells/textfld.cxx | 73 ++++++++++++++++++++++++++++++++++++ 6 files changed, 164 insertions(+), 1 deletion(-)
New commits: commit fcee75c95b61e9f0491e9e0691d8b6ad96721edb Author: Miklos Vajna <vmik...@collabora.com> AuthorDate: Mon Nov 28 09:06:23 2022 +0100 Commit: Miklos Vajna <vmik...@collabora.com> CommitDate: Tue Nov 29 12:27:31 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. (cherry picked from commit 7765b442e13048f857fd7ee49ced1731caee297e) Conflicts: sw/qa/uibase/shells/shells.cxx Change-Id: I44a1b1495ead79b92ccd0c9f6412a34cbec5d68b Reviewed-on: https://gerrit.libreoffice.org/c/core/+/143428 Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoff...@gmail.com> Reviewed-by: Miklos Vajna <vmik...@collabora.com> diff --git a/sfx2/source/view/frame.cxx b/sfx2/source/view/frame.cxx index 91bea023858e..f0bb649f0e1d 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 56b05c4aacd1..3ca95a5a4453 100644 --- a/sw/inc/cmdid.h +++ b/sw/inc/cmdid.h @@ -302,6 +302,8 @@ #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 5a7b2ca8d42c..e287ab6d23e6 100644 --- a/sw/qa/uibase/shells/shells.cxx +++ b/sw/qa/uibase/shells/shells.cxx @@ -25,6 +25,7 @@ #include <comphelper/processfactory.hxx> #include <comphelper/propertyvalue.hxx> #include <xmloff/odffields.hxx> +#include <comphelper/string.hxx> #include <IDocumentContentOperations.hxx> #include <cmdid.h> @@ -284,6 +285,66 @@ CPPUNIT_TEST_FIXTURE(SwUibaseShellsTest, testInsertTextFormField) CPPUNIT_ASSERT_EQUAL(OUString("aaa\nbbb"), aActualResult); } +CPPUNIT_TEST_FIXTURE(SwUibaseShellsTest, testUpdateFieldmarks) +{ + // Given a document with 2 fieldmarks: + SwDoc* pDoc = 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: + SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + pWrtShell->SttEndDoc(/*bStt=*/true); + SwCursor* pCursor = pWrtShell->GetCursor(); + OUString aActual = pCursor->Start()->nNode.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 e82a2fff3d88..655efeeaf4d6 100644 --- a/sw/sdi/_textsh.sdi +++ b/sw/sdi/_textsh.sdi @@ -1772,6 +1772,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 ab71c6c32914..35bb66c84b42 100644 --- a/sw/sdi/swriter.sdi +++ b/sw/sdi/swriter.sdi @@ -8220,6 +8220,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 a3690619122d..8aa66ef09ddc 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()->nContent += 2; + // Skip field end. + aPaM.GetMark()->nContent -= 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");