cui/source/inc/cuitabarea.hxx           |    2 
 cui/source/tabpages/tpbitmap.cxx        |   50 ++++++++++++++-----
 include/svx/XPropertyEntry.hxx          |    4 +
 include/svx/xtable.hxx                  |    9 +++
 svx/source/xoutdev/XPropertyEntry.cxx   |    1 
 svx/source/xoutdev/xtable.cxx           |   55 +++++++++++++++++++++
 sw/qa/uitest/data/paragraphAreaFill.odt |binary
 sw/qa/uitest/writer_tests8/tdf125969.py |   81 ++++++++++++++++++++++++++++++++
 8 files changed, 187 insertions(+), 15 deletions(-)

New commits:
commit a366cd34a85a21210939482d229d6d2dd9c1087c
Author:     Justin Luth <justin.l...@collabora.com>
AuthorDate: Thu Nov 7 15:52:53 2024 -0500
Commit:     Justin Luth <justin.l...@collabora.com>
CommitDate: Tue Dec 10 00:07:59 2024 +0100

    tdf#125969 cui: add in-use area image to bitmap list
    
    This fixes a five year old (non-)easyhack with 3 duplicates.
    Note the nice-to-have dependencies in the bug report(s).
    
    When the document has a unique background (area) image,
    it is now added to the list of available images
    IF/WHEN the user looks in the area tab.
    This allows the user to switch back after changing,
    or re-use it in other places in the document.
    
    Most of this patch ended up being plumbing to ensure that
    this added image is ONLY available to the current document,
    because it MUST NOT be saved to the user profile.
    
    This change affects all apps and all types of areas: NICE
      -tested Writer pages, paragraphs, headers, textbox, sidebar(page)
      -tested Calc page style
      -tested Draw page, sidebar(page), textbox
    
    Caveats:
    -the bitmap list is NOT updated at the time of document import,
     only when area property inspected.
     (The bug requesting inclusion at the time of import is tdf#100832).
    
    make -srj1 UITest_writer_tests8 \
        UITEST_TEST_NAME=tdf125969.tdf125969.test_tdf125969 \
        SAL_USE_VCLPLUGIN=gen
    
    Change-Id: Ic9fea9b199602c4df1376e781d5df019526473d5
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/176253
    Tested-by: Jenkins
    Reviewed-by: Justin Luth <jl...@mail.com>

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 <dialmgr.hxx>
 #include <svx/dlgutil.hxx>
 #include <svl/intitem.hxx>
+#include <sfx2/bindings.hxx>
 #include <sfx2/objsh.hxx>
 #include <sfx2/opengrf.hxx>
+#include <sfx2/viewfrm.hxx>
 #include <vcl/image.hxx>
 #include <vcl/svapp.hxx>
 #include <vcl/weld.hxx>
@@ -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<XBitmapEntry>(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<XBitmapEntry>(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<XPropertyEntry> 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<XPropertyEntry> 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<XPropertyEntry> 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<XPropertyEntry> 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<XPropertyEntry> 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<XPropertyEntry> 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<XPropertyEntry> 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<XPropertyEntry> XColorEntry::Clone() const
+{
+    return std::make_unique<XColorEntry>(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<XPropertyEntry> XLineEndEntry::Clone() const
+{
+    return std::make_unique<XLineEndEntry>(*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<XPropertyEntry> XDashEntry::Clone() const
+{
+    return std::make_unique<XDashEntry>(*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<XPropertyEntry> XHatchEntry::Clone() const
+{
+    return std::make_unique<XHatchEntry>(*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<XPropertyEntry> XGradientEntry::Clone() const
+{
+    return std::make_unique<XGradientEntry>(*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<XPropertyEntry> XBitmapEntry::Clone() const
+{
+    return std::make_unique<XBitmapEntry>(*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
", (int)meType, count++);
 }
@@ -183,6 +214,9 @@ void XPropertyList::Insert(std::unique_ptr<XPropertyEntry> 
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<XPropertyEntry>& rEntry : maList)
+        {
+            if (rEntry->GetSavingAllowed())
+                rExportableList->Insert(rEntry->Clone());
+            else
+                bHasUnsaveableEntry = true;
+        }
+        if (!bHasUnsaveableEntry)
+            mbNeedsExportableList = false;
+    }
+    css::uno::Reference<css::container::XNameContainer> 
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:

Reply via email to