include/oox/drawingml/shape.hxx | 5 +- include/svx/svdobj.hxx | 4 + include/svx/svdpage.hxx | 2 offapi/com/sun/star/presentation/Shape.idl | 8 +++ oox/inc/drawingml/textbody.hxx | 3 + oox/source/drawingml/shape.cxx | 21 +++++++-- oox/source/drawingml/shapecontext.cxx | 2 oox/source/drawingml/textbody.cxx | 15 ++++++ oox/source/export/shapes.cxx | 1 oox/source/ppt/pptshape.cxx | 10 +--- oox/source/token/properties.txt | 1 sd/inc/sdpage.hxx | 6 +- sd/qa/unit/data/pptx/tdf163239.pptx |binary sd/qa/unit/export-tests-ooxml4.cxx | 66 ++++++++++++++++++++++++++++- sd/source/core/sdpage.cxx | 53 ++++++++++++++++++++--- sd/source/filter/eppt/pptx-epptooxml.cxx | 24 +++++++++- sd/source/ui/inc/unoprnms.hxx | 1 sd/source/ui/unoidl/unoobj.cxx | 46 ++++++++++++++++++++ sd/source/ui/unoidl/unoobj.hxx | 3 + sd/source/ui/view/NotesPanelView.cxx | 2 sd/source/ui/view/drawview.cxx | 2 sd/source/ui/view/outlnvsh.cxx | 4 - sd/source/ui/view/outlview.cxx | 4 - sd/source/ui/view/sdview.cxx | 2 svx/source/svdraw/svdobj.cxx | 5 ++ svx/source/svdraw/svdpage.cxx | 6 ++ 26 files changed, 263 insertions(+), 33 deletions(-)
New commits: commit 6da1b384c70095f1636edb07cf7b1f3304dae025 Author: Balazs Varga <[email protected]> AuthorDate: Fri Aug 8 13:06:38 2025 +0200 Commit: Balazs Varga <[email protected]> CommitDate: Fri Aug 15 09:37:35 2025 +0200 tdf#163239 - OOXML sd: fix customized placeholder text in master slide to be shown as the "prompt" text in the final slides What should works - New sd specific uno api shape property: CustomPromptText - With this property, the corresponding master slide SdrObj's and their final slide SdrObj's placeholder text can be set. - If we have CustomPromptText value it will be used for the PresetObj and will be created (CreatePresObj) with those texts as a default prompt text. If we do not have the usual default ones will be used. - OOXML import and export (partly since we have many other problems around export) are works - Enable to OOXML import of com.sun.star.presentation.SubtitleShape (partly, since in the core we handle them as Text objects) to handle them a bit better. OOXML export is still not good for different reasons: see tdf#112557 - Unit tests for custom "prompt" text TODO - odf import/export - we cannot set in runtime new custom placeholder texts, so the new placeholders/presobj will be created with the usual default texts. Change-Id: Ic2b06a10f7a19f0cfdb2b705645b08a24b3e433b Reviewed-on: https://gerrit.libreoffice.org/c/core/+/189185 Tested-by: Jenkins Reviewed-by: Balazs Varga <[email protected]> (cherry picked from commit 41c59a730ffce72202083b8ce3438c10ba18f1d1) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/189517 Tested-by: Jenkins CollaboraOffice <[email protected]> Reviewed-by: Justin Luth <[email protected]> diff --git a/include/oox/drawingml/shape.hxx b/include/oox/drawingml/shape.hxx index 81797757faaf..40c207be2edd 100644 --- a/include/oox/drawingml/shape.hxx +++ b/include/oox/drawingml/shape.hxx @@ -244,6 +244,8 @@ public: void setTxbxHasLinkedTxtBox( const bool rhs){ mbHasLinkedTxbx = rhs; }; const LinkedTxbxAttr& getLinkedTxbxAttributes() const { return maLinkedTxbxAttr; }; bool isLinkedTxbx() const { return mbHasLinkedTxbx; }; + void setCustomPrompt( bool bValue ) { mbHasCustomPrompt = bValue; } + bool getCustomPrompt() const { return mbHasCustomPrompt; } void setZOrder(sal_Int32 nZOrder) { mnZOrder = nZOrder; } @@ -298,7 +300,7 @@ protected: const Theme* pTheme, const css::uno::Reference< css::drawing::XShapes >& rxShapes, bool bClearText, - bool bDoNotInsertEmptyTextBody, + const oox::drawingml::ShapePtr& pPlaceholder, basegfx::B2DHomMatrix& aTransformation, const FillProperties& rShapeOrParentShapeFillProps, const oox::drawingml::ShapePtr& pParentGroupShape = nullptr @@ -430,6 +432,7 @@ private: bool mbTextBox; ///< This shape has a textbox. LinkedTxbxAttr maLinkedTxbxAttr; bool mbHasLinkedTxbx; // this text box has linked text box ? + bool mbHasCustomPrompt; // indicates that it's not a generic placeholder css::uno::Sequence<css::beans::PropertyValue> maDiagramDoms; diff --git a/include/svx/svdobj.hxx b/include/svx/svdobj.hxx index 621c44e78c4a..524e57e59f44 100644 --- a/include/svx/svdobj.hxx +++ b/include/svx/svdobj.hxx @@ -826,6 +826,8 @@ public: void SetEmptyPresObj(bool bEpt); bool IsEmptyPresObj() const { return m_bEmptyPresObj;} + void SetCustomPromptText(const OUString& aVal); + OUString GetCustomPromptText() const { return m_aCustomPromptText; } void SetNotVisibleAsMaster(bool bFlg); bool IsNotVisibleAsMaster() const { return m_bNotVisibleAsMaster;} void SetUserCall(SdrObjUserCall* pUser); @@ -915,6 +917,8 @@ protected: bool mbLineIsOutsideGeometry : 1; // #i25616# bool mbSupportTextIndentingOnLineWidthChange : 1; + // custom prompt text for empty presentation object + OUString m_aCustomPromptText; std::unique_ptr<sdr::annotation::ObjectAnnotationData> mpAnnotationData; diff --git a/include/svx/svdpage.hxx b/include/svx/svdpage.hxx index f64af22e1353..4b0a4eb84b51 100644 --- a/include/svx/svdpage.hxx +++ b/include/svx/svdpage.hxx @@ -520,6 +520,8 @@ public: void MakePageObjectsNamesUnique(); + virtual bool RestoreDefaultText(SdrObject* pObj, const OUString& rStr); + protected: void TRG_ImpMasterPageRemoved(const SdrPage& rRemovedPage); diff --git a/offapi/com/sun/star/presentation/Shape.idl b/offapi/com/sun/star/presentation/Shape.idl index 0539228e090d..07abc55e8a81 100644 --- a/offapi/com/sun/star/presentation/Shape.idl +++ b/offapi/com/sun/star/presentation/Shape.idl @@ -128,6 +128,14 @@ published service Shape */ [property] long Verb; + /** Determines if the shape has custom placeholder text. + + This is the custom placeholder text for Presentation objects. + + @since LibreOffice 26.2 + */ + [optional, property] string CustomPromptText; + }; diff --git a/oox/inc/drawingml/textbody.hxx b/oox/inc/drawingml/textbody.hxx index 245589e86ac3..d059ed501306 100644 --- a/oox/inc/drawingml/textbody.hxx +++ b/oox/inc/drawingml/textbody.hxx @@ -64,7 +64,10 @@ public: const TextCharacterProperties& rTextStyleProperties, const TextListStylePtr& pMasterTextListStyle ) const; bool isEmpty() const; + /// Returns first run of text OUString toString() const; + /// Returns first paragraph of text + OUString firstParatoString() const; /** Returns whether the textbody had a rPr tag in it that alters it visually * diff --git a/oox/source/drawingml/shape.cxx b/oox/source/drawingml/shape.cxx index 4cb0160facee..54b8d91ba663 100644 --- a/oox/source/drawingml/shape.cxx +++ b/oox/source/drawingml/shape.cxx @@ -153,6 +153,7 @@ Shape::Shape() , mbWps( false ) , mbTextBox( false ) , mbHasLinkedTxbx( false ) +, mbHasCustomPrompt( false ) , maDiagramDoms( 0 ) , mpDiagramHelper( nullptr ) { @@ -186,6 +187,7 @@ Shape::Shape( const OUString& rServiceName, bool bDefaultHeight ) , mbWps( false ) , mbTextBox( false ) , mbHasLinkedTxbx( false ) +, mbHasCustomPrompt( false ) , maDiagramDoms( 0 ) , mpDiagramHelper( nullptr ) { @@ -230,6 +232,7 @@ Shape::Shape( const ShapePtr& pSourceShape ) , mbWps( pSourceShape->mbWps ) , mbTextBox( pSourceShape->mbTextBox ) , mbHasLinkedTxbx(false) +, mbHasCustomPrompt( pSourceShape->mbHasCustomPrompt ) , maDiagramDoms( pSourceShape->maDiagramDoms ) , mnZOrder(pSourceShape->mnZOrder) , mnZOrderOff(pSourceShape->mnZOrderOff) @@ -394,7 +397,7 @@ void Shape::addShape( if( !sServiceName.isEmpty() ) { basegfx::B2DHomMatrix aMatrix( aTransformation ); - Reference< XShape > xShape( createAndInsert( rFilterBase, sServiceName, pTheme, rxShapes, false, false, aMatrix, rShapeOrParentShapeFillProps, pParentGroupShape) ); + Reference< XShape > xShape( createAndInsert( rFilterBase, sServiceName, pTheme, rxShapes, false, nullptr, aMatrix, rShapeOrParentShapeFillProps, pParentGroupShape) ); if( pShapeMap && !msId.isEmpty() ) { @@ -916,7 +919,7 @@ Reference< XShape > const & Shape::createAndInsert( const Theme* pTheme, const css::uno::Reference< css::drawing::XShapes >& rxShapes, bool bClearText, - bool bDoNotInsertEmptyTextBody, + const oox::drawingml::ShapePtr& pPlaceholder, basegfx::B2DHomMatrix& aParentTransformation, const FillProperties& rShapeOrParentShapeFillProps, const oox::drawingml::ShapePtr& pParentGroupShape) @@ -1816,6 +1819,16 @@ Reference< XShape > const & Shape::createAndInsert( propertySet->setPropertyValue(u"InteropGrabBag"_ustr,uno::Any(aGrabBag)); } + // set custom prompt text if available + if (getCustomPrompt() && getTextBody() && !getTextBody()->isEmpty()) + { + aShapeProps.setProperty(PROP_CustomPromptText, getTextBody()->firstParatoString()); + } + else if (pPlaceholder && pPlaceholder->getCustomPrompt() && pPlaceholder->getTextBody() && !pPlaceholder->getTextBody()->isEmpty()) + { + aShapeProps.setProperty(PROP_CustomPromptText, pPlaceholder->getTextBody()->firstParatoString()); + } + PropertySet( xSet ).setProperties( aShapeProps ); if (mpTablePropertiesPtr && aServiceName == "com.sun.star.drawing.TableShape") @@ -2132,7 +2145,7 @@ Reference< XShape > const & Shape::createAndInsert( mpTextBody.reset(); // in some cases, we don't have any text body. - if( mpTextBody && ( !bDoNotInsertEmptyTextBody || !mpTextBody->isEmpty() ) ) + if( mpTextBody && ( !pPlaceholder || !mpTextBody->isEmpty() ) ) { Reference < XText > xText( mxShape, UNO_QUERY ); if ( xText.is() ) // not every shape is supporting an XText interface (e.g. GroupShape) @@ -2223,7 +2236,7 @@ Reference< XShape > const & Shape::createAndInsert( } // Set text glow effect for shapes - if (mpTextBody && (!bDoNotInsertEmptyTextBody || !mpTextBody->isEmpty())) + if (mpTextBody && (!pPlaceholder || !mpTextBody->isEmpty())) { const TextParagraphVector& rParagraphs = mpTextBody->getParagraphs(); if (!rParagraphs.empty()) diff --git a/oox/source/drawingml/shapecontext.cxx b/oox/source/drawingml/shapecontext.cxx index 1cd22c7cd422..2dc77a0e676f 100644 --- a/oox/source/drawingml/shapecontext.cxx +++ b/oox/source/drawingml/shapecontext.cxx @@ -86,6 +86,8 @@ ContextHandlerRef ShapeContext::onCreateContext( sal_Int32 aElementToken, const mpShapePtr->setSubType( rAttribs.getToken( XML_type, XML_obj ) ); if( rAttribs.hasAttribute( XML_idx ) ) mpShapePtr->setSubTypeIndex( rAttribs.getInteger( XML_idx, 0 ) ); + if( rAttribs.hasAttribute( XML_hasCustomPrompt ) ) + mpShapePtr->setCustomPrompt( rAttribs.getBool( XML_hasCustomPrompt, false ) ); break; // nvSpPr CT_ShapeNonVisual end diff --git a/oox/source/drawingml/textbody.cxx b/oox/source/drawingml/textbody.cxx index 1d9e45f505f5..9e1c68b5206f 100644 --- a/oox/source/drawingml/textbody.cxx +++ b/oox/source/drawingml/textbody.cxx @@ -95,6 +95,21 @@ OUString TextBody::toString() const return OUString(); } +OUString TextBody::firstParatoString() const +{ + OUStringBuffer aRet; + if (!isEmpty()) + { + const TextRunVector& rRuns = maParagraphs.front()->getRuns(); + for (TextRunVector::const_iterator aRIt = rRuns.begin(), aREnd = rRuns.end(); aRIt != aREnd; ++aRIt) + { + const TextRun& rTextRun = **aRIt; + aRet.append(rTextRun.getText()); + } + } + return aRet.makeStringAndClear(); +} + bool TextBody::hasVisualRunProperties() const { for ( auto& pTextParagraph : getParagraphs() ) diff --git a/oox/source/export/shapes.cxx b/oox/source/export/shapes.cxx index d8c396382217..b4062cda91f5 100644 --- a/oox/source/export/shapes.cxx +++ b/oox/source/export/shapes.cxx @@ -2185,6 +2185,7 @@ constexpr auto constMap = frozen::make_unordered_map<std::u16string_view, ShapeC { u"com.sun.star.presentation.OutlinerShape", &ShapeExport::WriteTextShape }, { u"com.sun.star.presentation.SlideNumberShape", &ShapeExport::WriteTextShape }, { u"com.sun.star.presentation.TitleTextShape", &ShapeExport::WriteTextShape }, + //{ u"com.sun.star.presentation.SubtitleShape", &ShapeExport::WriteTextShape }, TODO: handle subtitle shape: see tdf#112557 workaround }); } // end anonymous namespace diff --git a/oox/source/ppt/pptshape.cxx b/oox/source/ppt/pptshape.cxx index 634c46dc5255..61adfbd87512 100644 --- a/oox/source/ppt/pptshape.cxx +++ b/oox/source/ppt/pptshape.cxx @@ -195,12 +195,8 @@ void PPTShape::addShape( break; case XML_subTitle : { - if ((meShapeLocation == Master) || (meShapeLocation == Layout)) - sServiceName = OUString(); - else { - sServiceName = "com.sun.star.presentation.SubtitleShape"; - aMasterTextListStyle = rSlidePersist.getMasterPersist() ? rSlidePersist.getMasterPersist()->getBodyTextStyle() : rSlidePersist.getBodyTextStyle(); - } + sServiceName = "com.sun.star.presentation.SubtitleShape"; + aMasterTextListStyle = rSlidePersist.getMasterPersist() ? rSlidePersist.getMasterPersist()->getBodyTextStyle() : rSlidePersist.getBodyTextStyle(); } break; case XML_obj : @@ -454,7 +450,7 @@ void PPTShape::addShape( } else setMasterTextListStyle( aMasterTextListStyle ); - Reference< XShape > xShape( createAndInsert( rFilterBase, sServiceName, pTheme, rxShapes, bClearText, bool(mpPlaceholder), aTransformation, getFillProperties() ) ); + Reference< XShape > xShape( createAndInsert( rFilterBase, sServiceName, pTheme, rxShapes, bClearText, mpPlaceholder, aTransformation, getFillProperties() ) ); // Apply text properties on placeholder text inside this placeholder shape if (meShapeLocation == Slide && mpPlaceholder && getTextBody() && getTextBody()->isEmpty()) diff --git a/oox/source/token/properties.txt b/oox/source/token/properties.txt index d73008a80539..36e9a16991ef 100644 --- a/oox/source/token/properties.txt +++ b/oox/source/token/properties.txt @@ -125,6 +125,7 @@ CurveName CurveStyle CustomLabelPosition CustomLabelSize +CustomPromptText CustomShapeGeometry D3DSceneAmbientColor D3DSceneLightColor2 diff --git a/sd/inc/sdpage.hxx b/sd/inc/sdpage.hxx index 71b47e4b7cc9..1b2cafc550c9 100644 --- a/sd/inc/sdpage.hxx +++ b/sd/inc/sdpage.hxx @@ -167,7 +167,7 @@ public: sd::ShapeList& GetPresentationShapeList() { return maPresentationShapeList; } void EnsureMasterPageDefaultBackground(); - SD_DLLPUBLIC SdrObject* CreatePresObj(PresObjKind eObjKind, bool bVertical, const ::tools::Rectangle& rRect); + SD_DLLPUBLIC SdrObject* CreatePresObj(PresObjKind eObjKind, bool bVertical, const ::tools::Rectangle& rRect, const OUString& rCustomPrompt = OUString()); SD_DLLPUBLIC rtl::Reference<SdrObject> CreateDefaultPresObj(PresObjKind eObjKind); SD_DLLPUBLIC void DestroyDefaultPresObj(PresObjKind eObjKind); SD_DLLPUBLIC SdrObject* GetPresObj(PresObjKind eObjKind, int nIndex = 1, bool bFuzzySearch = false ); @@ -177,7 +177,7 @@ public: SfxStyleSheet* GetStyleSheetForPresObj(PresObjKind eObjKind) const; void GetPageInfo(::tools::JsonWriter& jsonWriter); void NotifyPagePropertyChanges(); - bool RestoreDefaultText( SdrObject* pObj ); + bool RestoreDefaultText( SdrObject* pObj, const OUString& rStr ) override; /** @return true if the given SdrObject is inside the presentation object list */ bool IsPresObj(const SdrObject* pObj); @@ -191,7 +191,7 @@ public: SD_DLLPUBLIC void SetAutoLayout(AutoLayout eLayout, bool bInit=false, bool bCreate=false); AutoLayout GetAutoLayout() const { return meAutoLayout; } void CreateTitleAndLayout(bool bInit=false, bool bCreate=false); - SdrObject* InsertAutoLayoutShape(SdrObject* pObj, PresObjKind eObjKind, bool bVertical, const ::tools::Rectangle& rRect, bool bInit); + SdrObject* InsertAutoLayoutShape(SdrObject* pObj, PresObjKind eObjKind, bool bVertical, const ::tools::Rectangle& rRect, const OUString& rCustomPrompt, bool bInit); virtual void NbcInsertObject(SdrObject* pObj, size_t nPos=SAL_MAX_SIZE) override; virtual rtl::Reference<SdrObject> NbcRemoveObject(size_t nObjNum) override; diff --git a/sd/qa/unit/data/pptx/tdf163239.pptx b/sd/qa/unit/data/pptx/tdf163239.pptx new file mode 100644 index 000000000000..7dbc2e2a9862 Binary files /dev/null and b/sd/qa/unit/data/pptx/tdf163239.pptx differ diff --git a/sd/qa/unit/export-tests-ooxml4.cxx b/sd/qa/unit/export-tests-ooxml4.cxx index d9bea30c1334..c88f71db7db4 100644 --- a/sd/qa/unit/export-tests-ooxml4.cxx +++ b/sd/qa/unit/export-tests-ooxml4.cxx @@ -797,7 +797,7 @@ CPPUNIT_TEST_FIXTURE(SdOOXMLExportTest4, testTdf140912_PicturePlaceholder) CPPUNIT_ASSERT(isEmptyPresentationObject); // If we supported custom prompt text, here we would also test "String" property, - // which would be equal to "Insert Image". + // which would be equal to "Insert Image". See first tests: testCustomPromptTexts } CPPUNIT_TEST_FIXTURE(SdOOXMLExportTest4, testEnhancedPathViewBox) @@ -1374,6 +1374,70 @@ CPPUNIT_TEST_FIXTURE(SdOOXMLExportTest4, testTdf165261HorzAnchor) CPPUNIT_ASSERT_EQUAL(drawing::TextHorizontalAdjust::TextHorizontalAdjust_CENTER, eHori); } +CPPUNIT_TEST_FIXTURE(SdOOXMLExportTest4, testCustomPromptTexts) +{ + createSdImpressDoc("pptx/tdf163239.pptx"); + saveAndReload(u"Impress Office Open XML"_ustr); + + const SdrPage* pPage1 = GetPage(1); + { + // subtitle placeholder text + SdrTextObj* pTxtObj = DynCastSdrTextObj(pPage1->GetObj(0)); + CPPUNIT_ASSERT_MESSAGE("no text object", pTxtObj != nullptr); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong object type!", SdrObjKind::Text, + pTxtObj->GetObjIdentifier()); + const EditTextObject& aEdit = pTxtObj->GetOutlinerParaObject()->GetTextObject(); + OUString aText = aEdit.GetText(0); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong placeholder text!", aText, u"Click to add Text"_ustr); + /* TODO: handle subtitle shape: see tdf#112557 workaround + - Expected: Click to edit customized Master Subtitle style + - Actual : Click to add Text + - Wrong placeholder text! + */ + + auto xShapeProps(getShapeFromPage(0, 0)); + CPPUNIT_ASSERT(xShapeProps->getPropertyValue(u"CustomPromptText"_ustr) >>= aText); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong placeholder text was set!", aText, u""_ustr); + /* TODO: handle subtitle shape: see tdf#112557 workaround + - Expected: Click to edit customized Master Subtitle style + - Actual : + - Wrong placeholder text was set! + */ + } + + { + SdrTextObj* pTxtObj = DynCastSdrTextObj(pPage1->GetObj(1)); + CPPUNIT_ASSERT_MESSAGE("no text object", pTxtObj != nullptr); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong object type!", SdrObjKind::TitleText, + pTxtObj->GetObjIdentifier()); + const EditTextObject& aEdit = pTxtObj->GetOutlinerParaObject()->GetTextObject(); + OUString aText = aEdit.GetText(0); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong placeholder text!", aText, u"Custom Title 1"_ustr); + + auto xShapeProps(getShapeFromPage(1, 0)); + CPPUNIT_ASSERT(xShapeProps->getPropertyValue(u"CustomPromptText"_ustr) >>= aText); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong placeholder text was set!", aText, + u"Custom Title 1"_ustr); + } + + const SdrPage* pPage2 = GetPage(3); + { + // body placeholder text + SdrTextObj* pTxtObj = DynCastSdrTextObj(pPage2->GetObj(0)); + CPPUNIT_ASSERT_MESSAGE("no text object", pTxtObj != nullptr); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong object type!", SdrObjKind::OutlineText, + pTxtObj->GetObjIdentifier()); + const EditTextObject& aEdit = pTxtObj->GetOutlinerParaObject()->GetTextObject(); + OUString aText = aEdit.GetText(0); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong placeholder text!", aText, u"Text placeholder"_ustr); + + auto xShapeProps(getShapeFromPage(0, 1)); + CPPUNIT_ASSERT(xShapeProps->getPropertyValue(u"CustomPromptText"_ustr) >>= aText); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong placeholder text was set!", aText, + u"Text placeholder"_ustr); + } +} + CPPUNIT_PLUGIN_IMPLEMENT(); /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sd/source/core/sdpage.cxx b/sd/source/core/sdpage.cxx index 76ab8e4b42cd..0d8d5aeebd6c 100644 --- a/sd/source/core/sdpage.cxx +++ b/sd/source/core/sdpage.cxx @@ -284,7 +284,7 @@ void SdPage::EnsureMasterPageDefaultBackground() /** creates a presentation object with the given PresObjKind on this page. A user call will be set */ -SdrObject* SdPage::CreatePresObj(PresObjKind eObjKind, bool bVertical, const ::tools::Rectangle& rRect ) +SdrObject* SdPage::CreatePresObj(PresObjKind eObjKind, bool bVertical, const ::tools::Rectangle& rRect, const OUString& rCustomPrompt) { SfxUndoManager* pUndoManager(static_cast< SdDrawDocument& >(getSdrModelFromSdrPage()).GetUndoManager()); const bool bUndo = pUndoManager && pUndoManager->IsInListAction() && IsInserted(); @@ -498,7 +498,15 @@ SdrObject* SdPage::CreatePresObj(PresObjKind eObjKind, bool bVertical, const ::t pSdrObj->SetLogicRect(rRect); } - OUString aString = GetPresObjText(eObjKind); + OUString aString; + if (!rCustomPrompt.isEmpty()) + { + pSdrObj->SetCustomPromptText(rCustomPrompt); + aString = rCustomPrompt; + } + else + aString = GetPresObjText(eObjKind); + if(!aString.isEmpty() || bForceText) if (auto pTextObj = DynCastSdrTextObj( pSdrObj.get() ) ) { @@ -1479,6 +1487,32 @@ static void CalcAutoLayoutRectangles( SdPage const & rPage,::tools::Rectangle* r } } +static void GetAutoLayoutCustomPromptTexts( SdPage& rPage, const LayoutDescriptor& rDescriptor, std::array<OUString, MAX_PRESOBJS>& rCustomPrompts ) +{ + // init layout shapes with their corresponding prompt text (if they have) + // for each presentation shape kind + if (rPage.GetPageKind() == PageKind::Handout || !rPage.TRG_HasMasterPage()) + return; + + SdPage& rMasterPage = static_cast<SdPage&>(rPage.TRG_GetMasterPage()); + + o3tl::enumarray<PresObjKind,int> PresObjIndex; + PresObjIndex.fill(1); + + // for each entry in the layoutdescriptor, arrange a presentation shape + for (int i = 0; (i < MAX_PRESOBJS) && (rDescriptor.meKind[i] != PresObjKind::NONE); i++) + { + PresObjKind eKind = rDescriptor.meKind[i]; + SdrObject* pObj = nullptr; + while( (pObj = rMasterPage.GetPresObj( eKind, PresObjIndex[eKind], true )) != nullptr ) + { + PresObjIndex[eKind]++; // on next search for eKind, find next shape with same eKind + rCustomPrompts[i] = pObj->GetCustomPromptText(); + break; + } + } +} + static void findAutoLayoutShapesImpl( SdPage& rPage, const LayoutDescriptor& rDescriptor, std::array<SdrObject*, MAX_PRESOBJS>& rShapes, bool bInit, bool bSwitchLayout ) { // init list of indexes for each presentation shape kind @@ -1662,6 +1696,9 @@ void SdPage::SetAutoLayout(AutoLayout eLayout, bool bInit, bool bCreate ) OUString sLayoutName( enumtoString(meAutoLayout) ); CalcAutoLayoutRectangles( *this, aRectangle, sLayoutName); + std::array<OUString, MAX_PRESOBJS > aCustomPromptTexts; + GetAutoLayoutCustomPromptTexts( *this, aDescriptor, aCustomPromptTexts ); + o3tl::sorted_vector< SdrObject* > aUsedPresentationObjects; std::array<SdrObject*, MAX_PRESOBJS > aLayoutShapes; @@ -1672,7 +1709,7 @@ void SdPage::SetAutoLayout(AutoLayout eLayout, bool bInit, bool bCreate ) for (int i = 0; (i < MAX_PRESOBJS) && (aDescriptor.meKind[i] != PresObjKind::NONE); i++) { PresObjKind eKind = aDescriptor.meKind[i]; - SdrObject* pObj = InsertAutoLayoutShape( aLayoutShapes[i], eKind, aDescriptor.mbVertical[i], aRectangle[i], bInit ); + SdrObject* pObj = InsertAutoLayoutShape( aLayoutShapes[i], eKind, aDescriptor.mbVertical[i], aRectangle[i], aCustomPromptTexts[i], bInit ); if( pObj ) aUsedPresentationObjects.insert(pObj); // remember that we used this empty shape } @@ -2247,12 +2284,14 @@ static rtl::Reference<SdrObject> convertPresentationObjectImpl(SdPage& rPage, Sd If true, the shape is created vertical if bInit is true @param rRect The rectangle that should be used to transform the shape + @param rCustomPrompt + The custom prompt text for placeholder text in presObj's, if its empty the default is used @param bInit If true the shape is created if not found @returns A presentation shape that was either found or created with the given parameters */ -SdrObject* SdPage::InsertAutoLayoutShape(SdrObject* pObj1, PresObjKind eObjKind, bool bVertical, const ::tools::Rectangle& rRect, bool bInit) +SdrObject* SdPage::InsertAutoLayoutShape(SdrObject* pObj1, PresObjKind eObjKind, bool bVertical, const ::tools::Rectangle& rRect, const OUString& rCustomPrompt, bool bInit) { rtl::Reference<SdrObject> pObj = pObj1; SfxUndoManager* pUndoManager(static_cast< SdDrawDocument& >(getSdrModelFromSdrPage()).GetUndoManager()); @@ -2260,7 +2299,7 @@ SdrObject* SdPage::InsertAutoLayoutShape(SdrObject* pObj1, PresObjKind eObjKind, if (!pObj && bInit) { - pObj = CreatePresObj(eObjKind, bVertical, rRect); + pObj = CreatePresObj(eObjKind, bVertical, rRect, rCustomPrompt); } else if ( pObj && (pObj->GetUserCall() || bInit) ) { @@ -2922,7 +2961,7 @@ bool SdPage::checkVisibility( return true; } -bool SdPage::RestoreDefaultText( SdrObject* pObj ) +bool SdPage::RestoreDefaultText( SdrObject* pObj, const OUString& rStr ) { bool bRet = false; @@ -2939,7 +2978,7 @@ bool SdPage::RestoreDefaultText( SdrObject* pObj ) { sd::ModifyGuard aGuard(static_cast<SdDrawDocument*>(&getSdrModelFromSdrPage())); - OUString aString( GetPresObjText(ePresObjKind) ); + OUString aString = rStr.isEmpty() ? GetPresObjText(ePresObjKind) : rStr; if (!aString.isEmpty()) { diff --git a/sd/source/filter/eppt/pptx-epptooxml.cxx b/sd/source/filter/eppt/pptx-epptooxml.cxx index 7ae6bab8c189..212a10c8f519 100644 --- a/sd/source/filter/eppt/pptx-epptooxml.cxx +++ b/sd/source/filter/eppt/pptx-epptooxml.cxx @@ -345,6 +345,12 @@ ShapeExport& PowerPointShapeExport::WriteTextShape(const Reference< XShape >& xS if (!WritePlaceholder(xShape, Title, mbMaster)) ShapeExport::WriteTextShape(xShape); } + /*else if (sShapeType == "com.sun.star.presentation.SubtitleShape") + { + TODO: handle subtitle shape: see tdf#112557 workaround + if (!WritePlaceholder(xShape, Subtitle, mbMaster)) + ShapeExport::WriteTextShape(xShape); + }*/ else SAL_WARN("sd.eppt", "PowerPointShapeExport::WriteTextShape: shape of type '" << sShapeType << "' is ignored"); @@ -2420,20 +2426,32 @@ ShapeExport& PowerPointShapeExport::WritePlaceholderShape(const Reference< XShap const char* pType = getPlaceholderTypeName(ePlaceholder); SAL_INFO("sd.eppt", "write placeholder " << pType); + + // export Custom Prompt + bool bUseCustomPrompt(false); + if (xProps.is() && xProps->getPropertySetInfo()->hasPropertyByName(u"CustomPromptText"_ustr)) + { + OUString aCustomPromptText; + xProps->getPropertyValue(u"CustomPromptText"_ustr) >>= aCustomPromptText; + if (!aCustomPromptText.isEmpty()) + bUseCustomPrompt = true; + } + if (bUsePlaceholderIndex) { mpFS->singleElementNS( XML_p, XML_ph, XML_type, pType, XML_idx, OString::number( - static_cast<PowerPointExport*>(GetFB())->CreateNewPlaceholderIndex(xShape))); + static_cast<PowerPointExport*>(GetFB())->CreateNewPlaceholderIndex(xShape)), + XML_hasCustomPrompt, sax_fastparser::UseIf("1", bUseCustomPrompt)); } else { if ((mePageType == PageType::LAYOUT || mePageType == PageType::NORMAL) && ePlaceholder == Outliner) - mpFS->singleElementNS(XML_p, XML_ph); + mpFS->singleElementNS(XML_p, XML_ph, XML_hasCustomPrompt, sax_fastparser::UseIf("1", bUseCustomPrompt)); else - mpFS->singleElementNS(XML_p, XML_ph, XML_type, pType); + mpFS->singleElementNS(XML_p, XML_ph, XML_type, pType, XML_hasCustomPrompt, sax_fastparser::UseIf("1", bUseCustomPrompt)); } mpFS->endElementNS(XML_p, XML_nvPr); mpFS->endElementNS(XML_p, XML_nvSpPr); diff --git a/sd/source/ui/inc/unoprnms.hxx b/sd/source/ui/inc/unoprnms.hxx index 9753c1a2871a..062d898f4992 100644 --- a/sd/source/ui/inc/unoprnms.hxx +++ b/sd/source/ui/inc/unoprnms.hxx @@ -59,6 +59,7 @@ #define UNO_NAME_OBJ_MASTERDEPENDENT "IsPlaceholderDependent" #define UNO_NAME_OBJ_ANIMATIONPATH "AnimationPath" #define UNO_NAME_OBJ_LEGACYFRAGMENT "LegacyFragment" +#define UNO_NAME_OBJ_CUSTOMPROMPT "CustomPromptText" #define UNO_NAME_LAYER_LOCKED "IsLocked" #define UNO_NAME_LAYER_PRINTABLE "IsPrintable" diff --git a/sd/source/ui/unoidl/unoobj.cxx b/sd/source/ui/unoidl/unoobj.cxx index 49633f61bc30..2dd93e58097d 100644 --- a/sd/source/ui/unoidl/unoobj.cxx +++ b/sd/source/ui/unoidl/unoobj.cxx @@ -104,6 +104,8 @@ using ::com::sun::star::drawing::XShape; #define WID_PLACEHOLDERTEXT 24 #define WID_LEGACYFRAGMENT 25 +#define WID_CUSTOMPROMPT 26 + #define IMPRESS_MAP_ENTRIES \ { u"" UNO_NAME_OBJ_LEGACYFRAGMENT ""_ustr,WID_LEGACYFRAGMENT, cppu::UnoType<drawing::XShape>::get(), 0, 0},\ { u"" UNO_NAME_OBJ_ANIMATIONPATH ""_ustr, WID_ANIMPATH, cppu::UnoType<drawing::XShape>::get(), 0, 0},\ @@ -128,6 +130,7 @@ using ::com::sun::star::drawing::XShape; { u"IsAnimation"_ustr, WID_ISANIMATION, cppu::UnoType<bool>::get(), 0, 0},\ { u"NavigationOrder"_ustr, WID_NAVORDER, cppu::UnoType<sal_Int32>::get(), 0, 0},\ { u"PlaceholderText"_ustr, WID_PLACEHOLDERTEXT, cppu::UnoType<OUString>::get(), 0, 0},\ + { u"" UNO_NAME_OBJ_CUSTOMPROMPT ""_ustr, WID_CUSTOMPROMPT, cppu::UnoType<OUString>::get(), 0, 0},\ static std::span<const SfxItemPropertyMapEntry> lcl_GetImpress_SdXShapePropertyGraphicMap_Impl() { @@ -574,6 +577,14 @@ void SAL_CALL SdXShape::setPropertyValue( const OUString& aPropertyName, const c case WID_ISEMPTYPRESOBJ: SetEmptyPresObj( ::cppu::any2bool(aValue) ); break; + case WID_CUSTOMPROMPT: + { + OUString aString; + if (!(aValue >>= aString)) + throw lang::IllegalArgumentException(); + SetCustomPromptText(aString); + break; + } case WID_MASTERDEPEND: SetMasterDepend( ::cppu::any2bool(aValue) ); break; @@ -672,6 +683,9 @@ css::uno::Any SAL_CALL SdXShape::getPropertyValue( const OUString& PropertyName case WID_ISEMPTYPRESOBJ: aRet <<= IsEmptyPresObj(); break; + case WID_CUSTOMPROMPT: + aRet <<= GetCustomPromptText(); + break; case WID_MASTERDEPEND: aRet <<= IsMasterDepend(); break; @@ -948,6 +962,38 @@ void SdXShape::SetEmptyPresObj(bool bEmpty) pObj->SetEmptyPresObj(bEmpty); } +OUString SdXShape::GetCustomPromptText() const +{ + if (!IsPresObj()) + return OUString(); + + SdrObject* pObj = mpShape->GetSdrObject(); + if (pObj == nullptr) + return OUString(); + + return pObj->GetCustomPromptText(); +} + +void SdXShape::SetCustomPromptText(const OUString& aVal) +{ + if (!IsPresObj() || aVal.isEmpty()) + return; + + SdrObject* pObj = mpShape->GetSdrObject(); + if (pObj == nullptr) + return; + + if (!pObj->getSdrPageFromSdrObject()->IsMasterPage()) + { + if (pObj->getSdrPageFromSdrObject()->RestoreDefaultText(pObj, aVal)) + pObj->SetCustomPromptText(aVal); + } + else + { + pObj->SetCustomPromptText(aVal); + } +} + bool SdXShape::IsMasterDepend() const noexcept { SdrObject* pObj = mpShape->GetSdrObject(); diff --git a/sd/source/ui/unoidl/unoobj.hxx b/sd/source/ui/unoidl/unoobj.hxx index 7c78bc520dcd..52206ea21a4a 100644 --- a/sd/source/ui/unoidl/unoobj.hxx +++ b/sd/source/ui/unoidl/unoobj.hxx @@ -57,6 +57,9 @@ private: bool IsEmptyPresObj() const; void SetEmptyPresObj(bool bEmpty); + OUString GetCustomPromptText() const; + void SetCustomPromptText(const OUString& aVal); + bool IsMasterDepend() const noexcept; void SetMasterDepend( bool bDepend ) noexcept; diff --git a/sd/source/ui/view/NotesPanelView.cxx b/sd/source/ui/view/NotesPanelView.cxx index 42562275b70f..3ed6d4690f6b 100644 --- a/sd/source/ui/view/NotesPanelView.cxx +++ b/sd/source/ui/view/NotesPanelView.cxx @@ -219,7 +219,7 @@ void NotesPanelView::onLoseFocus() // if the notes are empty restore the placeholder text and state. SdPage* pPage = dynamic_cast<SdPage*>(pNotesTextObj->getSdrPageFromSdrObject()); if (pPage) - pPage->RestoreDefaultText(pNotesTextObj); + pPage->RestoreDefaultText(pNotesTextObj, pNotesTextObj->GetCustomPromptText()); } else setNotesToDoc(); diff --git a/sd/source/ui/view/drawview.cxx b/sd/source/ui/view/drawview.cxx index c6da08c44f29..5f139cb620b4 100644 --- a/sd/source/ui/view/drawview.cxx +++ b/sd/source/ui/view/drawview.cxx @@ -592,7 +592,7 @@ void DrawView::DeleteMarked() SdrTextObj* pTextObj = DynCastSdrTextObj( pObj ); bool bVertical = pTextObj && pTextObj->IsVerticalWriting(); ::tools::Rectangle aRect( pObj->GetLogicRect() ); - SdrObject* pNewObj = pPage->InsertAutoLayoutShape( nullptr, ePresObjKind, bVertical, aRect, true ); + SdrObject* pNewObj = pPage->InsertAutoLayoutShape(nullptr, ePresObjKind, bVertical, aRect, OUString(), true); // pUndoManager should not be NULL (see assert above) // but since we have defensive code diff --git a/sd/source/ui/view/outlnvsh.cxx b/sd/source/ui/view/outlnvsh.cxx index e6e66c77558d..ea1347ccc42b 100644 --- a/sd/source/ui/view/outlnvsh.cxx +++ b/sd/source/ui/view/outlnvsh.cxx @@ -1620,7 +1620,7 @@ void OutlineViewShell::UpdateTitleObject( SdPage* pPage, Paragraph const * pPara // make it empty if( pOlView->isRecordingUndo() ) pOlView->AddUndo(GetDoc()->GetSdrUndoFactory().CreateUndoObjectSetText(*pTO,0)); - pPage->RestoreDefaultText( pTO ); + pPage->RestoreDefaultText( pTO, pTO->GetCustomPromptText() ); pTO->SetEmptyPresObj(true); pTO->ActionChanged(); } @@ -1720,7 +1720,7 @@ void OutlineViewShell::UpdateOutlineObject( SdPage* pPage, Paragraph* pPara ) // delete old OutlinerParaObject, too if( pOlView->isRecordingUndo() ) pOlView->AddUndo(GetDoc()->GetSdrUndoFactory().CreateUndoObjectSetText(*pTO,0)); - pPage->RestoreDefaultText( pTO ); + pPage->RestoreDefaultText( pTO, pTO->GetCustomPromptText() ); pTO->SetEmptyPresObj(true); pTO->ActionChanged(); } diff --git a/sd/source/ui/view/outlview.cxx b/sd/source/ui/view/outlview.cxx index cf5d0e081ae6..256a505dd9e6 100644 --- a/sd/source/ui/view/outlview.cxx +++ b/sd/source/ui/view/outlview.cxx @@ -908,7 +908,7 @@ SdrTextObj* OutlineView::CreateTitleTextObject(SdPage* pPage) { // we already have a layout with a title but the title // object was deleted, create a new one - pPage->InsertAutoLayoutShape( nullptr, PresObjKind::Title, false, pPage->GetTitleRect(), true ); + pPage->InsertAutoLayoutShape( nullptr, PresObjKind::Title, false, pPage->GetTitleRect(), OUString(), true ); } return GetTitleTextObject(pPage); @@ -944,7 +944,7 @@ SdrTextObj* OutlineView::CreateOutlineTextObject(SdPage* pPage) // object was deleted, create a new one pPage->InsertAutoLayoutShape( nullptr, PresObjKind::Outline, - false, pPage->GetLayoutRect(), true ); + false, pPage->GetLayoutRect(), OUString(), true ); } return GetOutlineTextObject(pPage); diff --git a/sd/source/ui/view/sdview.cxx b/sd/source/ui/view/sdview.cxx index 5a6be3034fb8..4ae79b48177e 100644 --- a/sd/source/ui/view/sdview.cxx +++ b/sd/source/ui/view/sdview.cxx @@ -853,7 +853,7 @@ bool View::RestoreDefaultText( SdrTextObj* pTextObj ) if(pPage) { - bRestored = pPage->RestoreDefaultText( pTextObj ); + bRestored = pPage->RestoreDefaultText( pTextObj, pTextObj->GetCustomPromptText() ); if( bRestored ) { SdrOutliner* pOutliner = GetTextEditOutliner(); diff --git a/svx/source/svdraw/svdobj.cxx b/svx/source/svdraw/svdobj.cxx index 4eb5146a0c0e..88226a6bfa1a 100644 --- a/svx/source/svdraw/svdobj.cxx +++ b/svx/source/svdraw/svdobj.cxx @@ -1779,6 +1779,7 @@ bool SdrObject::Equals(const SdrObject& rOtherObj) const return (m_aAnchor.X() == rOtherObj.m_aAnchor.X() && m_aAnchor.Y() == rOtherObj.m_aAnchor.Y() && m_nOrdNum == rOtherObj.m_nOrdNum && mnNavigationPosition == rOtherObj.mnNavigationPosition && mbSupportTextIndentingOnLineWidthChange == rOtherObj.mbSupportTextIndentingOnLineWidthChange && + m_aCustomPromptText == rOtherObj.m_aCustomPromptText && mbLineIsOutsideGeometry == rOtherObj.mbLineIsOutsideGeometry && m_bMarkProt == rOtherObj.m_bMarkProt && m_bIs3DObj == rOtherObj.m_bIs3DObj && m_bIsEdge == rOtherObj.m_bIsEdge && m_bClosedObj == rOtherObj.m_bClosedObj && m_bNotVisibleAsMaster == rOtherObj.m_bNotVisibleAsMaster && m_bEmptyPresObj == rOtherObj.m_bEmptyPresObj && @@ -2578,6 +2579,10 @@ void SdrObject::SetEmptyPresObj(bool bEpt) m_bEmptyPresObj = bEpt; } +void SdrObject::SetCustomPromptText(const OUString& rVal) +{ + m_aCustomPromptText = rVal; +} void SdrObject::SetNotVisibleAsMaster(bool bFlg) { diff --git a/svx/source/svdraw/svdpage.cxx b/svx/source/svdraw/svdpage.cxx index 672f89b2bb87..755b95c284f2 100644 --- a/svx/source/svdraw/svdpage.cxx +++ b/svx/source/svdraw/svdpage.cxx @@ -1726,6 +1726,12 @@ void SdrPage::MakePageObjectsNamesUnique() } } +bool SdrPage::RestoreDefaultText(SdrObject* /*pObj*/, const OUString& /*rStr*/) +{ + assert(false); + return false; +} + const SdrPageGridFrameList* SdrPage::GetGridFrameList(const SdrPageView* /*pPV*/, const tools::Rectangle* /*pRect*/) const { return nullptr;
