diff --git a/cui/source/inc/cuitabarea.hxx b/cui/source/inc/cuitabarea.hxx index 7021a7aa7847..30feba7fe85f 100644 --- a/cui/source/inc/cuitabarea.hxx +++ b/cui/source/inc/cuitabarea.hxx @@ -551,6 +551,8 @@ private: void CalculateBitmapPresetSize(); sal_Int32 SearchBitmapList(std::u16string_view rBitmapName); sal_Int32 SearchBitmapList(const GraphicObject& rGraphicObject); + tools::Long AddBitmap(const GraphicObject& rGraphicObject, const OUString& rName, + bool bOnlyForThisDocument = false); public: SvxBitmapTabPage(weld::Container* pPage, weld::DialogController* pController, const SfxItemSet& rInAttrs); diff --git a/cui/source/tabpages/tpbitmap.cxx b/cui/source/tabpages/tpbitmap.cxx index 9e016879b4cb..a4154aa344c8 100644 --- a/cui/source/tabpages/tpbitmap.cxx +++ b/cui/source/tabpages/tpbitmap.cxx @@ -37,8 +37,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -167,9 +169,13 @@ void SvxBitmapTabPage::ActivatePage( const SfxItemSet& rSet ) sal_Int32 nPos( 0 ); if ( !aItem.isPattern() ) { - nPos = SearchBitmapList( aItem.GetGraphicObject() ); - if (nPos == -1) + const GraphicObject& aGraphicObj = aItem.GetGraphicObject(); + if (aGraphicObj.GetType() != GraphicType::Bitmap) return; + + nPos = SearchBitmapList(aGraphicObj); + if (nPos == -1) + nPos = AddBitmap(aGraphicObj, aItem.GetName(), /*OnlyForThisDocument=*/true); } else { @@ -781,18 +787,7 @@ IMPL_LINK_NOARG(SvxBitmapTabPage, ClickImportHdl, weld::Button&, void) pDlg.disposeAndClear(); if( !nError ) - { - m_pBitmapList->Insert(std::make_unique(aGraphic, aName), nCount); - - sal_Int32 nId = m_xBitmapLB->GetItemId( nCount - 1 ); - BitmapEx aBitmap = m_pBitmapList->GetBitmapForPreview( nCount, m_xBitmapLB->GetIconSize() ); - - m_xBitmapLB->InsertItem( nId + 1, Image(aBitmap), aName ); - m_xBitmapLB->SelectItem( nId + 1 ); - m_nBitmapListState |= ChangeType::MODIFIED; - - ModifyBitmapHdl(m_xBitmapLB.get()); - } + AddBitmap(aGraphic, aName); } else { @@ -836,4 +831,31 @@ sal_Int32 SvxBitmapTabPage::SearchBitmapList(std::u16string_view rBitmapName) return nPos; } +tools::Long SvxBitmapTabPage::AddBitmap(const GraphicObject& rGraphicObject, const OUString& rName, + bool bOnlyForThisDocument) +{ + const tools::Long nLastPos = m_pBitmapList->Count(); + + auto xBitmapEntry = std::make_unique(rGraphicObject, rName); + if (bOnlyForThisDocument) + xBitmapEntry->SetSavingAllowed(false); + m_pBitmapList->Insert(std::move(xBitmapEntry), nLastPos); + + BitmapEx aBitmap = m_pBitmapList->GetBitmapForPreview(nLastPos, m_xBitmapLB->GetIconSize()); + + const sal_uInt16 nHighestId = m_xBitmapLB->GetItemId(nLastPos - 1); + m_xBitmapLB->InsertItem(nHighestId + 1, Image(aBitmap), rName); + m_xBitmapLB->SelectItem(nHighestId + 1); + m_nBitmapListState |= ChangeType::MODIFIED; + + ModifyBitmapHdl(m_xBitmapLB.get()); + + // inform sidebar, etc. that the list of images has changed. + SfxViewFrame* pViewFrame = SfxViewFrame::Current(); + if (pViewFrame) + pViewFrame->GetBindings().Invalidate(SID_ATTR_PAGE_BITMAP, /*ClearCacheStatus=*/true); + + return nLastPos; +} + /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/include/svx/XPropertyEntry.hxx b/include/svx/XPropertyEntry.hxx index bfb0aa25c702..8ac1734a4196 100644 --- a/include/svx/XPropertyEntry.hxx +++ b/include/svx/XPropertyEntry.hxx @@ -28,6 +28,7 @@ class SVXCORE_DLLPUBLIC XPropertyEntry private: OUString maPropEntryName; BitmapEx maUiBitmap; + bool mbSavingAllowed; protected: XPropertyEntry(OUString aPropEntryName); @@ -40,11 +41,14 @@ public: XPropertyEntry& operator=(XPropertyEntry const&) = default; XPropertyEntry& operator=(XPropertyEntry&&) = default; + virtual std::unique_ptr Clone() const = 0; void SetName(const OUString& rPropEntryName) { maPropEntryName = rPropEntryName; } const OUString& GetName() const { return maPropEntryName; } void SetUiBitmap(const BitmapEx& rUiBitmap) { maUiBitmap = rUiBitmap; } const BitmapEx& GetUiBitmap() const { return maUiBitmap; } + void SetSavingAllowed(bool bSet) { mbSavingAllowed = bSet; } + bool GetSavingAllowed() const { return mbSavingAllowed; } }; #endif // INCLUDED_SVX_XPROPERTYENTRY_HXX diff --git a/include/svx/xtable.hxx b/include/svx/xtable.hxx index 43ea9820d5a1..5725d02dd76e 100644 --- a/include/svx/xtable.hxx +++ b/include/svx/xtable.hxx @@ -46,6 +46,7 @@ private: public: XColorEntry(const Color& rColor, const OUString& rName); + std::unique_ptr Clone() const override; const Color& GetColor() const { @@ -61,6 +62,7 @@ private: public: XLineEndEntry(basegfx::B2DPolyPolygon aB2DPolyPolygon, const OUString& rName); XLineEndEntry(const XLineEndEntry& rOther); + std::unique_ptr Clone() const override; const basegfx::B2DPolyPolygon& GetLineEnd() const { @@ -76,6 +78,7 @@ private: public: XDashEntry(const XDash& rDash, const OUString& rName); XDashEntry(const XDashEntry& rOther); + std::unique_ptr Clone() const override; const XDash& GetDash() const { @@ -91,6 +94,7 @@ private: public: XHatchEntry(const XHatch& rHatch, const OUString& rName); XHatchEntry(const XHatchEntry& rOther); + std::unique_ptr Clone() const override; const XHatch& GetHatch() const { @@ -106,6 +110,7 @@ private: public: XGradientEntry(const basegfx::BGradient& rGradient, const OUString& rName); XGradientEntry(const XGradientEntry& rOther); + std::unique_ptr Clone() const override; const basegfx::BGradient& GetGradient() const { @@ -121,6 +126,7 @@ private: public: XBitmapEntry(const GraphicObject& rGraphicObject, const OUString& rName); XBitmapEntry(const XBitmapEntry& rOther); + std::unique_ptr Clone() const override; const GraphicObject& GetGraphicObject() const { @@ -167,6 +173,9 @@ protected: bool isValidIdx(tools::Long nIndex) const; virtual BitmapEx CreateBitmapForUI(tools::Long nIndex) = 0; +private: + bool mbNeedsExportableList; // impl: avoid seldom-needed, expensive list cloning + public: XPropertyList(const XPropertyList&) = delete; XPropertyList& operator=(const XPropertyList&) = delete; diff --git a/svx/source/xoutdev/XPropertyEntry.cxx b/svx/source/xoutdev/XPropertyEntry.cxx index 2791946838c9..14b54cb8c6b3 100644 --- a/svx/source/xoutdev/XPropertyEntry.cxx +++ b/svx/source/xoutdev/XPropertyEntry.cxx @@ -22,6 +22,7 @@ XPropertyEntry::XPropertyEntry(OUString aPropEntryName) : maPropEntryName(std::move(aPropEntryName)) + , mbSavingAllowed(true) { } diff --git a/svx/source/xoutdev/xtable.cxx b/svx/source/xoutdev/xtable.cxx index ea7385063701..ab6cfc60882c 100644 --- a/svx/source/xoutdev/xtable.cxx +++ b/svx/source/xoutdev/xtable.cxx @@ -36,6 +36,11 @@ XColorEntry::XColorEntry(const Color& rColor, const OUString& rName) { } +std::unique_ptr XColorEntry::Clone() const +{ + return std::make_unique(m_aColor, GetName()); +} + XLineEndEntry::XLineEndEntry(basegfx::B2DPolyPolygon _aB2DPolyPolygon, const OUString& rName) : XPropertyEntry(rName), m_aB2DPolyPolygon(std::move(_aB2DPolyPolygon)) @@ -48,6 +53,11 @@ XLineEndEntry::XLineEndEntry(const XLineEndEntry& rOther) { } +std::unique_ptr XLineEndEntry::Clone() const +{ + return std::make_unique(*this); +} + XDashEntry::XDashEntry(const XDash& rDash, const OUString& rName) : XPropertyEntry(rName), m_aDash(rDash) @@ -60,6 +70,11 @@ m_aDash(rOther.m_aDash) { } +std::unique_ptr XDashEntry::Clone() const +{ + return std::make_unique(*this); +} + XHatchEntry::XHatchEntry(const XHatch& rHatch, const OUString& rName) : XPropertyEntry(rName), m_aHatch(rHatch) @@ -72,6 +87,11 @@ XHatchEntry::XHatchEntry(const XHatchEntry& rOther) { } +std::unique_ptr XHatchEntry::Clone() const +{ + return std::make_unique(*this); +} + XGradientEntry::XGradientEntry(const basegfx::BGradient& rGradient, const OUString& rName) : XPropertyEntry(rName), m_aGradient(rGradient) @@ -84,6 +104,11 @@ XGradientEntry::XGradientEntry(const XGradientEntry& rOther) { } +std::unique_ptr XGradientEntry::Clone() const +{ + return std::make_unique(*this); +} + XBitmapEntry::XBitmapEntry(const GraphicObject& rGraphicObject, const OUString& rName) : XPropertyEntry(rName), maGraphicObject(rGraphicObject) @@ -96,6 +121,11 @@ XBitmapEntry::XBitmapEntry(const XBitmapEntry& rOther) { } +std::unique_ptr XBitmapEntry::Clone() const +{ + return std::make_unique(*this); +} + XPropertyList::XPropertyList( XPropertyListType type, OUString aPath, OUString aReferer @@ -105,6 +135,7 @@ XPropertyList::XPropertyList( maReferer (std::move( aReferer )), mbListDirty ( true ), mbEmbedInDocument( false ) + , mbNeedsExportableList(false) { // fprintf (stderr, "Create type %d count %d\n", (int)meType, count++); } @@ -183,6 +214,9 @@ void XPropertyList::Insert(std::unique_ptr pEntry, tools::Long n return; } + if (!pEntry->GetSavingAllowed()) + mbNeedsExportableList = true; + if (isValidIdx(nIndex)) { maList.insert( maList.begin()+nIndex, std::move(pEntry) ); } else { @@ -302,8 +336,27 @@ bool XPropertyList::Save() if( aURL.getExtension().isEmpty() ) aURL.setExtension( GetDefaultExt() ); + XPropertyListRef rExportableList = CreatePropertyList(meType, maPath, ""); + if (mbNeedsExportableList) + { + rExportableList->SetName(maName); + rExportableList->SetDirty(mbListDirty); + bool bHasUnsaveableEntry = false; + for (const std::unique_ptr& rEntry : maList) + { + if (rEntry->GetSavingAllowed()) + rExportableList->Insert(rEntry->Clone()); + else + bHasUnsaveableEntry = true; + } + if (!bHasUnsaveableEntry) + mbNeedsExportableList = false; + } + css::uno::Reference xExportableNameContainer + = mbNeedsExportableList ? rExportableList->createInstance() : createInstance(); + return SvxXMLXTableExportComponent::save( aURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ), - createInstance(), + xExportableNameContainer, uno::Reference< embed::XStorage >(), nullptr ); } diff --git a/sw/qa/uitest/data/paragraphAreaFill.odt b/sw/qa/uitest/data/paragraphAreaFill.odt new file mode 100644 index 000000000000..01baf1592621 Binary files /dev/null and b/sw/qa/uitest/data/paragraphAreaFill.odt differ diff --git a/sw/qa/uitest/writer_tests8/tdf125969.py b/sw/qa/uitest/writer_tests8/tdf125969.py new file mode 100644 index 000000000000..2334bec06fc4 --- /dev/null +++ b/sw/qa/uitest/writer_tests8/tdf125969.py @@ -0,0 +1,81 @@ +# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*- +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +from libreoffice.uno.propertyvalue import mkPropertyValues + +from uitest.framework import UITestCase +from uitest.uihelper.common import get_url_for_data_file +from uitest.uihelper.common import select_pos, get_state_as_dict +import time + +# bug 125969: make in-use bitmap-area-fill available for re-use, but ONLY IN THE SAME DOCUMENT +class tdf125969(UITestCase): + + number_of_images = 0 + + def click_button(self, dialog, button): + xButton = dialog.getChild(button) + xButton.executeAction("CLICK", tuple()) + + def test_tdf125969(self): + with self.ui_test.load_file(get_url_for_data_file("paragraphAreaFill.odt")): + xWriterDoc = self.xUITest.getTopFocusWindow() + xWriterEdit = xWriterDoc.getChild("writer_edit") + + self.xUITest.executeCommand(".uno:Sidebar") #turn on sidebar + xWriterEdit.executeAction("SIDEBAR", mkPropertyValues({"PANEL": "PageStylesPanel"})) + + # Get baseline from sidebar: count number of initially available bitmaps by default + backgroundType = xWriterEdit.getChild('bgselect') #type of background: color, gradient, ... + self.ui_test.wait_until_property_is_updated(backgroundType, "SelectEntryText", "Bitmap") + + imageCollection = xWriterEdit.getChild("lbbitmap") #listbox containing image names + number_of_images = get_state_as_dict(imageCollection)["EntryCount"] + # print (get_state_as_dict(imageCollection)) + # time.sleep (10) + + # The paragraph area has a custom background logo - which we want to become available + # for re-use everywhere as a background fill + + # visit the paragraph background property - which now auto-adds it to the collection + with self.ui_test.execute_dialog_through_command(".uno:ParagraphDialog", close_button="cancel") as xDialog: + tabcontrol = xDialog.getChild("tabcontrol") + select_pos(tabcontrol, "8") # area tab + #time.sleep(1) + + self.ui_test.wait_until_property_is_updated(imageCollection, "SelectEntryText", "Painted White") + # xToolkit = self.xContext.ServiceManager.createInstance('com.sun.star.awt.Toolkit') + # xToolkit.waitUntilAllIdlesDispatched() + time.sleep (1) + # test: the paragraph's wasta-offline logo was added and the list box was refreshed + self.assertEqual(int(number_of_images) + 1, int(get_state_as_dict(imageCollection)["EntryCount"])) + + # A new document must not have access to the collected images from another document + with self.ui_test.create_doc_in_start_center("writer"): + xWriterDoc = self.xUITest.getTopFocusWindow() + xWriterEdit = xWriterDoc.getChild("writer_edit") + + # because I don't know how to change the sidebar to bitmap mode, use the page dialog + with self.ui_test.execute_dialog_through_command(".uno:PageDialog", close_button="ok") as xDialog: + tabcontrol = xDialog.getChild("tabcontrol") + select_pos(tabcontrol, "2") # area tab + self.click_button(xDialog, 'btnbitmap') + #time.sleep (2) + + backgroundType = xWriterEdit.getChild('bgselect') + imageCollection = xWriterEdit.getChild("lbbitmap") + self.ui_test.wait_until_property_is_updated(backgroundType, "SelectEntryText", "Bitmap") + # This number MUST NOT be higher than the initial state. + # We must not allow document images to leak into the user profile + self.assertEqual(number_of_images, get_state_as_dict(imageCollection)["EntryCount"]) + #time.sleep (10) + + # xWriterEdit.getChild("bogus for debugging") + + self.xUITest.executeCommand(".uno:Sidebar") # good idea to turn off sidebar again +# vim: set shiftwidth=4 softtabstop=4 expandtab: