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 <jluth@mail.com>
This commit is contained in:
Justin Luth 2024-11-07 15:52:53 -05:00
parent d97085cc6c
commit a366cd34a8
8 changed files with 187 additions and 15 deletions

View file

@ -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);

View file

@ -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: */

View file

@ -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

View file

@ -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;

View file

@ -22,6 +22,7 @@
XPropertyEntry::XPropertyEntry(OUString aPropEntryName)
: maPropEntryName(std::move(aPropEntryName))
, mbSavingAllowed(true)
{
}

View file

@ -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\n", (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 );
}

Binary file not shown.

View file

@ -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: