weld custom animation panel

and align animation panel and slide transitions panel padding/spacing

Change-Id: I25a1d10894de635ebb9c8a1cba358940886125c2
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/91006
Tested-by: Jenkins
Reviewed-by: Caolán McNamara <caolanm@redhat.com>
diff --git a/sd/Library_sd.mk b/sd/Library_sd.mk
index 048a0c4..43b168b 100644
--- a/sd/Library_sd.mk
+++ b/sd/Library_sd.mk
@@ -183,7 +183,6 @@ $(eval $(call gb_Library_add_exception_objects,sd,\
	sd/source/ui/accessibility/AccessibleSlideSorterView \
	sd/source/ui/accessibility/AccessibleViewForwarder \
	sd/source/ui/accessibility/SdShapeTypes \
	sd/source/ui/animations/CategoryListBox \
	sd/source/ui/animations/CustomAnimationDialog \
	sd/source/ui/animations/CustomAnimationList \
	sd/source/ui/animations/CustomAnimationPane \
diff --git a/sd/inc/strings.hrc b/sd/inc/strings.hrc
index 26ec246..84cfea8 100644
--- a/sd/inc/strings.hrc
+++ b/sd/inc/strings.hrc
@@ -444,7 +444,6 @@
#define STR_CUSTOMANIMATION_BROWSE_SOUND                NC_("STR_CUSTOMANIMATION_BROWSE_SOUND", "Other sound...")
#define STR_CUSTOMANIMATION_SAMPLE                      NC_("STR_CUSTOMANIMATION_SAMPLE", "Sample")
#define STR_CUSTOMANIMATION_TRIGGER                     NC_("STR_CUSTOMANIMATION_TRIGGER", "Trigger")
#define STR_CUSTOMANIMATION_LIST_HELPTEXT               NC_("STR_CUSTOMANIMATION_LIST_HELPTEXT", "First select the slide element and then click 'Add...' to add an animation effect.")
#define STR_CUSTOMANIMATION_USERPATH                    NC_("STR_CUSTOMANIMATION_USERPATH", "User paths")
#define STR_CUSTOMANIMATION_ENTRANCE                    NC_("STR_CUSTOMANIMATION_ENTRANCE", "Entrance: %1")
#define STR_CUSTOMANIMATION_EMPHASIS                    NC_("STR_CUSTOMANIMATION_EMPHASIS", "Emphasis: %1")
diff --git a/sd/source/ui/animations/CategoryListBox.cxx b/sd/source/ui/animations/CategoryListBox.cxx
deleted file mode 100644
index 054cb84..0000000
--- a/sd/source/ui/animations/CategoryListBox.cxx
+++ /dev/null
@@ -1,93 +0,0 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-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/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include "CategoryListBox.hxx"
#include <vcl/builderfactory.hxx>

#include <vcl/event.hxx>

namespace sd {

CategoryListBox::CategoryListBox( vcl::Window* pParent )
: ListBox( pParent, WB_TABSTOP | WB_BORDER )
{
    EnableUserDraw( true );
    SetDoubleClickHdl( LINK( this, CategoryListBox, implDoubleClickHdl ) );
}

VCL_BUILDER_FACTORY(CategoryListBox)

CategoryListBox::~CategoryListBox()
{
}

void  CategoryListBox::InsertCategory( const OUString& rStr )
{
    sal_Int32  n = ListBox::InsertEntry( rStr );
    if( n != LISTBOX_ENTRY_NOTFOUND )
        ListBox::SetEntryFlags( n, ListBox::GetEntryFlags(n) | ListBoxEntryFlags::DisableSelection );
}

void CategoryListBox::UserDraw( const UserDrawEvent& rUDEvt )
{
    const sal_uInt16 nItem = rUDEvt.GetItemId();

    if( ListBox::GetEntryFlags(nItem) & ListBoxEntryFlags::DisableSelection )
    {
        ::tools::Rectangle aOutRect( rUDEvt.GetRect() );
        vcl::RenderContext* pDev = rUDEvt.GetRenderContext();

        // fill the background
        Color aColor (GetSettings().GetStyleSettings().GetDialogColor());

        pDev->SetFillColor (aColor);
        pDev->SetLineColor ();
        pDev->DrawRect(aOutRect);

        // Erase the four corner pixels to make the rectangle appear rounded.
        pDev->SetLineColor( GetSettings().GetStyleSettings().GetWindowColor());
        pDev->DrawPixel( aOutRect.TopLeft());
        pDev->DrawPixel( Point(aOutRect.Right(), aOutRect.Top()));
        pDev->DrawPixel( Point(aOutRect.Left(), aOutRect.Bottom()));
        pDev->DrawPixel( Point(aOutRect.Right(), aOutRect.Bottom()));

        // draw the category title
        pDev->DrawText (aOutRect, GetEntry(nItem), DrawTextFlags::Center );
    }
    else
    {
        DrawEntry( rUDEvt );
    }
}

IMPL_LINK_NOARG(CategoryListBox, implDoubleClickHdl, ListBox&, void)
{
    CaptureMouse();
}

void CategoryListBox::MouseButtonUp( const MouseEvent& rMEvt )
{
    ReleaseMouse();
    if (!( rMEvt.IsLeft() && (rMEvt.GetClicks() == 2) ))
    {
        ListBox::MouseButtonUp( rMEvt );
    }
}

}
diff --git a/sd/source/ui/animations/CategoryListBox.hxx b/sd/source/ui/animations/CategoryListBox.hxx
deleted file mode 100644
index 8ae5af8..0000000
--- a/sd/source/ui/animations/CategoryListBox.hxx
+++ /dev/null
@@ -1,45 +0,0 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-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/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#ifndef INCLUDED_SD_SOURCE_UI_ANIMATIONS_CATEGORYLISTBOX_HXX
#define INCLUDED_SD_SOURCE_UI_ANIMATIONS_CATEGORYLISTBOX_HXX

#include <vcl/lstbox.hxx>

namespace sd {

class CategoryListBox : public ListBox
{
public:
    explicit CategoryListBox( vcl::Window* pParent );
    virtual ~CategoryListBox() override;

    virtual void        MouseButtonUp( const MouseEvent& rMEvt ) override;

    void                InsertCategory( const OUString& rStr );

    DECL_LINK(implDoubleClickHdl, ListBox&, void);

private:
    virtual void    UserDraw( const UserDrawEvent& rUDEvt ) override;
};

}

#endif // INCLUDED_SD_SOURCE_UI_ANIMATIONS_CATEGORYLISTBOX_HXX
diff --git a/sd/source/ui/animations/CustomAnimationDialog.cxx b/sd/source/ui/animations/CustomAnimationDialog.cxx
index 3c60f17..c62a738 100644
--- a/sd/source/ui/animations/CustomAnimationDialog.cxx
+++ b/sd/source/ui/animations/CustomAnimationDialog.cxx
@@ -163,6 +163,13 @@ PresetPropertyBox::~PresetPropertyBox()
    mpControl.disposeAndClear();
}

SdPropertySubControl::SdPropertySubControl(weld::Container* pParent)
    : mxBuilder(Application::CreateBuilder(pParent, "modules/simpress/ui/customanimationfragment.ui"))
    , mxContainer(mxBuilder->weld_container("EffectFragment"))
    , mpParent(pParent)
{
}

Any PresetPropertyBox::getValue()
{
    return makeAny( maPropertyValues[mpControl->GetSelectedEntryPos()] );
@@ -175,6 +182,7 @@ Control* PresetPropertyBox::getControl()

SdPropertySubControl::~SdPropertySubControl()
{
    mpParent->move(mxContainer.get(), nullptr);
}

namespace {
@@ -221,6 +229,7 @@ void SdPresetPropertyBox::setValue( const Any& rValue, const OUString& rPresetId

    mxControl->freeze();
    mxControl->clear();
    maPropertyValues.clear();
    int nPos = -1;

    const CustomAnimationPresets& rPresets = CustomAnimationPresets::getCustomAnimationPresets();
diff --git a/sd/source/ui/animations/CustomAnimationDialog.hxx b/sd/source/ui/animations/CustomAnimationDialog.hxx
index 46c8178..1ed6972 100644
--- a/sd/source/ui/animations/CustomAnimationDialog.hxx
+++ b/sd/source/ui/animations/CustomAnimationDialog.hxx
@@ -116,12 +116,7 @@ private:
class SdPropertySubControl
{
public:
    explicit SdPropertySubControl(weld::Container* pParent)
        : mxBuilder(Application::CreateBuilder(pParent, "modules/simpress/ui/customanimationfragment.ui"))
        , mxContainer(mxBuilder->weld_container("EffectFragment"))
    {
    }

    explicit SdPropertySubControl(weld::Container* pParent);
    virtual ~SdPropertySubControl();

    virtual             css::uno::Any getValue() = 0;
@@ -139,6 +134,7 @@ public:
protected:
    std::unique_ptr<weld::Builder> mxBuilder;
    std::unique_ptr<weld::Container> mxContainer;
    weld::Container* mpParent;
};

class PropertyControl : public ListBox
diff --git a/sd/source/ui/animations/CustomAnimationList.cxx b/sd/source/ui/animations/CustomAnimationList.cxx
index 4793d8a..559ac92a 100644
--- a/sd/source/ui/animations/CustomAnimationList.cxx
+++ b/sd/source/ui/animations/CustomAnimationList.cxx
@@ -29,20 +29,18 @@
#include <com/sun/star/drawing/XDrawPage.hpp>
#include "CustomAnimationList.hxx"
#include <CustomAnimationPreset.hxx>
#include <vcl/settings.hxx>
#include <vcl/builderfactory.hxx>
#include <vcl/commandevent.hxx>
#include <vcl/event.hxx>
#include <vcl/image.hxx>
#include <vcl/settings.hxx>
#include <vcl/svapp.hxx>
#include <vcl/weldutils.hxx>
#include <tools/debug.hxx>
#include <tools/gen.hxx>
#include <osl/diagnose.h>

#include <sdresid.hxx>

#include <vcl/svlbitm.hxx>
#include <vcl/treelistentry.hxx>
#include <vcl/viewdataentry.hxx>


#include <strings.hrc>
#include <bitmaps.hlst>

@@ -215,33 +213,35 @@ static OUString getDescription( const Any& rTarget, bool bWithText )
    return aDescription;
}

class CustomAnimationListEntryItem : public SvLBoxString
class CustomAnimationListEntryItem
{
public:
    CustomAnimationListEntryItem(const OUString& aDescription,
                                 const CustomAnimationEffectPtr& pEffect, CustomAnimationList* pParent);
    void InitViewData(SvTreeListBox*,SvTreeListEntry*,SvViewDataItem* = nullptr) override;
    virtual std::unique_ptr<SvLBoxItem> Clone(SvLBoxItem const * pSource) const override;
                                 const CustomAnimationEffectPtr& pEffect);
    const CustomAnimationEffectPtr& getEffect() const { return mpEffect; }

    virtual void Paint(const Point&, SvTreeListBox& rDev, vcl::RenderContext& rRenderContext,
                       const SvViewDataEntry* pView,const SvTreeListEntry& rEntry) override;
    Size GetSize(vcl::RenderContext& rRenderContext);
    void Paint(vcl::RenderContext& rRenderContext, const ::tools::Rectangle& rRect, bool bSelected);
    void PaintEffect(vcl::RenderContext& rRenderContext, const ::tools::Rectangle& rRect, bool bSelected);
    void PaintTrigger(vcl::RenderContext& rRenderContext, const ::tools::Rectangle& rRect);

private:
    VclPtr<CustomAnimationList> mpParent;
    OUString        msDescription;
    OUString        msEffectName;
    CustomAnimationEffectPtr mpEffect;

    static const long nIconWidth = 19;
    static const long nItemMinHeight = 38;
};

CustomAnimationListEntryItem::CustomAnimationListEntryItem( const OUString& aDescription, const CustomAnimationEffectPtr& pEffect, CustomAnimationList* pParent  )
: SvLBoxString( aDescription )
, mpParent( pParent )
, msDescription( aDescription )
, msEffectName( OUString() )
, mpEffect(pEffect)
CustomAnimationListEntryItem::CustomAnimationListEntryItem(const OUString& aDescription, const CustomAnimationEffectPtr& pEffect)
    : msDescription(aDescription)
    , msEffectName(OUString())
    , mpEffect(pEffect)
{
    switch(mpEffect->getPresetClass())
    if (!mpEffect)
        return;
    switch (mpEffect->getPresetClass())
    {
    case EffectPresetClass::ENTRANCE:
        msEffectName = SdResId(STR_CUSTOMANIMATION_ENTRANCE); break;
@@ -257,30 +257,85 @@ CustomAnimationListEntryItem::CustomAnimationListEntryItem( const OUString& aDes
    msEffectName = msEffectName.replaceFirst( "%1" , CustomAnimationPresets::getCustomAnimationPresets().getUINameForPresetId(mpEffect->getPresetId()));
}

void CustomAnimationListEntryItem::InitViewData( SvTreeListBox* pView, SvTreeListEntry* pEntry, SvViewDataItem* pViewData )
IMPL_STATIC_LINK(CustomAnimationList, CustomRenderHdl, weld::TreeView::render_args, aPayload, void)
{
    if( !pViewData )
        pViewData = pView->GetViewDataItem( pEntry, this );
    vcl::RenderContext& rRenderContext = std::get<0>(aPayload);
    const ::tools::Rectangle& rRect = std::get<1>(aPayload);
    bool bSelected = std::get<2>(aPayload);
    const OUString& rId = std::get<3>(aPayload);

    long width = pView->GetTextWidth( msDescription ) + nIconWidth;
    if( width < (pView->GetTextWidth( msEffectName ) + 2*nIconWidth))
        width = pView->GetTextWidth( msEffectName ) + 2*nIconWidth;
    CustomAnimationListEntryItem* pItem = reinterpret_cast<CustomAnimationListEntryItem*>(rId.toInt64());

    Size aSize( width, pView->GetTextHeight() );
    if( aSize.Height() < nItemMinHeight )
        aSize.setHeight( nItemMinHeight );
    pViewData->mnWidth = aSize.Width();
    pViewData->mnHeight = aSize.Height();
    pItem->Paint(rRenderContext, rRect, bSelected);
}

void CustomAnimationListEntryItem::Paint(const Point& rPos, SvTreeListBox& rDev, vcl::RenderContext& rRenderContext,
                                         const SvViewDataEntry* /*pView*/, const SvTreeListEntry& rEntry)
IMPL_STATIC_LINK(CustomAnimationList, CustomGetSizeHdl, weld::TreeView::get_size_args, aPayload, Size)
{
    vcl::RenderContext& rRenderContext = aPayload.first;
    const OUString& rId = aPayload.second;

    const SvViewDataItem* pViewData = mpParent->GetViewDataItem(&rEntry, this);
    CustomAnimationListEntryItem* pItem = reinterpret_cast<CustomAnimationListEntryItem*>(rId.toInt64());
    return pItem->GetSize(rRenderContext);
}

    Point aPos(rPos);
    int nItemHeight = pViewData->mnHeight;
Size CustomAnimationListEntryItem::GetSize(vcl::RenderContext& rRenderContext)
{
    auto width = rRenderContext.GetTextWidth( msDescription ) + nIconWidth;
    if (width < (rRenderContext.GetTextWidth( msEffectName ) + 2*nIconWidth))
        width = rRenderContext.GetTextWidth( msEffectName ) + 2*nIconWidth;

    Size aSize(width, rRenderContext.GetTextHeight());
    if (aSize.Height() < nItemMinHeight)
        aSize.setHeight(nItemMinHeight);
    return aSize;
}

void CustomAnimationListEntryItem::PaintTrigger(vcl::RenderContext& rRenderContext, const ::tools::Rectangle& rRect)
{
    Size aSize(rRect.GetSize());

    ::tools::Rectangle aOutRect(rRect);

    // fill the background
    Color aColor(rRenderContext.GetSettings().GetStyleSettings().GetDialogColor());

    rRenderContext.Push();
    rRenderContext.SetFillColor(aColor);
    rRenderContext.SetLineColor();
    rRenderContext.DrawRect(aOutRect);

    // Erase the four corner pixels to make the rectangle appear rounded.
    rRenderContext.SetLineColor(rRenderContext.GetSettings().GetStyleSettings().GetWindowColor());
    rRenderContext.DrawPixel(aOutRect.TopLeft());
    rRenderContext.DrawPixel(Point(aOutRect.Right(), aOutRect.Top()));
    rRenderContext.DrawPixel(Point(aOutRect.Left(), aOutRect.Bottom()));
    rRenderContext.DrawPixel(Point(aOutRect.Right(), aOutRect.Bottom()));

    // draw the category title

    int nVertBorder = ((aSize.Height() - rRenderContext.GetTextHeight()) >> 1);
    int nHorzBorder = rRenderContext.LogicToPixel(Size(3, 3), MapMode(MapUnit::MapAppFont)).Width();

    aOutRect.AdjustLeft(nHorzBorder );
    aOutRect.AdjustRight( -nHorzBorder );
    aOutRect.AdjustTop( nVertBorder );
    aOutRect.AdjustBottom( -nVertBorder );

    rRenderContext.DrawText(aOutRect, rRenderContext.GetEllipsisString(msDescription, aOutRect.GetWidth()));
    rRenderContext.Pop();
}

void CustomAnimationListEntryItem::PaintEffect(vcl::RenderContext& rRenderContext, const ::tools::Rectangle& rRect, bool bSelected)
{
    rRenderContext.Push(PushFlags::TEXTCOLOR);
    const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings();
    if (bSelected)
        rRenderContext.SetTextColor(rStyleSettings.GetHighlightTextColor());
    else
        rRenderContext.SetTextColor(rStyleSettings.GetDialogTextColor());

    Point aPos(rRect.TopLeft());
    int nItemHeight = rRect.GetHeight();

    sal_Int16 nNodeType = mpEffect->getNodeType();
    if (nNodeType == EffectNodeType::ON_CLICK )
@@ -296,12 +351,12 @@ void CustomAnimationListEntryItem::Paint(const Point& rPos, SvTreeListBox& rDev,
        //FIXME With previous image not defined in CustomAnimation.src
    }

    aPos.AdjustX(nIconWidth );
    aPos.AdjustX(nIconWidth);

    //TODO, full width of widget ?
    rRenderContext.DrawText(aPos, rRenderContext.GetEllipsisString(msDescription, rRect.GetWidth()));

    rRenderContext.DrawText(aPos, rRenderContext.GetEllipsisString(msDescription, rDev.GetOutputSizePixel().Width() - aPos.X()));

    aPos.AdjustY(nIconWidth );
    aPos.AdjustY(nIconWidth);

    OUString sImage;
    switch (mpEffect->getPresetClass())
@@ -341,369 +396,129 @@ void CustomAnimationListEntryItem::Paint(const Point& rPos, SvTreeListBox& rDev,
    }

    aPos.AdjustX(nIconWidth );
    aPos.AdjustY((nItemHeight/2 - rDev.GetTextHeight()) >> 1 );
    aPos.AdjustY((nItemHeight/2 - rRenderContext.GetTextHeight()) >> 1 );

    rRenderContext.DrawText(aPos, rRenderContext.GetEllipsisString(msEffectName, rDev.GetOutputSizePixel().Width() - aPos.X()));
}

std::unique_ptr<SvLBoxItem> CustomAnimationListEntryItem::Clone(SvLBoxItem const *) const
{
    return nullptr;
}

namespace {

class CustomAnimationListEntry : public SvTreeListEntry
{
public:
    CustomAnimationListEntry();
    explicit CustomAnimationListEntry(const CustomAnimationEffectPtr& pEffect);

    const CustomAnimationEffectPtr& getEffect() const { return mpEffect; }

private:
    CustomAnimationEffectPtr mpEffect;
};

}

CustomAnimationListEntry::CustomAnimationListEntry()
{
}

CustomAnimationListEntry::CustomAnimationListEntry(const CustomAnimationEffectPtr& pEffect)
: mpEffect( pEffect )
{
}

namespace {

class CustomAnimationTriggerEntryItem : public SvLBoxString
{
public:
    explicit        CustomAnimationTriggerEntryItem( const OUString& aDescription );

    void            InitViewData( SvTreeListBox*,SvTreeListEntry*,SvViewDataItem* = nullptr ) override;
    virtual std::unique_ptr<SvLBoxItem> Clone(SvLBoxItem const * pSource) const override;
    virtual void Paint(const Point& rPos, SvTreeListBox& rOutDev, vcl::RenderContext& rRenderContext,
                       const SvViewDataEntry* pView, const SvTreeListEntry& rEntry) override;

private:
    OUString        msDescription;
    static const long nIconWidth = 19;
};

}

CustomAnimationTriggerEntryItem::CustomAnimationTriggerEntryItem( const OUString& aDescription )
: SvLBoxString( aDescription ), msDescription( aDescription )
{
}

void CustomAnimationTriggerEntryItem::InitViewData( SvTreeListBox* pView, SvTreeListEntry* pEntry, SvViewDataItem* pViewData )
{
    if( !pViewData )
        pViewData = pView->GetViewDataItem( pEntry, this );

    Size aSize(pView->GetTextWidth( msDescription ) + 2 * nIconWidth, pView->GetTextHeight() );
    if( aSize.Height() < nIconWidth )
        aSize.setHeight( nIconWidth );
    pViewData->mnWidth = aSize.Width();
    pViewData->mnHeight = aSize.Height();
}

void CustomAnimationTriggerEntryItem::Paint(const Point& rPos, SvTreeListBox& rDev, vcl::RenderContext& rRenderContext,
                                            const SvViewDataEntry* /*pView*/, const SvTreeListEntry& /*rEntry*/)
{
    Size aSize(rDev.GetOutputSizePixel().Width(), rDev.GetEntryHeight());

    Point aPos(0, rPos.Y());

    ::tools::Rectangle aOutRect(aPos, aSize);

    // fill the background
    Color aColor(rRenderContext.GetSettings().GetStyleSettings().GetDialogColor());

    rRenderContext.Push();
    rRenderContext.SetFillColor(aColor);
    rRenderContext.SetLineColor();
    rRenderContext.DrawRect(aOutRect);

    // Erase the four corner pixels to make the rectangle appear rounded.
    rRenderContext.SetLineColor(rRenderContext.GetSettings().GetStyleSettings().GetWindowColor());
    rRenderContext.DrawPixel(aOutRect.TopLeft());
    rRenderContext.DrawPixel(Point(aOutRect.Right(), aOutRect.Top()));
    rRenderContext.DrawPixel(Point(aOutRect.Left(), aOutRect.Bottom()));
    rRenderContext.DrawPixel(Point(aOutRect.Right(), aOutRect.Bottom()));

    // draw the category title

    int nVertBorder = ((aSize.Height() - rDev.GetTextHeight()) >> 1);
    int nHorzBorder = rRenderContext.LogicToPixel(Size(3, 3), MapMode(MapUnit::MapAppFont)).Width();

    aOutRect.AdjustLeft(nHorzBorder );
    aOutRect.AdjustRight( -nHorzBorder );
    aOutRect.AdjustTop( nVertBorder );
    aOutRect.AdjustBottom( -nVertBorder );

    rRenderContext.DrawText(aOutRect, rRenderContext.GetEllipsisString(msDescription, aOutRect.GetWidth()));
    rRenderContext.DrawText(aPos, rRenderContext.GetEllipsisString(msEffectName, rRect.GetWidth()));
    rRenderContext.Pop();
}

std::unique_ptr<SvLBoxItem> CustomAnimationTriggerEntryItem::Clone(SvLBoxItem const *) const
void CustomAnimationListEntryItem::Paint(vcl::RenderContext& rRenderContext, const ::tools::Rectangle& rRect, bool bSelected)
{
    return nullptr;
    if (mpEffect)
        PaintEffect(rRenderContext, rRect, bSelected);
    else
        PaintTrigger(rRenderContext, rRect);
}

CustomAnimationList::CustomAnimationList( vcl::Window* pParent )
    : SvTreeListBox( pParent, WB_TABSTOP | WB_BORDER | WB_HASLINES | WB_HASBUTTONS | WB_HASBUTTONSATROOT )
CustomAnimationList::CustomAnimationList(std::unique_ptr<weld::TreeView> xTreeView,
                                         std::unique_ptr<weld::Label> xLabel,
                                         std::unique_ptr<weld::Widget> xScrolledWindow)
    : mxTreeView(std::move(xTreeView))
    , maDropTargetHelper(*this)
    , mxEmptyLabel(std::move(xLabel))
    , mxEmptyLabelParent(std::move(xScrolledWindow))
    , mbIgnorePaint(false)
    , mpController(nullptr)
    , mnLastGroupId(0)
    , mpLastParentEntry(nullptr)
    , mpDndEffectDragging(nullptr)
    , mpDndEffectInsertBefore(nullptr)
    , mnPostExpandEvent(nullptr)
    , mnPostCollapseEvent(nullptr)
{
    EnableContextMenuHandling();
    SetSelectionMode( SelectionMode::Multiple );
    SetOptimalImageIndent();
    SetNodeDefaultImages();
    mxEmptyLabel->set_stack_background();

    SetDragDropMode(DragDropMode::CTRL_MOVE);
    mxTreeView->set_selection_mode(SelectionMode::Multiple);
    mxTreeView->set_column_custom_renderer(0);
    mxTreeView->connect_changed(LINK(this, CustomAnimationList, SelectHdl));
    mxTreeView->connect_key_press(LINK(this, CustomAnimationList, KeyInputHdl));
    mxTreeView->connect_popup_menu(LINK(this, CustomAnimationList, CommandHdl));
    mxTreeView->connect_row_activated(LINK(this, CustomAnimationList, DoubleClickHdl));
    mxTreeView->connect_expanding(LINK(this, CustomAnimationList, ExpandHdl));
    mxTreeView->connect_collapsing(LINK(this, CustomAnimationList, CollapseHdl));
    mxTreeView->connect_drag_begin(LINK(this, CustomAnimationList, DragBeginHdl));
    mxTreeView->connect_custom_get_size(LINK(this, CustomAnimationList, CustomGetSizeHdl));
    mxTreeView->connect_custom_render(LINK(this, CustomAnimationList, CustomRenderHdl));
}

CustomAnimationListDropTarget::CustomAnimationListDropTarget(CustomAnimationList& rTreeView)
    : DropTargetHelper(rTreeView.get_widget().get_drop_target())
    , m_rTreeView(rTreeView)
{
}

sal_Int8 CustomAnimationListDropTarget::AcceptDrop(const AcceptDropEvent& rEvt)
{
    sal_Int8 nAccept = m_rTreeView.AcceptDrop(rEvt);

    if (nAccept != DND_ACTION_NONE)
    {
        // to enable the autoscroll when we're close to the edges
        weld::TreeView& rWidget = m_rTreeView.get_widget();
        rWidget.get_dest_row_at_pos(rEvt.maPosPixel, nullptr);
    }

    return nAccept;
}

sal_Int8 CustomAnimationListDropTarget::ExecuteDrop(const ExecuteDropEvent& rEvt)
{
    return m_rTreeView.ExecuteDrop(rEvt);
}

// D'n'D #1: Record selected effects for drag'n'drop.
void CustomAnimationList::StartDrag( sal_Int8 nAction, const Point& rPosPixel )
IMPL_LINK(CustomAnimationList, DragBeginHdl, bool&, rUnsetDragIcon, bool)
{
    rUnsetDragIcon = false;

    // Record which effects are selected:
    // Since NextSelected(..) iterates through the selected items in the order they
    // were selected, create a sorted list for simpler drag'n'drop algorithms.
    mDndEffectsSelected.clear();
    for( SvTreeListEntry* pEntry = First(); pEntry; pEntry = Next(pEntry) )
    {
        if( IsSelected(pEntry) )
        {
            mDndEffectsSelected.push_back( pEntry );
        }
    }

    // Allow normal processing; this calls our NotifyStartDrag().
    SvTreeListBox::StartDrag( nAction, rPosPixel );
}

// D'n'D #2: Prepare selected element for moving.
DragDropMode CustomAnimationList::NotifyStartDrag( TransferDataContainer& /*rData*/, SvTreeListEntry* pEntry )
{
    // Restore selection for multiple selected effects.
    // Do it here to remove a flicker on the UI with effects being unselected and reselected.
    for( auto &pEffect : mDndEffectsSelected )
        SelectListEntry( pEffect, true);
    mxTreeView->selected_foreach([this](weld::TreeIter& rEntry){
        mDndEffectsSelected.emplace_back(mxTreeView->make_iterator(&rEntry));
        return false;
    });

    // Note: pEntry is the effect with focus (if multiple effects are selected)
    mpDndEffectDragging = pEntry;
    mpDndEffectInsertBefore = pEntry;
    mxDndEffectDragging = mxTreeView->make_iterator();
    mxTreeView->get_cursor(mxDndEffectDragging.get());
    mxDndEffectInsertBefore = mxTreeView->make_iterator(mxDndEffectDragging.get());

    return DragDropMode::CTRL_MOVE;
    // Allow normal processing.
    return false;
}

// D'n'D #3: Called each time mouse moves during drag
sal_Int8 CustomAnimationList::AcceptDrop( const AcceptDropEvent& rEvt )
{
    /*
        Don't call SvTreeListBox::AcceptDrop because it puts an unnecessary
        highlight via ImplShowTargetEmphasis()
    */

    sal_Int8 ret = DND_ACTION_NONE;

    const bool bIsMove = ( DND_ACTION_MOVE == rEvt.mnAction );
    if( mpDndEffectDragging && !rEvt.mbLeaving && bIsMove )
    {
        SvTreeListEntry* pEntry = GetDropTarget( rEvt.maPosPixel );

        const bool bOverASelectedEffect =
            std::find( mDndEffectsSelected.begin(), mDndEffectsSelected.end(), pEntry ) != mDndEffectsSelected.end();
        if( pEntry && !bOverASelectedEffect )
        {
            ReparentChildrenDuringDrag();

            ReorderEffectsInUiDuringDragOver( pEntry );
        }

        // Return DND_ACTION_MOVE on internal drag'n'drops so that ExecuteDrop() is called.
        // Return MOVE even if we are over other dragged effect because dragged effect moves.
    const bool bIsMove = DND_ACTION_MOVE == rEvt.mnAction;
    if (mxDndEffectDragging && !rEvt.mbLeaving && bIsMove)
        ret = DND_ACTION_MOVE;
    }

    return ret;
}

// D'n'D: For each dragged effect, re-parent (only in the UI) non-selected
//        visible children so they are not dragged with the parent.
void CustomAnimationList::ReparentChildrenDuringDrag()
{
    /*
        Re-parent (only in the UI!):
          a) the dragged effect's first non-selected child to the root, and
          b) the remaining non-selected children to that re-parented 1st child.
    */
    for( auto &pEffect : mDndEffectsSelected )
    {
        const bool bExpandedWithChildren = GetVisibleChildCount( pEffect ) > 0;
        if( bExpandedWithChildren )
        {
            SvTreeListEntry* pEntryParent = GetParent( pEffect );

            SvTreeListEntry* pFirstNonSelectedChild = nullptr;
            sal_uLong nInsertNextChildPos = 0;

            // Process all children of this effect
            SvTreeListEntry* pChild = FirstChild( pEffect );
            while( pChild && ( GetParent( pChild ) == pEffect ) )
            {
                // Start by finding next child because if pChild moves, we cannot then
                // ask it what the next child is because it's no longer with its siblings.
                SvTreeListEntry* pNextChild = Next( pChild );

                // Skip selected effects: they stay with their previous parent to be moved.
                // During drag, the IsSelected() set changes, so use mDndEffectsSelected instead
                const bool bIsSelected = std::find( mDndEffectsSelected.begin(), mDndEffectsSelected.end(), pChild ) != mDndEffectsSelected.end();
                if( !bIsSelected )
                {
                    // Re-parent 1st non-selected child to root, below all the other children.
                    if( !pFirstNonSelectedChild )
                    {
                        pFirstNonSelectedChild = pChild;
                        sal_uLong nInsertAfterPos = SvTreeList::GetRelPos( pEffect ) + 1;
                        pModel->Move( pFirstNonSelectedChild, pEntryParent, nInsertAfterPos );
                    }
                    else
                    {
                        // Re-parent remaining non-selected children to 1st child
                        ++nInsertNextChildPos;
                        pModel->Move( pChild, pFirstNonSelectedChild, nInsertNextChildPos );
                    }
                }

                pChild = pNextChild;
            }

            // Expand all children (they were previously visible)
            if( pFirstNonSelectedChild )
                Expand( pFirstNonSelectedChild );

        }
    }
}

// D'n'D: Update UI to show where dragged event will appear if dropped now.
void CustomAnimationList::ReorderEffectsInUiDuringDragOver( SvTreeListEntry* pOverEntry )
{
    /*
        Update the order of effects in *just the UI* while the user is dragging.
        The model (MainSequence) will only be changed after the user drops
        the effect so that there is minimal work to do if the drag is canceled.
        Plus only one undo record should be created per drag, and changing
        the model recreates all effects (on a background timer) which invalidates
        all effect pointers.
    */

    // Compute new location in *UI*
    SvTreeListEntry* pNewParent = nullptr;
    sal_uLong nInsertAfterPos = 0;

    Point aPosOverEffect( GetEntryPosition(pOverEntry) );
    Point aPosDraggedEffect( GetEntryPosition(mpDndEffectDragging) );
    const bool bDraggingUp = (aPosDraggedEffect.Y() - aPosOverEffect.Y()) > 0;

    if( bDraggingUp )
    {
        // Drag up   --> place above the element we are over
        pNewParent = GetParent( pOverEntry );
        nInsertAfterPos = SvTreeList::GetRelPos( pOverEntry );
        mpDndEffectInsertBefore = pOverEntry;
    }
    else
    {
        // Drag down -->  place below the element we are over
        SvTreeListEntry* pNextVisBelowTarget = NextVisible( pOverEntry );
        if( pNextVisBelowTarget )
        {
            // Match parent of NEXT visible effect (works for sub-items too)
            pNewParent = GetParent( pNextVisBelowTarget );
            nInsertAfterPos = SvTreeList::GetRelPos( pNextVisBelowTarget );
            mpDndEffectInsertBefore = pNextVisBelowTarget;
        }
        else
        {
            // Over the last element: no next to work with
            pNewParent = GetParent( pOverEntry );
            nInsertAfterPos = SvTreeList::GetRelPos( pOverEntry ) + 1;
            mpDndEffectInsertBefore = nullptr;
        }
    }

    // Move each selected effect in *just* the UI to show where it would be if dropped.
    // This leaves the exist parent relationships in the non-dragged elements so that
    // the list does not seem to change structure during drag. Parent relationships will
    // be correctly recreated on drop.
    for( auto aItr = mDndEffectsSelected.rbegin();
         aItr != mDndEffectsSelected.rend();
         ++aItr)
    {
        SvTreeListEntry* pEffect = *aItr;

        // Move only effects whose parents is not selected because
        // they will automatically move when their parent is moved.
        const bool bParentIsSelected =
            std::find(mDndEffectsSelected.begin(), mDndEffectsSelected.end(), GetParent(pEffect)) != mDndEffectsSelected.end();

        if( !bParentIsSelected )
        {
            // If the current effect is being moved down, the insert position must be decremented
            // after move if it will have the same parent as it currently does because it moves
            // from above the insertion point to below it, hence changing its index.
            // Must decide move-up vs move-down for each effect being dragged because we may be
            // processing a discontinuous set of selected effects (some below, some above insertion point)
            Point aCurPosOverEffect( GetEntryPosition( pOverEntry ) );
            Point aCurPosMovedEffect( GetEntryPosition( pEffect ) );
            const bool bCurDraggingDown = ( aCurPosMovedEffect.Y() - aCurPosOverEffect.Y() ) < 0;
            const bool bWillHaveSameParent = ( pNewParent == GetParent(pEffect) );

            pModel->Move( pEffect, pNewParent, nInsertAfterPos );

            if( bCurDraggingDown && bWillHaveSameParent )
                --nInsertAfterPos;
        }
    }

    // Restore selection (calling Select() is slow; SelectListEntry() is faster)
    for( auto &pEffect : mDndEffectsSelected )
        SelectListEntry( pEffect, true);
}

// D'n'D #5: Tell model to update effect order.
sal_Int8 CustomAnimationList::ExecuteDrop( const ExecuteDropEvent& /*rEvt*/ )
sal_Int8 CustomAnimationList::ExecuteDrop(const ExecuteDropEvent& rEvt)
{
    // NOTE: We cannot just override NotifyMoving() because it's not called
    //       since we dynamically reorder effects during drag.
    if (!mxTreeView->get_dest_row_at_pos(rEvt.maPosPixel, mxDndEffectInsertBefore.get()))
        mxDndEffectInsertBefore.reset();

    sal_Int8 ret = DND_ACTION_NONE;

    const bool bMovingEffect = ( mpDndEffectDragging != nullptr );
    const bool bMoveNotSelf  = ( mpDndEffectInsertBefore != mpDndEffectDragging );
    const bool bMovingEffect = ( mxDndEffectDragging != nullptr );
    const bool bMoveNotSelf  = !mxDndEffectInsertBefore || (mxDndEffectDragging && mxTreeView->iter_compare(*mxDndEffectInsertBefore, *mxDndEffectDragging) != 0);
    const bool bHaveSequence = ( mpMainSequence.get() != nullptr );

    if( bMovingEffect && bMoveNotSelf && bHaveSequence )
    {
        CustomAnimationListEntry*  pTarget = static_cast< CustomAnimationListEntry* >( mpDndEffectInsertBefore );
        CustomAnimationListEntryItem* pTarget = mxDndEffectInsertBefore ?
                                                    reinterpret_cast<CustomAnimationListEntryItem*>(mxTreeView->get_id(*mxDndEffectInsertBefore).toInt64()) :
                                                    nullptr;

        // Build list of effects
        std::vector< CustomAnimationEffectPtr > aEffects;
        for( const auto &pEntry : mDndEffectsSelected )
        {
            CustomAnimationListEntry* pCustomAnimationEffect = static_cast< CustomAnimationListEntry* >( pEntry );
            aEffects.push_back( pCustomAnimationEffect->getEffect() );
            CustomAnimationListEntryItem* pCustomAnimationEffect = reinterpret_cast<CustomAnimationListEntryItem*>(mxTreeView->get_id(*pEntry).toInt64());
            aEffects.push_back(pCustomAnimationEffect->getEffect());
        }

        // Callback to observer to have it update the model.
@@ -713,90 +528,82 @@ sal_Int8 CustomAnimationList::ExecuteDrop( const ExecuteDropEvent& /*rEvt*/ )
            pTarget ? pTarget->getEffect() : nullptr );

        // Reset selection
        Select( mpDndEffectDragging );

        ret = DND_ACTION_MOVE;
        mxTreeView->select(*mxDndEffectDragging);
        Select();
    }

    // NOTE: Don't call SvTreeListBox::ExecuteDrop(...) because all required
    // NOTE: Don't call default handler because all required
    //       move operations have been completed here to update the model.
    return ret;
    return DND_ACTION_NONE;
}

// D'n'D #6: Cleanup (regardless of if we were target of drop or not)
void CustomAnimationList::DragFinished( sal_Int8 /*nDropAction*/ )
{
    mpDndEffectDragging = nullptr;
    mpDndEffectInsertBefore = nullptr;
    mDndEffectsSelected.clear();

    // Rebuild because we may have re-parented the dragged effect's first child.
    // Can hit this without running ExecuteDrop(...) when drag canceled.
    mpMainSequence->rebuild();

    // Note: Don't call SvTreeListBox::DragFinished(...) because we don't call
    //       SvTreeListBox::ExecuteDrop(...) which sets variables that are
    //       needed in its DragFinished(...) method.
}

VCL_BUILDER_FACTORY(CustomAnimationList)

CustomAnimationList::~CustomAnimationList()
{
    disposeOnce();
}
    if (mnPostExpandEvent)
    {
        Application::RemoveUserEvent(mnPostExpandEvent);
        mnPostExpandEvent = nullptr;
    }

void CustomAnimationList::dispose()
{
    if (mnPostCollapseEvent)
    {
        Application::RemoveUserEvent(mnPostCollapseEvent);
        mnPostCollapseEvent = nullptr;
    }

    if( mpMainSequence.get() )
        mpMainSequence->removeListener( this );

    clear();

    mxMenu.disposeAndClear();
    mxBuilder.reset();

    SvTreeListBox::dispose();
}

void CustomAnimationList::KeyInput( const KeyEvent& rKEvt )
IMPL_LINK(CustomAnimationList, KeyInputHdl, const KeyEvent&, rKEvt, bool)
{
    const int nKeyCode = rKEvt.GetKeyCode().GetCode();
    switch( nKeyCode )
    switch (nKeyCode)
    {
        case KEY_DELETE:
            mpController->onContextMenu("remove");
            return;
            return true;
        case KEY_INSERT:
            mpController->onContextMenu("create");
            return;
            return true;
        case KEY_SPACE:
        {
            std::unique_ptr<weld::TreeIter> xEntry = mxTreeView->make_iterator();
            if (mxTreeView->get_cursor(xEntry.get()))
            {
                const Point aPos;
                const CommandEvent aCEvt( aPos, CommandEventId::ContextMenu );
                Command( aCEvt );
                return;
                auto aRect = mxTreeView->get_row_area(*xEntry);
                const Point aPos(aRect.getWidth() / 2, aRect.getHeight() / 2);
                const CommandEvent aCEvt(aPos, CommandEventId::ContextMenu);
                CommandHdl(aCEvt);
                return true;
            }

        }
    }

    ::SvTreeListBox::KeyInput( rKEvt );
    return false;
}

/** selects or deselects the given effect.
    Selections of other effects are not changed */
void CustomAnimationList::select( const CustomAnimationEffectPtr& pEffect )
{
    CustomAnimationListEntry* pEntry = static_cast< CustomAnimationListEntry* >(First());
    while( pEntry )
    CustomAnimationListEntryItem* pEntry = nullptr;

    std::unique_ptr<weld::TreeIter> xEntry = mxTreeView->make_iterator();
    if (mxTreeView->get_iter_first(*xEntry))
    {
        if( pEntry->getEffect() == pEffect )
        do
        {
            Select( pEntry );
            MakeVisible( pEntry );
            break;
        }
        pEntry = static_cast< CustomAnimationListEntry* >(Next( pEntry ));
            CustomAnimationListEntryItem* pTestEntry = reinterpret_cast<CustomAnimationListEntryItem*>(mxTreeView->get_id(*xEntry).toInt64());
            if (pTestEntry->getEffect() == pEffect)
            {
                mxTreeView->select(*xEntry);
                mxTreeView->scroll_to_row(*xEntry);
                pEntry = pTestEntry;
                break;
            }
        } while (mxTreeView->iter_next(*xEntry));
    }

    if( !pEntry )
@@ -808,9 +615,13 @@ void CustomAnimationList::select( const CustomAnimationEffectPtr& pEffect )

void CustomAnimationList::clear()
{
    Clear();
    mxEntries.clear();
    mxTreeView->clear();

    mpLastParentEntry = nullptr;
    mxEmptyLabelParent->show();
    mxTreeView->hide();

    mxLastParentEntry.reset();
    mxLastTargetShape = nullptr;
}

@@ -841,9 +652,6 @@ void stl_append_effect_func::operator()(const CustomAnimationEffectPtr& pEffect)
void CustomAnimationList::update()
{
    mbIgnorePaint = true;
    SetUpdateMode( false );

    CustomAnimationListEntry* pEntry = nullptr;

    std::vector< CustomAnimationEffectPtr > aVisible;
    std::vector< CustomAnimationEffectPtr > aSelected;
@@ -856,62 +664,77 @@ void CustomAnimationList::update()
    long nFirstSelOld = -1;
    long nLastSelOld = -1;

    std::unique_ptr<weld::TreeIter> xEntry = mxTreeView->make_iterator();

    if( mpMainSequence.get() )
    {
        // save scroll position
        pEntry = static_cast<CustomAnimationListEntry*>(GetFirstEntryInView());
        if( pEntry )
            nFirstVis = GetAbsPos( pEntry );

        pEntry = static_cast<CustomAnimationListEntry*>(GetLastEntryInView());
        if( pEntry )
            nLastVis = GetAbsPos( pEntry );

        pEntry = static_cast<CustomAnimationListEntry*>(FirstSelected());
        if( pEntry )
        {
            pFirstSelEffect = pEntry->getEffect();
            nFirstSelOld = GetAbsPos( pEntry );
        }

        pEntry = static_cast<CustomAnimationListEntry*>(LastSelected());
        if( pEntry )
        {
            pLastSelEffect = pEntry->getEffect();
            nLastSelOld = GetAbsPos( pEntry );
        }
        std::unique_ptr<weld::TreeIter> xLastSelectedEntry;
        std::unique_ptr<weld::TreeIter> xLastVisibleEntry;

        // save selection, current, and expand (visible) states
        pEntry = static_cast<CustomAnimationListEntry*>(First());

        while( pEntry )
        {
            CustomAnimationEffectPtr pEffect( pEntry->getEffect() );
            if( pEffect.get() )
        mxTreeView->all_foreach([this, &aVisible, &nFirstVis, &xLastVisibleEntry,
                                 &aSelected, &nFirstSelOld, &pFirstSelEffect, &xLastSelectedEntry](weld::TreeIter& rEntry){
            CustomAnimationListEntryItem* pEntry = reinterpret_cast<CustomAnimationListEntryItem*>(mxTreeView->get_id(rEntry).toInt64());
            CustomAnimationEffectPtr pEffect(pEntry->getEffect());
            if (pEffect.get())
            {
                if( IsEntryVisible( pEntry ) )
                    aVisible.push_back( pEffect );
                if (weld::IsEntryVisible(*mxTreeView, rEntry))
                {
                    aVisible.push_back(pEffect);
                    // save scroll position
                    if (nFirstVis == -1)
                        nFirstVis = weld::GetAbsPos(*mxTreeView, rEntry);
                    if (!xLastVisibleEntry)
                        xLastVisibleEntry = mxTreeView->make_iterator(&rEntry);
                    else
                        mxTreeView->copy_iterator(rEntry, *xLastVisibleEntry);
                }

                if( IsSelected( pEntry ) )
                    aSelected.push_back( pEffect );
                if (mxTreeView->is_selected(rEntry))
                {
                    aSelected.push_back(pEffect);
                    if (nFirstSelOld == -1)
                    {
                        pFirstSelEffect = pEffect;
                        nFirstSelOld = weld::GetAbsPos(*mxTreeView, rEntry);
                    }
                    if (!xLastSelectedEntry)
                        xLastSelectedEntry = mxTreeView->make_iterator(&rEntry);
                    else
                        mxTreeView->copy_iterator(rEntry, *xLastSelectedEntry);
                }
            }

            pEntry = static_cast<CustomAnimationListEntry*>(Next( pEntry ));
            return false;
        });

        if (xLastSelectedEntry)
        {
            CustomAnimationListEntryItem* pEntry = reinterpret_cast<CustomAnimationListEntryItem*>(mxTreeView->get_id(*xLastSelectedEntry).toInt64());
            pLastSelEffect = pEntry->getEffect();
            nLastSelOld = weld::GetAbsPos(*mxTreeView, *xLastSelectedEntry);
        }

        pEntry = static_cast<CustomAnimationListEntry*>(GetCurEntry());
        if( pEntry )
        if (xLastVisibleEntry)
            nLastVis = weld::GetAbsPos(*mxTreeView, *xLastVisibleEntry);

        if (mxTreeView->get_cursor(xEntry.get()))
        {
            CustomAnimationListEntryItem* pEntry = reinterpret_cast<CustomAnimationListEntryItem*>(mxTreeView->get_id(*xEntry).toInt64());
            aCurrent = pEntry->getEffect();
        }
    }

    // rebuild list

    mxTreeView->freeze();

    clear();
    if( mpMainSequence.get() )

    if (mpMainSequence.get())
    {
        long nFirstSelNew = -1;
        long nLastSelNew = -1;
        std::for_each( mpMainSequence->getBegin(), mpMainSequence->getEnd(), stl_append_effect_func( *this ) );
        mpLastParentEntry = nullptr;
        mxLastParentEntry.reset();

        auto rInteractiveSequenceVector = mpMainSequence->getInteractiveSequenceVector();

@@ -920,54 +743,76 @@ void CustomAnimationList::update()
            Reference< XShape > xShape( pIS->getTriggerShape() );
            if( xShape.is() )
            {
                SvTreeListEntry* pLBoxEntry = new CustomAnimationListEntry;
                pLBoxEntry->AddItem(std::make_unique<SvLBoxContextBmp>(Image(), Image(), false));
                OUString aDescription = SdResId(STR_CUSTOMANIMATION_TRIGGER) + ": " +
                    getShapeDescription( xShape, false );
                pLBoxEntry->AddItem(std::make_unique<CustomAnimationTriggerEntryItem>(aDescription));
                Insert( pLBoxEntry );
                SvViewDataEntry* pViewData = GetViewData( pLBoxEntry );
                if( pViewData )
                    pViewData->SetSelectable(false);

                mxEntries.emplace_back(std::make_unique<CustomAnimationListEntryItem>(aDescription, nullptr));

                OUString sId(OUString::number(reinterpret_cast<sal_Int64>(mxEntries.back().get())));
                mxTreeView->insert(nullptr, -1, &aDescription, &sId, nullptr, nullptr, nullptr, false, nullptr);
                std::for_each( pIS->getBegin(), pIS->getEnd(), stl_append_effect_func( *this ) );
                mpLastParentEntry = nullptr;
                mxLastParentEntry.reset();
            }
        }
    }

    mxTreeView->thaw();

    if (mxTreeView->n_children())
    {
        mxEmptyLabelParent->hide();
        mxTreeView->show();
    }

    if (mpMainSequence.get())
    {
        long nFirstSelNew = -1;
        long nLastSelNew = -1;

        std::vector<std::unique_ptr<weld::TreeIter>> aNewSelection;

        // restore selection state, expand state, and current-entry (under cursor)
        pEntry = static_cast<CustomAnimationListEntry*>(First());

        while( pEntry )
        if (mxTreeView->get_iter_first(*xEntry))
        {
            CustomAnimationEffectPtr pEffect( pEntry->getEffect() );
            if( pEffect.get() )
            do
            {
                // Any effects that were visible should still be visible, so expand their parents.
                // (a previously expanded parent may have moved leaving a child to now be the new parent to expand)
                if( std::find( aVisible.begin(), aVisible.end(), pEffect ) != aVisible.end() )
                CustomAnimationListEntryItem* pEntry = reinterpret_cast<CustomAnimationListEntryItem*>(mxTreeView->get_id(*xEntry).toInt64());

                CustomAnimationEffectPtr pEffect( pEntry->getEffect() );
                if (pEffect.get())
                {
                    if( GetParent(pEntry) )
                        Expand( GetParent(pEntry) );
                    // Any effects that were visible should still be visible, so expand their parents.
                    // (a previously expanded parent may have moved leaving a child to now be the new parent to expand)
                    if( std::find( aVisible.begin(), aVisible.end(), pEffect ) != aVisible.end() )
                    {
                        if (mxTreeView->get_iter_depth(*xEntry))
                        {
                            std::unique_ptr<weld::TreeIter> xParentEntry = mxTreeView->make_iterator(xEntry.get());
                            mxTreeView->iter_parent(*xParentEntry);
                            mxTreeView->expand_row(*xParentEntry);
                        }
                    }

                    if( std::find( aSelected.begin(), aSelected.end(), pEffect ) != aSelected.end() )
                        aNewSelection.emplace_back(mxTreeView->make_iterator(xEntry.get()));

                    // Restore the cursor, as it may deselect other effects wait until
                    // after the loop to reset the selection
                    if( pEffect == aCurrent )
                        mxTreeView->set_cursor(*xEntry);

                    if (pEffect == pFirstSelEffect)
                        nFirstSelNew = weld::GetAbsPos(*mxTreeView, *xEntry);

                    if (pEffect == pLastSelEffect)
                        nLastSelNew = weld::GetAbsPos(*mxTreeView, *xEntry);
                }

                if( std::find( aSelected.begin(), aSelected.end(), pEffect ) != aSelected.end() )
                    Select( pEntry );

                // Restore the cursor; don't use SetCurEntry() as it may deselect other effects
                if( pEffect == aCurrent )
                    SetCursor( pEntry );

                if( pEffect == pFirstSelEffect )
                    nFirstSelNew = GetAbsPos( pEntry );

                if( pEffect == pLastSelEffect )
                    nLastSelNew = GetAbsPos( pEntry );
            }

            pEntry = static_cast<CustomAnimationListEntry*>(Next( pEntry ));
            } while (mxTreeView->iter_next(*xEntry));
        }

        for (const auto& rEntry : aNewSelection)
            mxTreeView->select(*rEntry);

        // Scroll to a selected entry, depending on where the selection moved.
        const bool bMoved = nFirstSelNew != nFirstSelOld;
        const bool bMovedUp = nFirstSelNew < nFirstSelOld;
@@ -981,103 +826,104 @@ void CustomAnimationList::update()
            {
                // The entries in the selection range can't fit in view.
                // Scroll so the last selected entry is last in view.
                ScrollToAbsPos( nLastSelNew - (nLastVis - nFirstVis) );
                mxTreeView->vadjustment_set_value(nLastSelNew - (nLastVis - nFirstVis));
            }
            else
                ScrollToAbsPos( nFirstSelNew );
                mxTreeView->vadjustment_set_value(nFirstSelNew);
        }
        else if( bMoved && nFirstSelOld > nLastVis && nFirstSelNew > nLastVis )
        {
            // The selection is below the visible area.
            // Scroll down to the first few selected entries.
            ScrollToAbsPos( nFirstSelNew );
            mxTreeView->vadjustment_set_value(nFirstSelNew);
        }
        else if( bMovedUp && nFirstSelOld <= nFirstVis )
        {
            // A visible entry has moved up out of view; scroll up one.
            ScrollToAbsPos( nFirstVis - 1 );
            mxTreeView->vadjustment_set_value(nFirstVis - 1);
        }
        else if( bMovedDown && nLastSelOld >= nLastVis )
        {
            // An entry has moved down out of view; scroll down one.
            ScrollToAbsPos( nFirstVis + 1 );
            mxTreeView->vadjustment_set_value(nFirstVis + 1);
        }
        else if ( nFirstVis != -1 )
        {
            // The selection is still in view, or it hasn't moved.
            ScrollToAbsPos( nFirstVis );
            mxTreeView->vadjustment_set_value(nFirstVis);
        }
    }

    mbIgnorePaint = false;
    SetUpdateMode( true );
    Invalidate();

    Select();
}

void CustomAnimationList::append( CustomAnimationEffectPtr pEffect )
{
    // create a ui description
    OUString aDescription;

    Any aTarget( pEffect->getTarget() );
    if( !aTarget.hasValue() )
        return;

    try
    {
        aDescription = getDescription( aTarget, pEffect->getTargetSubItem() != ShapeAnimationSubType::ONLY_BACKGROUND );
        // create a ui description
        OUString aDescription = getDescription(aTarget, pEffect->getTargetSubItem() != ShapeAnimationSubType::ONLY_BACKGROUND);

        SvTreeListEntry* pParentEntry = nullptr;
        std::unique_ptr<weld::TreeIter> xParentEntry;

        Reference< XShape > xTargetShape( pEffect->getTargetShape() );
        sal_Int32 nGroupId = pEffect->getGroupId();

        // if this effect has the same target and group-id as the last root effect,
        // the last root effect is also this effects parent
        if( mpLastParentEntry && (nGroupId != -1) && (mxLastTargetShape == xTargetShape) && (mnLastGroupId == nGroupId) )
            pParentEntry = mpLastParentEntry;
        if (mxLastParentEntry && nGroupId != -1 && mxLastTargetShape == xTargetShape && mnLastGroupId == nGroupId)
            xParentEntry = mxTreeView->make_iterator(mxLastParentEntry.get());

        // create an entry for the effect
        SvTreeListEntry* pEntry = new CustomAnimationListEntry( pEffect );
        std::unique_ptr<weld::TreeIter> xEntry = mxTreeView->make_iterator();

        pEntry->AddItem(std::make_unique<SvLBoxContextBmp>(Image(), Image(), false));
        pEntry->AddItem(std::make_unique<CustomAnimationListEntryItem>(aDescription, pEffect, this));
        mxEntries.emplace_back(std::make_unique<CustomAnimationListEntryItem>(aDescription, pEffect));

        if( pParentEntry )
        OUString sId(OUString::number(reinterpret_cast<sal_Int64>(mxEntries.back().get())));

        if (xParentEntry)
        {
            // add a subentry
            Insert( pEntry, pParentEntry );
            mxTreeView->insert(xParentEntry.get(), -1, &aDescription, &sId, nullptr, nullptr, nullptr, false, xEntry.get());
        }
        else
        {
            // add a root entry
            Insert( pEntry );
            mxTreeView->insert(nullptr, -1, &aDescription, &sId, nullptr, nullptr, nullptr, false, xEntry.get());

            // and the new root entry becomes the possible next group header
            mxLastTargetShape = xTargetShape;
            mnLastGroupId = nGroupId;
            mpLastParentEntry = pEntry;
            mxLastParentEntry = std::move(xEntry);
        }
    }
    catch( Exception& )
    catch (const Exception&)
    {
        OSL_FAIL("sd::CustomAnimationList::append(), exception caught!" );
    }
}

static void selectShape( SvTreeListBox* pTreeList, const Reference< XShape >& xShape )
static void selectShape(weld::TreeView* pTreeList, const Reference< XShape >& xShape )
{
    CustomAnimationListEntry* pEntry = static_cast< CustomAnimationListEntry* >(pTreeList->First());
    while( pEntry )
    std::unique_ptr<weld::TreeIter> xEntry = pTreeList->make_iterator();
    if (pTreeList->get_iter_first(*xEntry))
    {
        CustomAnimationEffectPtr pEffect( pEntry->getEffect() );
        if( pEffect.get() )
        do
        {
            if( pEffect->getTarget() == xShape )
                pTreeList->Select( pEntry );
        }

        pEntry = static_cast< CustomAnimationListEntry* >(pTreeList->Next( pEntry ));
            CustomAnimationListEntryItem* pEntry = reinterpret_cast<CustomAnimationListEntryItem*>(pTreeList->get_id(*xEntry).toInt64());
            CustomAnimationEffectPtr pEffect(pEntry->getEffect());
            if (pEffect)
            {
                if (pEffect->getTarget() == xShape)
                    pTreeList->select(*xEntry);
            }
        } while (pTreeList->iter_next(*xEntry));
    }
}

@@ -1085,7 +931,7 @@ void CustomAnimationList::onSelectionChanged(const Any& rSelection)
{
    try
    {
        SelectAll(false);
        mxTreeView->unselect_all();

        if (rSelection.hasValue())
        {
@@ -1098,18 +944,18 @@ void CustomAnimationList::onSelectionChanged(const Any& rSelection)
                {
                    Reference< XShape > xShape( xShapes->getByIndex( nIndex ), UNO_QUERY );
                    if( xShape.is() )
                        selectShape( this, xShape );
                        selectShape(mxTreeView.get(), xShape);
                }
            }
            else
            {
                Reference< XShape > xShape(rSelection, UNO_QUERY);
                if( xShape.is() )
                    selectShape( this, xShape );
                    selectShape(mxTreeView.get(), xShape);
            }
        }

        SelectHdl();
        Select();
    }
    catch( Exception& )
    {
@@ -1117,170 +963,186 @@ void CustomAnimationList::onSelectionChanged(const Any& rSelection)
    }
}

// Notify controller to refresh UI when we are notified of selection change from base class
void CustomAnimationList::SelectHdl()
IMPL_LINK_NOARG(CustomAnimationList, SelectHdl, weld::TreeView&, void)
{
    if( mbIgnorePaint )
        return;
    SvTreeListBox::SelectHdl();
    mpController->onSelect();
    Select();
}

// Notify controller to refresh UI when we are notified of selection change from base class
void CustomAnimationList::DeselectHdl()
void CustomAnimationList::Select()
{
    if( mbIgnorePaint )
        return;
    SvTreeListBox::DeselectHdl();
    mpController->onSelect();
}


bool CustomAnimationList::Expand( SvTreeListEntry* pParent )
IMPL_LINK_NOARG(CustomAnimationList, PostExpandHdl, void*, void)
{
    bool result = SvTreeListBox::Expand( pParent );

    // If expanded entry is selected, then select its children too.
    if( IsSelected( pParent )) {
        for( auto pChild = FirstChild( pParent ); pChild; pChild = pChild->NextSibling() )
    std::unique_ptr<weld::TreeIter> xEntry = mxTreeView->make_iterator();
    if (mxTreeView->get_selected(xEntry.get()))
    {
        for (bool bChild = mxTreeView->iter_children(*xEntry); bChild; bChild = mxTreeView->iter_next_sibling(*xEntry))
        {
            if( !IsSelected( pChild ) )
            {
                SelectListEntry( pChild, true );
            }
            if (!mxTreeView->is_selected(*xEntry))
                mxTreeView->select(*xEntry);
        }
    }

    // Notify controller that selection has changed (it should update the UI)
    mpController->onSelect();

    return result;
    mnPostExpandEvent = nullptr;
}

bool CustomAnimationList::Collapse( SvTreeListEntry* pParent )
IMPL_LINK(CustomAnimationList, ExpandHdl, const weld::TreeIter&, rParent, bool)
{
    // SvTreeListBox::Collapse(..) discards multi-selection state
    // of list entries, so first save current selection state
    std::vector< SvTreeListEntry* > selectedEntries;
    for( auto pEntry = FirstSelected(); pEntry; pEntry = NextSelected( pEntry ))
    // If expanded entry is selected, then select its children too afterwards.
    if (mxTreeView->is_selected(rParent) && !mnPostExpandEvent) {
        mnPostExpandEvent = Application::PostUserEvent(LINK(this, CustomAnimationList, PostExpandHdl));
    }

    return true;
}

IMPL_LINK_NOARG(CustomAnimationList, PostCollapseHdl, void*, void)
{
    // Deselect all entries as SvTreeListBox::Collapse selects the last
    // entry to have focus (or its parent), which is not desired
    mxTreeView->unselect_all();

    // Restore selection state for entries which are still visible
    for (auto &pEntry : lastSelectedEntries)
    {
        selectedEntries.push_back( pEntry );
        if (weld::IsEntryVisible(*mxTreeView, *pEntry))
            mxTreeView->select(*pEntry);
    }

    lastSelectedEntries.clear();

    // Notify controller that selection has changed (it should update the UI)
    mpController->onSelect();

    mnPostCollapseEvent = nullptr;
}

IMPL_LINK_NOARG(CustomAnimationList, CollapseHdl, const weld::TreeIter&, bool)
{
    if (!mnPostCollapseEvent)
    {
        // weld::TreeView::collapse() discards multi-selection state
        // of list entries, so first save current selection state
        mxTreeView->selected_foreach([this](weld::TreeIter& rEntry){
            lastSelectedEntries.emplace_back(mxTreeView->make_iterator(&rEntry));
            return false;
        });

        mnPostCollapseEvent = Application::PostUserEvent(LINK(this, CustomAnimationList, PostCollapseHdl));
    }

    // Execute collapse on base class
    bool result = SvTreeListBox::Collapse( pParent );

    // Deselect all entries as SvTreeListBox::Collapse selects the last
    // entry to have focus (or its parent), which is not desired
    for( auto pEntry = FirstSelected(); pEntry; pEntry = NextSelected( pEntry ))
    {
        SelectListEntry( pEntry, false );
    }

    // Restore selection state for entries which are still visible
    for( auto &pEntry : selectedEntries )
    {
        if( IsEntryVisible( pEntry ))
        {
            SelectListEntry( pEntry, true );
        }
    }

    // Notify controller that selection has changed (it should update the UI)
    mpController->onSelect();

    return result;
    return true;
}

bool CustomAnimationList::isExpanded( const CustomAnimationEffectPtr& pEffect ) const
{
    CustomAnimationListEntry* pEntry = static_cast<CustomAnimationListEntry*>(First());
    bool bExpanded = true; // we assume expanded by default

    while( pEntry )
    std::unique_ptr<weld::TreeIter> xEntry = mxTreeView->make_iterator();
    if (mxTreeView->get_iter_first(*xEntry))
    {
        if( pEntry->getEffect() == pEffect )
            break;

        pEntry = static_cast<CustomAnimationListEntry*>(Next( pEntry ));
        do
        {
            CustomAnimationListEntryItem* pEntry =
                reinterpret_cast<CustomAnimationListEntryItem*>(mxTreeView->get_id(*xEntry).toInt64());
            if (pEntry->getEffect() == pEffect)
            {
                if (mxTreeView->get_iter_depth(*xEntry)) // no parent, keep expanded default of true
                {
                    std::unique_ptr<weld::TreeIter> xParentEntry = mxTreeView->make_iterator(xEntry.get());
                    if (mxTreeView->iter_parent(*xParentEntry))
                        bExpanded = mxTreeView->get_row_expanded(*xParentEntry);
                }
                break;
            }
        } while (mxTreeView->iter_next(*xEntry));
    }

    if( pEntry )
        pEntry = static_cast<CustomAnimationListEntry*>(GetParent( pEntry ));

    return (pEntry == nullptr) || IsExpanded( pEntry );
    return bExpanded;
}

bool CustomAnimationList::isVisible( const CustomAnimationEffectPtr& pEffect ) const
bool CustomAnimationList::isVisible(const CustomAnimationEffectPtr& pEffect) const
{
    CustomAnimationListEntry* pEntry = static_cast<CustomAnimationListEntry*>(First());

    while( pEntry )
    std::unique_ptr<weld::TreeIter> xEntry = mxTreeView->make_iterator();
    if (mxTreeView->get_iter_first(*xEntry))
    {
        if( pEntry->getEffect() == pEffect )
            break;

        pEntry = static_cast<CustomAnimationListEntry*>(Next( pEntry ));
        do
        {
            CustomAnimationListEntryItem* pTestEntry = reinterpret_cast<CustomAnimationListEntryItem*>(mxTreeView->get_id(*xEntry).toInt64());
            if (pTestEntry->getEffect() == pEffect)
                return weld::IsEntryVisible(*mxTreeView, *xEntry);
        } while (mxTreeView->iter_next(*xEntry));
    }

    return (pEntry == nullptr) || IsEntryVisible( pEntry );
    return true;
}

EffectSequence CustomAnimationList::getSelection() const
{
    EffectSequence aSelection;

    CustomAnimationListEntry* pEntry = dynamic_cast< CustomAnimationListEntry* >(FirstSelected());
    while( pEntry )
    {
        CustomAnimationEffectPtr pEffect( pEntry->getEffect() );
        if( pEffect.get() )
            aSelection.push_back( pEffect );
    mxTreeView->selected_foreach([this, &aSelection](weld::TreeIter& rEntry){
        CustomAnimationListEntryItem* pEntry = reinterpret_cast<CustomAnimationListEntryItem*>(mxTreeView->get_id(rEntry).toInt64());
        CustomAnimationEffectPtr pEffect(pEntry->getEffect());
        if (pEffect)
            aSelection.push_back(pEffect);

        // if the selected effect is not expanded and has children
        // we say that the children are automatically selected
        if( !IsExpanded( pEntry ) )
        if (!mxTreeView->get_row_expanded(rEntry) && mxTreeView->iter_has_child(rEntry))
        {
            CustomAnimationListEntry* pChild = dynamic_cast< CustomAnimationListEntry* >( FirstChild( pEntry ) );
            while( pChild )
            std::unique_ptr<weld::TreeIter> xChild = mxTreeView->make_iterator(&rEntry);
            mxTreeView->iter_children(*xChild);

            do
            {
                if( !IsSelected( pChild ) )
                if (!mxTreeView->is_selected(*xChild))
                {
                    CustomAnimationListEntryItem* pChild = reinterpret_cast<CustomAnimationListEntryItem*>(mxTreeView->get_id(*xChild).toInt64());
                    const CustomAnimationEffectPtr& pChildEffect( pChild->getEffect() );
                    if( pChildEffect.get() )
                        aSelection.push_back( pChildEffect );
                }

                pChild = dynamic_cast< CustomAnimationListEntry* >(  pChild->NextSibling() );
            }
            } while (mxTreeView->iter_next_sibling(*xChild));
        }

        pEntry = static_cast< CustomAnimationListEntry* >(NextSelected( pEntry ));
    }
        return false;
    });

    return aSelection;
}

bool CustomAnimationList::DoubleClickHdl()
IMPL_LINK_NOARG(CustomAnimationList, DoubleClickHdl, weld::TreeView&, bool)
{
    mpController->onDoubleClick();
    return false;
}

VclPtr<PopupMenu> CustomAnimationList::CreateContextMenu()
IMPL_LINK(CustomAnimationList, CommandHdl, const CommandEvent&, rCEvt, bool)
{
    mxMenu.disposeAndClear();
    mxBuilder.reset(new VclBuilder(nullptr, VclBuilderContainer::getUIRootDir(), "modules/simpress/ui/effectmenu.ui", ""));
    mxMenu.set(mxBuilder->get_menu("menu"));
    if (rCEvt.GetCommand() != CommandEventId::ContextMenu)
        return false;

    std::unique_ptr<weld::Builder> xBuilder(Application::CreateBuilder(mxTreeView.get(), "modules/simpress/ui/effectmenu.ui"));
    std::unique_ptr<weld::Menu> xMenu = xBuilder->weld_menu("menu");

    sal_Int16 nNodeType = -1;
    sal_Int16 nEntries = 0;

    CustomAnimationListEntry* pEntry = static_cast< CustomAnimationListEntry* >(FirstSelected());
    while( pEntry )
    {
    mxTreeView->selected_foreach([this, &nNodeType, &nEntries](weld::TreeIter& rEntry){
        CustomAnimationListEntryItem* pEntry = reinterpret_cast<CustomAnimationListEntryItem*>(mxTreeView->get_id(rEntry).toInt64());
        CustomAnimationEffectPtr pEffect(pEntry->getEffect());

        nEntries++;
        CustomAnimationEffectPtr pEffect( pEntry->getEffect() );
        if( pEffect.get() )
        if (pEffect.get())
        {
            if( nNodeType == -1 )
            {
@@ -1291,26 +1153,30 @@ VclPtr<PopupMenu> CustomAnimationList::CreateContextMenu()
                if( nNodeType != pEffect->getNodeType() )
                {
                    nNodeType = -1;
                    break;
                    return true;
                }
            }
        }

        pEntry = static_cast< CustomAnimationListEntry* >(NextSelected( pEntry ));
    }
        return false;
    });

    mxMenu->CheckItem("onclick", nNodeType == EffectNodeType::ON_CLICK);
    mxMenu->CheckItem("withprev", nNodeType == EffectNodeType::WITH_PREVIOUS);
    mxMenu->CheckItem("afterprev", nNodeType == EffectNodeType::AFTER_PREVIOUS);
    mxMenu->EnableItem(mxMenu->GetItemId("options"), nEntries == 1);
    mxMenu->EnableItem(mxMenu->GetItemId("timing"), nEntries == 1);
    xMenu->set_active("onclick", nNodeType == EffectNodeType::ON_CLICK);
    xMenu->set_active("withprev", nNodeType == EffectNodeType::WITH_PREVIOUS);
    xMenu->set_active("afterprev", nNodeType == EffectNodeType::AFTER_PREVIOUS);
    xMenu->set_sensitive("options", nEntries == 1);
    xMenu->set_sensitive("timing", nEntries == 1);

    return mxMenu;
    OString sCommand = xMenu->popup_at_rect(mxTreeView.get(), ::tools::Rectangle(rCEvt.GetMousePosPixel(), Size(1,1)));
    if (!sCommand.isEmpty())
        ExecuteContextMenuAction(sCommand);

    return true;
}

void CustomAnimationList::ExecuteContextMenuAction( sal_uInt16 nSelectedPopupEntry )
void CustomAnimationList::ExecuteContextMenuAction(const OString& rIdent)
{
    mpController->onContextMenu(mxMenu->GetItemIdent(nSelectedPopupEntry));
    mpController->onContextMenu(rIdent);
}

void CustomAnimationList::notify_change()
@@ -1319,34 +1185,6 @@ void CustomAnimationList::notify_change()
    mpController->onSelect();
}

void CustomAnimationList::Paint(vcl::RenderContext& rRenderContext, const ::tools::Rectangle& rRect)
{
    if( mbIgnorePaint )
        return;

    SvTreeListBox::Paint(rRenderContext, rRect);

    // draw help text if list box is still empty
    if( First() != nullptr )
        return;

    Color aOldColor(rRenderContext.GetTextColor());
    rRenderContext.SetTextColor(rRenderContext.GetSettings().GetStyleSettings().GetDisableColor());
    ::Point aOffset(rRenderContext.LogicToPixel(Point(6, 6), MapMode(MapUnit::MapAppFont)));

    ::tools::Rectangle aRect(Point(0,0), GetOutputSizePixel());

    aRect.AdjustLeft(aOffset.X() );
    aRect.AdjustTop(aOffset.Y() );
    aRect.AdjustRight( -(aOffset.X()) );
    aRect.AdjustBottom( -(aOffset.Y()) );

    rRenderContext.DrawText(aRect, SdResId(STR_CUSTOMANIMATION_LIST_HELPTEXT),
                            DrawTextFlags::MultiLine | DrawTextFlags::WordBreak | DrawTextFlags::Center | DrawTextFlags::VCenter );

    rRenderContext.SetTextColor(aOldColor);
}

}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/animations/CustomAnimationList.hxx b/sd/source/ui/animations/CustomAnimationList.hxx
index 9b73818..3987938 100644
--- a/sd/source/ui/animations/CustomAnimationList.hxx
+++ b/sd/source/ui/animations/CustomAnimationList.hxx
@@ -24,7 +24,8 @@

#include <memory>

#include <vcl/treelistbox.hxx>
#include <vcl/transfer.hxx>
#include <vcl/weld.hxx>
#include <CustomAnimationEffect.hxx>

namespace com { namespace sun { namespace star { namespace drawing { class XShape; } } } }
@@ -45,15 +46,31 @@ public:
    virtual ~ICustomAnimationListController() {}
};

class CustomAnimationList : public SvTreeListBox, public ISequenceListener
class CustomAnimationList;
class CustomAnimationListEntryItem;

class CustomAnimationListDropTarget : public DropTargetHelper
{
private:
    CustomAnimationList& m_rTreeView;

    virtual sal_Int8 AcceptDrop( const AcceptDropEvent& rEvt ) override;
    virtual sal_Int8 ExecuteDrop( const ExecuteDropEvent& rEvt ) override;

public:
    CustomAnimationListDropTarget(CustomAnimationList& rTreeView);
};

class CustomAnimationList : public ISequenceListener
{
    friend class CustomAnimationListEntryItem;
    friend struct stl_append_effect_func;

public:
    explicit CustomAnimationList( vcl::Window* pParent );
    virtual ~CustomAnimationList() override;
    virtual void dispose() override;
    explicit CustomAnimationList(std::unique_ptr<weld::TreeView> xTreeView,
                                 std::unique_ptr<weld::Label> xLabel,
                                 std::unique_ptr<weld::Widget> xScrolledWindow);
    virtual ~CustomAnimationList();

    // methods

@@ -71,22 +88,10 @@ public:
    // events
    void onSelectionChanged(const css::uno::Any& rSelection);

    // overrides
    virtual void    SelectHdl() override;
    virtual void    DeselectHdl() override;
    virtual bool    DoubleClickHdl() override;

    virtual void    Paint( vcl::RenderContext& rRenderContext, const ::tools::Rectangle& rRect ) override;

    virtual VclPtr<PopupMenu> CreateContextMenu() override;
    virtual void    ExecuteContextMenuAction( sal_uInt16 nSelectedPopupEntry ) override;

    virtual void KeyInput( const KeyEvent& rKEvt ) override;
    void Select();

    virtual void notify_change() override;

    virtual bool Expand( SvTreeListEntry* pParent ) override;
    virtual bool Collapse( SvTreeListEntry* pParent ) override;
    bool isExpanded( const CustomAnimationEffectPtr& pEffect ) const;
    bool isVisible( const CustomAnimationEffectPtr& pEffect ) const;

@@ -98,23 +103,41 @@ public:
        mpController = pController;
    };

    sal_Int8     AcceptDrop(const AcceptDropEvent& rEvt);
    sal_Int8     ExecuteDrop(const ExecuteDropEvent& rEvt);

protected:
    // drag & drop
    virtual void         StartDrag( sal_Int8 nAction, const Point& rPosPixel ) override;
    virtual DragDropMode NotifyStartDrag( TransferDataContainer& rData, SvTreeListEntry* pEntry ) override;
    virtual sal_Int8     AcceptDrop( const AcceptDropEvent& rEvt ) override;
            void         ReparentChildrenDuringDrag();
            void         ReorderEffectsInUiDuringDragOver( SvTreeListEntry* pOverEntry);
    virtual sal_Int8     ExecuteDrop( const ExecuteDropEvent& rEvt ) override;
    virtual void         DragFinished( sal_Int8 nDropAction ) override;
    void set_sensitive(bool bSensitive) { mxTreeView->set_sensitive(bSensitive); }
    int get_height_rows(int nRows) { return mxTreeView->get_height_rows(nRows); }
    int get_approximate_digit_width() const { return mxTreeView->get_approximate_digit_width(); }
    void set_size_request(int nWidth, int nHeight) { mxTreeView->set_size_request(nWidth, nHeight); }
    void unselect_all() { mxTreeView->unselect_all(); }
    weld::TreeView& get_widget() { return *mxTreeView; }

    DECL_LINK(KeyInputHdl, const KeyEvent&, bool);
    DECL_LINK(ExpandHdl, const weld::TreeIter&, bool);
    DECL_LINK(PostExpandHdl, void*, void);
    DECL_LINK(CollapseHdl, const weld::TreeIter&, bool);
    DECL_LINK(PostCollapseHdl, void*, void);

private:
    std::unique_ptr<VclBuilder> mxBuilder;
    VclPtr<PopupMenu> mxMenu;
    std::unique_ptr<weld::TreeView> mxTreeView;
    CustomAnimationListDropTarget maDropTargetHelper;
    std::unique_ptr<weld::Label> mxEmptyLabel;
    std::unique_ptr<weld::Widget> mxEmptyLabelParent;
    std::vector<std::unique_ptr<CustomAnimationListEntryItem>> mxEntries;
    std::vector<std::unique_ptr<weld::TreeIter>> lastSelectedEntries;

    bool    mbIgnorePaint;

    DECL_LINK(SelectHdl, weld::TreeView&, void);
    DECL_LINK(CommandHdl, const CommandEvent&, bool);
    DECL_LINK(DoubleClickHdl, weld::TreeView&, bool);
    DECL_LINK(DragBeginHdl, bool&, bool);
    DECL_STATIC_LINK(CustomAnimationList, CustomRenderHdl, weld::TreeView::render_args, void);
    DECL_STATIC_LINK(CustomAnimationList, CustomGetSizeHdl, weld::TreeView::get_size_args, Size);

    void ExecuteContextMenuAction(const OString& rSelectedPopupEntry);

    /** appends the given effect to the list*/
    void append( CustomAnimationEffectPtr pEffect );

@@ -124,12 +147,15 @@ private:

    css::uno::Reference< css::drawing::XShape > mxLastTargetShape;
    sal_Int32 mnLastGroupId;
    SvTreeListEntry* mpLastParentEntry;
    ImplSVEvent* mnPostExpandEvent;
    ImplSVEvent* mnPostCollapseEvent;

    std::unique_ptr<weld::TreeIter> mxLastParentEntry;

    // drag & drop
    SvTreeListEntry* mpDndEffectDragging;
    SvTreeListEntry* mpDndEffectInsertBefore;
    std::vector< SvTreeListEntry* > mDndEffectsSelected;
    std::unique_ptr<weld::TreeIter> mxDndEffectDragging;
    std::unique_ptr<weld::TreeIter> mxDndEffectInsertBefore;
    std::vector<std::unique_ptr<weld::TreeIter>> mDndEffectsSelected;
};

OUString getPropertyName( sal_Int32 nPropertyType );
diff --git a/sd/source/ui/animations/CustomAnimationPane.cxx b/sd/source/ui/animations/CustomAnimationPane.cxx
index 5b4ff94..15dde73 100644
--- a/sd/source/ui/animations/CustomAnimationPane.cxx
+++ b/sd/source/ui/animations/CustomAnimationPane.cxx
@@ -46,10 +46,6 @@
#include "motionpathtag.hxx"
#include <CustomAnimationPreset.hxx>
#include <createcustomanimationpanel.hxx>
#include <vcl/lstbox.hxx>
#include <vcl/fixed.hxx>

#include <vcl/button.hxx>

#include <comphelper/lok.hxx>
#include <comphelper/sequence.hxx>
@@ -123,81 +119,68 @@ void fillRepeatComboBox(weld::ComboBox& rBox)

CustomAnimationPane::CustomAnimationPane( Window* pParent, ViewShellBase& rBase,
                                          const css::uno::Reference<css::frame::XFrame>& rxFrame )
:   PanelLayout( pParent, "CustomAnimationsPanel", "modules/simpress/ui/customanimationspanel.ui", rxFrame ),
    mrBase( rBase ),
    mnPropertyType( nPropertyTypeNone ),
    mnCurvePathPos( LISTBOX_ENTRY_NOTFOUND ),
    mnPolygonPathPos( LISTBOX_ENTRY_NOTFOUND ),
    mnFreeformPathPos( LISTBOX_ENTRY_NOTFOUND ),
    maLateInitTimer()
    : PanelLayout(pParent, "CustomAnimationsPanel", "modules/simpress/ui/customanimationspanel.ui", rxFrame, true)
    , mrBase(rBase)
    // load resources
    , mxFTAnimation(m_xBuilder->weld_label("effectlabel"))
    , mxCustomAnimationList(new CustomAnimationList(m_xBuilder->weld_tree_view("custom_animation_list"),
                                                    m_xBuilder->weld_label("custom_animation_label"),
                                                    m_xBuilder->weld_widget("custom_animation_label_parent")))
    , mxPBAddEffect(m_xBuilder->weld_button("add_effect"))
    , mxPBRemoveEffect(m_xBuilder->weld_button("remove_effect"))
    , mxPBMoveUp(m_xBuilder->weld_button("move_up"))
    , mxPBMoveDown(m_xBuilder->weld_button("move_down"))
    , mxFTCategory(m_xBuilder->weld_label("categorylabel"))
    , mxLBCategory(m_xBuilder->weld_combo_box("categorylb"))
    , mxFTEffect(m_xBuilder->weld_label("effect_label"))
    , mxLBAnimation(m_xBuilder->weld_tree_view("effect_list"))
    , mxFTStart(m_xBuilder->weld_label("start_effect"))
    , mxLBStart(m_xBuilder->weld_combo_box("start_effect_list"))
    , mxFTProperty(m_xBuilder->weld_label("effect_property"))
    , mxPlaceholderBox(m_xBuilder->weld_container("placeholder"))
    , mxPBPropertyMore(m_xBuilder->weld_button("more_properties"))
    , mxFTDuration(m_xBuilder->weld_label("effect_duration"))
    , mxCBXDuration(m_xBuilder->weld_metric_spin_button("anim_duration", FieldUnit::SECOND))
    , mxFTStartDelay(m_xBuilder->weld_label("delay_label"))
    , mxMFStartDelay(m_xBuilder->weld_metric_spin_button("delay_value", FieldUnit::SECOND))
    , mxCBAutoPreview(m_xBuilder->weld_check_button("auto_preview"))
    , mxPBPlay(m_xBuilder->weld_button("play"))
    , mnLastSelectedAnimation(-1)
    , mnPropertyType(nPropertyTypeNone)
    , mnCurvePathPos(-1)
    , mnPolygonPathPos(-1)
    , mnFreeformPathPos(-1)
    , maLateInitTimer()
{
    initialize();
}

void CustomAnimationPane::initialize()
{
    // load resources
    get(mpPBAddEffect, "add_effect");
    get(mpPBRemoveEffect, "remove_effect");
    mxLBAnimation->connect_changed(LINK(this, CustomAnimationPane, AnimationSelectHdl));
    mxCustomAnimationList->setController( dynamic_cast<ICustomAnimationListController*> ( this ) );
    mxCustomAnimationList->set_size_request(mxCustomAnimationList->get_approximate_digit_width() * 15,
                                            mxCustomAnimationList->get_height_rows(8));

    get(mpFTEffect, "effect_label");
    mxLBAnimation->set_size_request(mxLBAnimation->get_approximate_digit_width() * 15,
                                    mxLBAnimation->get_height_rows(8));

    get(mpFTStart, "start_effect");
    get(mpLBStart, "start_effect_list");
    get(mpFTProperty, "effect_property");
    get(mpPlaceholderBox, "placeholder");
    get(mpLBProperty, "effect_property_list");
    get(mpPBPropertyMore, "more_properties");
    maStrProperty = mxFTProperty->get_label();

    get(mpFTDuration, "effect_duration");
    get(mpCBXDuration, "anim_duration");
    get(mpFTCategory, "categorylabel");
    get(mpLBCategory, "categorylb");
    get(mpFTAnimation, "effectlabel");
    get(mpLBAnimation, "effect_list");
    get(mpFTStartDelay, "delay_label");
    get(mpMFStartDelay, "delay_value");
    mxPBAddEffect->connect_clicked( LINK( this, CustomAnimationPane, implClickHdl ) );
    mxPBRemoveEffect->connect_clicked( LINK( this, CustomAnimationPane, implClickHdl ) );
    mxLBStart->connect_changed( LINK( this, CustomAnimationPane, implControlListBoxHdl ) );
    mxCBXDuration->connect_value_changed(LINK( this, CustomAnimationPane, DurationModifiedHdl));
    mxPBPropertyMore->connect_clicked( LINK( this, CustomAnimationPane, implClickHdl ) );
    mxPBMoveUp->connect_clicked( LINK( this, CustomAnimationPane, implClickHdl ) );
    mxPBMoveDown->connect_clicked( LINK( this, CustomAnimationPane, implClickHdl ) );
    mxPBPlay->connect_clicked( LINK( this, CustomAnimationPane, implClickHdl ) );
    mxCBAutoPreview->connect_clicked( LINK( this, CustomAnimationPane, implClickHdl ) );
    mxLBCategory->connect_changed( LINK(this, CustomAnimationPane, UpdateAnimationLB) );
    mxMFStartDelay->connect_value_changed( LINK(this, CustomAnimationPane, DelayModifiedHdl) );
    mxMFStartDelay->connect_focus_out(LINK( this, CustomAnimationPane, DelayLoseFocusHdl));

    mpLBAnimation->SetSelectHdl(LINK(this, CustomAnimationPane, AnimationSelectHdl));
    get(mpCustomAnimationList, "custom_animation_list");
    mpCustomAnimationList->setController( dynamic_cast<ICustomAnimationListController*> ( this ) );
    mpCustomAnimationList->set_width_request(mpCustomAnimationList->approximate_digit_width() * 15);
    mpCustomAnimationList->set_height_request(mpCustomAnimationList->GetTextHeight() * 8);

    mpLBAnimation->set_width_request(mpLBAnimation->approximate_digit_width() * 15);
    mpLBAnimation->set_height_request(mpLBAnimation->GetTextHeight() * 8);

    get(mpPBMoveUp, "move_up");
    get(mpPBMoveDown, "move_down");
    get(mpPBPlay, "play");
    get(mpCBAutoPreview,"auto_preview");

    maStrProperty = mpFTProperty->GetText();

    //fillDurationMetricComboBox
    mpCBXDuration->InsertValue(50, FieldUnit::CUSTOM);
    mpCBXDuration->InsertValue(100, FieldUnit::CUSTOM);
    mpCBXDuration->InsertValue(200, FieldUnit::CUSTOM);
    mpCBXDuration->InsertValue(300, FieldUnit::CUSTOM);
    mpCBXDuration->InsertValue(500, FieldUnit::CUSTOM);
    mpCBXDuration->AdaptDropDownLineCountToMaximum();


    mpPBAddEffect->SetClickHdl( LINK( this, CustomAnimationPane, implClickHdl ) );
    mpPBRemoveEffect->SetClickHdl( LINK( this, CustomAnimationPane, implClickHdl ) );
    mpLBStart->SetSelectHdl( LINK( this, CustomAnimationPane, implControlListBoxHdl ) );
    mpCBXDuration->SetModifyHdl(LINK( this, CustomAnimationPane, DurationModifiedHdl));
    mpPBPropertyMore->SetClickHdl( LINK( this, CustomAnimationPane, implClickHdl ) );
    mpPBMoveUp->SetClickHdl( LINK( this, CustomAnimationPane, implClickHdl ) );
    mpPBMoveDown->SetClickHdl( LINK( this, CustomAnimationPane, implClickHdl ) );
    mpPBPlay->SetClickHdl( LINK( this, CustomAnimationPane, implClickHdl ) );
    mpCBAutoPreview->SetClickHdl( LINK( this, CustomAnimationPane, implClickHdl ) );
    mpLBCategory->SetSelectHdl( LINK(this, CustomAnimationPane, UpdateAnimationLB) );
    mpMFStartDelay->SetModifyHdl( LINK(this, CustomAnimationPane, DelayModifiedHdl) );
    mpMFStartDelay->SetLoseFocusHdl(LINK( this, CustomAnimationPane, DelayLoseFocusHdl));


    maStrModify = mpFTEffect->GetText();
    maStrModify = mxFTEffect->get_label();

    // get current controller and initialize listeners
    try
@@ -237,28 +220,28 @@ void CustomAnimationPane::dispose()
    for (auto const& tag : aTags)
        tag->Dispose();

    mpPBAddEffect.clear();
    mpPBRemoveEffect.clear();
    mpFTEffect.clear();
    mpFTStart.clear();
    mpLBStart.clear();
    mpFTProperty.clear();
    mpPlaceholderBox.clear();
    mpLBProperty.clear();
    mpPBPropertyMore.clear();
    mpFTDuration.clear();
    mpCBXDuration.clear();
    mpFTStartDelay.clear();
    mpMFStartDelay.clear();
    mpCustomAnimationList.clear();
    mpPBMoveUp.clear();
    mpPBMoveDown.clear();
    mpPBPlay.clear();
    mpCBAutoPreview.clear();
    mpFTCategory.clear();
    mpLBCategory.clear();
    mpFTAnimation.clear();
    mpLBAnimation.clear();
    mxPBAddEffect.reset();
    mxPBRemoveEffect.reset();
    mxFTEffect.reset();
    mxFTStart.reset();
    mxLBStart.reset();
    mxLBSubControl.reset();
    mxFTProperty.reset();
    mxPlaceholderBox.reset();
    mxPBPropertyMore.reset();
    mxFTDuration.reset();
    mxCBXDuration.reset();
    mxFTStartDelay.reset();
    mxMFStartDelay.reset();
    mxCustomAnimationList.reset();
    mxPBMoveUp.reset();
    mxPBMoveDown.reset();
    mxPBPlay.reset();
    mxCBAutoPreview.reset();
    mxFTCategory.reset();
    mxLBCategory.reset();
    mxFTAnimation.reset();
    mxLBAnimation.reset();

    PanelLayout::dispose();
}
@@ -284,8 +267,8 @@ void CustomAnimationPane::StateChanged( StateChangedType nStateChange )

void CustomAnimationPane::KeyInput( const KeyEvent& rKEvt )
{
    if( mpCustomAnimationList )
        mpCustomAnimationList->KeyInput( rKEvt );
    if (mxCustomAnimationList)
        mxCustomAnimationList->KeyInputHdl(rKEvt);
}

void CustomAnimationPane::addListener()
@@ -340,8 +323,8 @@ IMPL_LINK(CustomAnimationPane,EventMultiplexerListener,
            onChangeCurrentPage();
            break;
        case EventMultiplexerEventId::EndTextEdit:
            if( mpMainSequence.get() && rEvent.mpUserData )
                mpCustomAnimationList->update( mpMainSequence );
            if (mpMainSequence.get() && rEvent.mpUserData)
                mxCustomAnimationList->update( mpMainSequence );
            break;
        default: break;
    }
@@ -470,70 +453,74 @@ OUString getPropertyName( sal_Int32 nPropertyType )

void CustomAnimationPane::updateControls()
{
    mpFTDuration->Enable( mxView.is() );
    mpCBXDuration->Enable( mxView.is() );
    mpCustomAnimationList->Enable( mxView.is() );
    mxFTDuration->set_sensitive(mxView.is());
    mxCBXDuration->set_sensitive(mxView.is());
    mxCustomAnimationList->set_sensitive(mxView.is());
    if (comphelper::LibreOfficeKit::isActive())
    {
        mpPBPlay->Hide();
        mpCBAutoPreview->Check(false);
        mpCBAutoPreview->Hide();
        mxPBPlay->hide();
        mxCBAutoPreview->set_active(false);
        mxCBAutoPreview->hide();
    }
    else
    {
        mpPBPlay->Enable( mxView.is() );
        mpCBAutoPreview->Enable( mxView.is() );
        mxPBPlay->set_sensitive(mxView.is());
        mxCBAutoPreview->set_sensitive(mxView.is());
    }

    if( !mxView.is() )
    if (!mxView.is())
    {
        mpPBAddEffect->Enable( false );
        mpPBRemoveEffect->Enable( false );
        mpFTStart->Enable( false );
        mpLBStart->Enable( false );
        mpPBPropertyMore->Enable( false );
        mpLBProperty->Enable( false );
        mpFTProperty->Enable( false );
        mpFTCategory->Disable();
        mpLBCategory->Disable();
        mpFTAnimation->Disable();
        mpLBAnimation->Disable();
        mpFTStartDelay->Disable();
        mpMFStartDelay->Disable();
        mpLBAnimation->Clear();
        mpCustomAnimationList->clear();
        mxPBAddEffect->set_sensitive(false);
        mxPBRemoveEffect->set_sensitive(false);
        mxFTStart->set_sensitive(false);
        mxLBStart->set_sensitive(false);
        mxPBPropertyMore->set_sensitive(false);
        mxPlaceholderBox->set_sensitive(false);
        mxFTProperty->set_sensitive(false);
        mxFTCategory->set_sensitive(false);
        mxLBCategory->set_sensitive(false);
        mxFTAnimation->set_sensitive(false);
        mxLBAnimation->set_sensitive(false);
        mxFTStartDelay->set_sensitive(false);
        mxMFStartDelay->set_sensitive(false);
        mxLBAnimation->clear();
        mnLastSelectedAnimation = -1;
        mxCustomAnimationList->clear();
        return;
    }

    const int nSelectionCount = maListSelection.size();

    mpPBAddEffect->Enable( maViewSelection.hasValue() );
    mpPBRemoveEffect->Enable(nSelectionCount != 0);
    mxPBAddEffect->set_sensitive( maViewSelection.hasValue() );
    mxPBRemoveEffect->set_sensitive(nSelectionCount != 0);
    bool bIsSelected = (nSelectionCount == 1);

    if(bIsSelected)
    {
        mpFTAnimation->Enable();
        mpLBAnimation->Enable();
        mxFTAnimation->set_sensitive(true);
        mxLBAnimation->set_sensitive(true);
    }
    else
    {
        mpFTAnimation->Disable();
        mpLBAnimation->Disable();
        mpLBAnimation->Clear();
        mxFTAnimation->set_sensitive(false);
        mxLBAnimation->set_sensitive(false);
        mxLBAnimation->clear();
        mnLastSelectedAnimation = -1;
    }

    mpLBCategory->Enable(bIsSelected);
    mpFTCategory->Enable(bIsSelected);
    mxLBCategory->set_sensitive(bIsSelected);
    mxFTCategory->set_sensitive(bIsSelected);

    mpFTStart->Enable(nSelectionCount > 0);
    mpLBStart->Enable(nSelectionCount > 0);
    mpLBProperty->Enable(nSelectionCount > 0);
    mpPBPropertyMore->Enable(nSelectionCount > 0);
    mpFTStartDelay->Enable(nSelectionCount > 0);
    mpMFStartDelay->Enable(nSelectionCount > 0);
    mxFTStart->set_sensitive(nSelectionCount > 0);
    mxLBStart->set_sensitive(nSelectionCount > 0);
    mxPlaceholderBox->set_sensitive(nSelectionCount > 0);
    mxPBPropertyMore->set_sensitive(nSelectionCount > 0);
    mxFTStartDelay->set_sensitive(nSelectionCount > 0);
    mxMFStartDelay->set_sensitive(nSelectionCount > 0);

    mpFTProperty->SetText( maStrProperty );
    mxFTProperty->set_label(maStrProperty);

    sal_Int32 nOldPropertyType = mnPropertyType;

    mnPropertyType = nPropertyTypeNone;

@@ -548,59 +535,50 @@ void CustomAnimationPane::updateControls()
        if( !aUIName.isEmpty() )
        {
            aTemp += " " + aUIName;
            mpFTEffect->SetText( aTemp );
            mxFTEffect->set_label( aTemp );
        }

        Any aValue;
        CustomAnimationPresetPtr pDescriptor = CustomAnimationPresets::getCustomAnimationPresets().getEffectDescriptor( pEffect->getPresetId() );
        if( pDescriptor.get() )
        if (pDescriptor.get())
        {
            PropertySubControl* pSubControl = nullptr;

            Any aValue;

            std::vector<OUString> aProperties( pDescriptor->getProperties() );
            if( !aProperties.empty() )
            {
                mnPropertyType = getPropertyType( aProperties.front() );

                mpFTProperty->SetText( getPropertyName( mnPropertyType )  );
                mxFTProperty->set_label( getPropertyName( mnPropertyType )  );

                aValue = getProperty1Value( mnPropertyType, pEffect );
            }
        }

            if( aValue.hasValue() )
            {
                pSubControl = mpLBProperty->getSubControl();
                if( !pSubControl || (pSubControl->getControlType() != mnPropertyType) )
                {
                    auto pNewControl = PropertySubControl::create( mnPropertyType, mpPlaceholderBox, aValue, pEffect->getPresetId(), LINK( this, CustomAnimationPane, implPropertyHdl ) );
                    pSubControl = pNewControl.get();
                    mpLBProperty->setSubControl( std::move(pNewControl) );
                }
                else
                {
                    pSubControl->setValue( aValue, pEffect->getPresetId() );
                }
            }
            else
            {
                mpLBProperty->setSubControl( nullptr );
            }
        sal_Int32 nNewPropertyType = mnPropertyType;
        // if there is no value, then the control will be disabled, just show a disabled Direction box in that
        // case to have something to fill the space
        if (!aValue.hasValue())
            nNewPropertyType = nPropertyTypeDirection;

            bool bEnable = (pSubControl != nullptr) && (pSubControl->getControl()->IsEnabled());
            mpLBProperty->Enable( bEnable );
            mpFTProperty->Enable( bEnable );
        if (!mxLBSubControl || nOldPropertyType != nNewPropertyType)
        {
            mxLBSubControl = SdPropertySubControl::create(nNewPropertyType, mxFTProperty.get(), mxPlaceholderBox.get(), GetFrameWeld(), aValue, pEffect->getPresetId(), LINK(this, CustomAnimationPane, implPropertyHdl));
        }
        else
        {
            mpLBProperty->setSubControl( nullptr );
            mpFTProperty->Enable( false );
            mpLBProperty->Enable( false );
            mpPBPropertyMore->Enable( false );
            mpFTStartDelay->Enable( false );
            mpMFStartDelay->Enable( false );
            mxLBSubControl->setValue(aValue, pEffect->getPresetId());
        }
        sal_uInt32 nCategoryPos = LISTBOX_ENTRY_NOTFOUND;

        bool bEnable = aValue.hasValue();
        mxPlaceholderBox->set_sensitive( bEnable );
        mxFTProperty->set_sensitive( bEnable );

        if (!pDescriptor.get())
        {
            mxPBPropertyMore->set_sensitive( false );
            mxFTStartDelay->set_sensitive( false );
            mxMFStartDelay->set_sensitive( false );
        }
        sal_Int32 nCategoryPos = -1;
        switch(pEffect->getPresetClass())
        {
            case EffectPresetClass::ENTRANCE: nCategoryPos = 0; break;
@@ -619,19 +597,22 @@ void CustomAnimationPane::updateControls()
            default:
            break;
        }
        mpLBCategory->SelectEntryPos(nCategoryPos);
        mxLBCategory->set_active(nCategoryPos);

        fillAnimationLB( pEffect->hasText() );

        OUString rsPresetId = pEffect->getPresetId();
        sal_Int32 nAnimationPos = mpLBAnimation->GetEntryCount();
        sal_Int32 nAnimationPos = mxLBAnimation->n_children();
        while( nAnimationPos-- )
        {
            void* pEntryData = mpLBAnimation->GetEntryData( nAnimationPos );
            if( pEntryData )
            auto nEntryData = mxLBAnimation->get_id(nAnimationPos).toInt64();
            if (nEntryData)
            {
                CustomAnimationPresetPtr& pPtr = *static_cast< CustomAnimationPresetPtr* >(pEntryData);
                CustomAnimationPresetPtr& pPtr = *reinterpret_cast<CustomAnimationPresetPtr*>(nEntryData);
                if( pPtr.get() && pPtr->getPresetId() == rsPresetId )
                {
                    mpLBAnimation->SelectEntryPos( nAnimationPos );
                    mxLBAnimation->select( nAnimationPos );
                    mnLastSelectedAnimation = nAnimationPos;
                    break;
                }
            }
@@ -641,11 +622,20 @@ void CustomAnimationPane::updateControls()
        if (nAnimationPos < 0 && nCategoryPos == 3)
        {
            if (rsPresetId == "libo-motionpath-curve")
                mpLBAnimation->SelectEntryPos(mnCurvePathPos);
            {
                mxLBAnimation->select(mnCurvePathPos);
                mnLastSelectedAnimation = mnCurvePathPos;
            }
            else if (rsPresetId == "libo-motionpath-polygon")
                mpLBAnimation->SelectEntryPos(mnPolygonPathPos);
            {
                mxLBAnimation->select(mnPolygonPathPos);
                mnLastSelectedAnimation = mnPolygonPathPos;
            }
            else if (rsPresetId == "libo-motionpath-freeform-line")
                mpLBAnimation->SelectEntryPos(mnFreeformPathPos);
            {
                mxLBAnimation->select(mnFreeformPathPos);
                mnLastSelectedAnimation = mnFreeformPathPos;
            }
        }

        sal_uInt16 nPos = 0xffff;
@@ -658,38 +648,39 @@ void CustomAnimationPane::updateControls()
        case EffectNodeType::AFTER_PREVIOUS:    nPos = 2; break;
        }

        mpLBStart->SelectEntryPos( nPos );
        mxLBStart->set_active( nPos );

        double fDuration = pEffect->getDuration();
        const bool bHasSpeed = fDuration > 0.001;

        mpFTDuration->Enable(bHasSpeed);
        mpCBXDuration->Enable(bHasSpeed);
        mxFTDuration->set_sensitive(bHasSpeed);
        mxCBXDuration->set_sensitive(bHasSpeed);

        if( bHasSpeed )
        {
            mpCBXDuration->SetValue( fDuration*100.0 );
            mxCBXDuration->set_value(fDuration*100.0, FieldUnit::NONE);
        }

        mpPBPropertyMore->Enable();
        mxPBPropertyMore->set_sensitive(true);

        mpFTStartDelay->Enable();
        mpMFStartDelay->Enable();
        mxFTStartDelay->set_sensitive(true);
        mxMFStartDelay->set_sensitive(true);
        double fBegin = pEffect->getBegin();
        mpMFStartDelay->SetValue(fBegin*10.0);
        mxMFStartDelay->set_value(fBegin*10.0, FieldUnit::NONE);
    }
    else
    {
        mpLBProperty->setSubControl( nullptr );
        mpFTProperty->Enable( false );
        mpLBProperty->Enable( false );
        mpFTStartDelay->Enable( false );
        mpMFStartDelay->Enable( false );
        mpPBPropertyMore->Enable( false );
        mpFTDuration->Enable(false);
        mpCBXDuration->Enable(false);
        mpCBXDuration->SetNoSelection();
        mpFTEffect->SetText( maStrModify );
        // use an empty direction box to fill the space
        mxLBSubControl = SdPropertySubControl::create(nPropertyTypeDirection, mxFTProperty.get(), mxPlaceholderBox.get(), GetFrameWeld(), uno::Any(), OUString(), LINK(this, CustomAnimationPane, implPropertyHdl));
        mxPlaceholderBox->set_sensitive(false);
        mxFTProperty->set_sensitive(false);
        mxFTStartDelay->set_sensitive(false);
        mxMFStartDelay->set_sensitive(false);
        mxPBPropertyMore->set_sensitive(false);
        mxFTDuration->set_sensitive(false);
        mxCBXDuration->set_sensitive(false);
        mxCBXDuration->set_text(OUString());
        mxFTEffect->set_label(maStrModify);
    }

    bool bEnableUp = true;
@@ -715,7 +706,7 @@ void CustomAnimationPane::updateControls()
            {
                ++aIter;
            }
            while( (aIter != mpMainSequence->getEnd()) && !(mpCustomAnimationList->isExpanded(*aIter) ) );
            while( (aIter != mpMainSequence->getEnd()) && !(mxCustomAnimationList->isExpanded(*aIter) ) );

            if( aIter == mpMainSequence->getEnd() )
                bEnableDown = false;
@@ -748,11 +739,11 @@ void CustomAnimationPane::updateControls()
        }
    }

    mpPBMoveUp->Enable(mxView.is() &&  bEnableUp);
    mpPBMoveDown->Enable(mxView.is() && bEnableDown);
    mxPBMoveUp->set_sensitive(mxView.is() &&  bEnableUp);
    mxPBMoveDown->set_sensitive(mxView.is() && bEnableDown);

    SdOptions* pOptions = SD_MOD()->GetSdOptions(DocumentType::Impress);
    mpCBAutoPreview->Check( pOptions->IsPreviewChangedEffects() );
    mxCBAutoPreview->set_active(pOptions->IsPreviewChangedEffects());

    updateMotionPathTags();
}
@@ -845,7 +836,7 @@ void CustomAnimationPane::onSelectionChanged()
    {
        Reference< XSelectionSupplier >  xSel( mxView, UNO_QUERY_THROW );
        maViewSelection = xSel->getSelection();
        mpCustomAnimationList->onSelectionChanged( maViewSelection );
        mxCustomAnimationList->onSelectionChanged( maViewSelection );
        updateControls();
    }
    catch( Exception& )
@@ -889,12 +880,6 @@ void CustomAnimationPane::UpdateLook()
        ::sfx2::sidebar::Theme::GetWallpaper(
            ::sfx2::sidebar::Theme::Paint_PanelBackground));
    SetBackground(aBackground);
    if (mpFTStart != nullptr)
        mpFTStart->SetBackground(aBackground);
    if (mpFTProperty != nullptr)
        mpFTProperty->SetBackground(aBackground);
    if (mpFTDuration != nullptr)
        mpFTDuration->SetBackground(aBackground);
}

static void addValue( const std::unique_ptr<STLPropertySet>& pSet, sal_Int32 nHandle, const Any& rValue )
@@ -1663,7 +1648,7 @@ void CustomAnimationPane::onChangeCurrentPage()
            if( pPage )
            {
                mpMainSequence = pPage->getMainSequence();
                mpCustomAnimationList->update( mpMainSequence );
                mxCustomAnimationList->update( mpMainSequence );
            }
            updateControls();
        }
@@ -1797,44 +1782,44 @@ void CustomAnimationPane::onAdd()
    }

    CustomAnimationPresetPtr pDescriptor;
    mpFTCategory->Enable();
    mpFTAnimation->Enable();
    mxFTCategory->set_sensitive(true);
    mxFTAnimation->set_sensitive(true);

    bool bCategoryReset = false;

    if (!mpLBCategory->IsEnabled() ||
            mpLBCategory->GetSelectedEntryPos()  == LISTBOX_ENTRY_NOTFOUND)
    if (!mxLBCategory->get_sensitive() || mxLBCategory->get_active() == -1)
    {
        mpLBCategory->Enable();
        mpLBCategory->SelectEntryPos(0);
        mxLBCategory->set_sensitive(true);
        mxLBCategory->set_active(0);
        bCategoryReset = true;
    }

    if (bCategoryReset || !mpLBAnimation->IsEnabled() ||
            mpLBAnimation->GetSelectedEntryPos()  == LISTBOX_ENTRY_NOTFOUND)
    if (bCategoryReset || !mxLBAnimation->get_sensitive() ||
        mxLBAnimation->get_selected_index() == -1)
    {
        mpLBAnimation->Enable();
        mxLBAnimation->set_sensitive(true);

        sal_uInt32 nFirstEffect = fillAnimationLB( bHasText );
        if(nFirstEffect == LISTBOX_ENTRY_NOTFOUND)
        sal_Int32 nFirstEffect = fillAnimationLB(bHasText);
        if (nFirstEffect == -1)
            return;

        mpLBAnimation->SelectEntryPos(nFirstEffect);
        mxLBAnimation->select(nFirstEffect);
        mnLastSelectedAnimation = nFirstEffect;
    }

    void* pEntryData = mpLBAnimation->GetSelectedEntryData();
    if( pEntryData )
        pDescriptor = *static_cast< CustomAnimationPresetPtr* >( pEntryData );
    auto nEntryData = mxLBAnimation->get_selected_id().toInt64();
    if (nEntryData)
        pDescriptor = *reinterpret_cast<CustomAnimationPresetPtr*>(nEntryData);

    const double fDuration = pDescriptor->getDuration();
    mpCBXDuration->SetValue( fDuration*100.0 );
    mxCBXDuration->set_value(fDuration*100.0, FieldUnit::NONE);
    bool bHasSpeed = pDescriptor->getDuration() > 0.001;
    mpCBXDuration->Enable( bHasSpeed );
    mpFTDuration->Enable( bHasSpeed );
    mxCBXDuration->set_sensitive( bHasSpeed );
    mxFTDuration->set_sensitive( bHasSpeed );

    if( pDescriptor.get() )
    {
        mpCustomAnimationList->SelectAll( false );
        mxCustomAnimationList->unselect_all();

        // gather shapes from the selection
        bool bFirst = true;
@@ -1858,7 +1843,7 @@ void CustomAnimationPane::onAdd()
                pCreated->setNodeType( EffectNodeType::WITH_PREVIOUS );

            if( pCreated.get() )
                mpCustomAnimationList->select( pCreated );
                mxCustomAnimationList->select( pCreated );
        }
    }

@@ -1902,11 +1887,8 @@ void CustomAnimationPane::remove( CustomAnimationEffectPtr const & pEffect )

void CustomAnimationPane::onChangeStart()
{
    if( mpLBStart->GetSelectedEntryCount() != 1 )
        return;

    sal_Int16 nNodeType;
    switch( mpLBStart->GetSelectedEntryPos() )
    switch( mxLBStart->get_active() )
    {
    case 0: nNodeType = EffectNodeType::ON_CLICK; break;
    case 1: nNodeType = EffectNodeType::WITH_PREVIOUS; break;
@@ -1971,10 +1953,9 @@ double CustomAnimationPane::getDuration() const
{
    double fDuration = 0;

    if(!(mpCBXDuration->GetText()).isEmpty())
    {
        fDuration = static_cast<double>(mpCBXDuration->GetValue())/100.0;
    }
    if (!mxCBXDuration->get_text().isEmpty())
        fDuration = mxCBXDuration->get_value(FieldUnit::NONE) / 100.0;

    return fDuration;
}

@@ -1982,10 +1963,10 @@ PathKind CustomAnimationPane::getCreatePathKind() const
{
    PathKind eKind = PathKind::NONE;

    if( ( mpLBAnimation->GetSelectedEntryCount() == 1 ) &&
        ( mpLBCategory->GetSelectedEntryPos() == gnMotionPathPos ) )
    if (mxLBAnimation->count_selected_rows() == 1 &&
        mxLBCategory->get_active() == gnMotionPathPos)
    {
        const sal_Int32 nPos = mpLBAnimation->GetSelectedEntryPos();
        const sal_Int32 nPos = mxLBAnimation->get_selected_index();
        if( nPos == mnCurvePathPos )
        {
            eKind = PathKind::CURVE;
@@ -2039,14 +2020,14 @@ void CustomAnimationPane::createPath( PathKind eKind, std::vector< Any >& rTarge
/// this link is called when the property box is modified by the user
IMPL_LINK_NOARG(CustomAnimationPane, implPropertyHdl, LinkParamNone*, void)
{
    if( !mpLBProperty->getSubControl() )
    if (!mxLBSubControl)
        return;

    addUndo();

    MainSequenceRebuildGuard aGuard( mpMainSequence );

    const Any aValue( mpLBProperty->getSubControl()->getValue() );
    const Any aValue(mxLBSubControl->getValue());

    bool bNeedUpdate = false;

@@ -2067,14 +2048,14 @@ IMPL_LINK_NOARG(CustomAnimationPane, implPropertyHdl, LinkParamNone*, void)
    onPreview( false );
}

IMPL_LINK_NOARG(CustomAnimationPane, DelayModifiedHdl, Edit&, void)
IMPL_LINK_NOARG(CustomAnimationPane, DelayModifiedHdl, weld::MetricSpinButton&, void)
{
    addUndo();
}

IMPL_LINK_NOARG(CustomAnimationPane, DelayLoseFocusHdl, Control&, void)
IMPL_LINK_NOARG(CustomAnimationPane, DelayLoseFocusHdl, weld::Widget&, void)
{
    double fBegin = mpMFStartDelay->GetValue();
    double fBegin = mxMFStartDelay->get_value(FieldUnit::NONE);

    //sequence rebuild only when the control loses focus
    MainSequenceRebuildGuard aGuard( mpMainSequence );
@@ -2090,19 +2071,28 @@ IMPL_LINK_NOARG(CustomAnimationPane, DelayLoseFocusHdl, Control&, void)
    mrBase.GetDocShell()->SetModified();
}

IMPL_LINK_NOARG(CustomAnimationPane, AnimationSelectHdl, ListBox&, void)
IMPL_LINK_NOARG(CustomAnimationPane, AnimationSelectHdl, weld::TreeView&, void)
{
    int nSelected = mxLBAnimation->get_selected_index();

    // tdf#99137, the selected entry may also be a subcategory title, so not an effect
    // just skip it and move to the next one in this case
    if (mxLBAnimation->get_text_emphasis(nSelected, 0))
    {
        if (nSelected == 0 || nSelected > mnLastSelectedAnimation)
            mxLBAnimation->select(++nSelected);
        else
            mxLBAnimation->select(--nSelected);
    }

    mnLastSelectedAnimation = nSelected;

    if( maListSelection.size() != 1 )
        return;

    CustomAnimationPresetPtr* pPreset = static_cast< CustomAnimationPresetPtr* >(mpLBAnimation->GetSelectedEntryData());
    CustomAnimationPresetPtr* pPreset = reinterpret_cast<CustomAnimationPresetPtr*>(mxLBAnimation->get_id(nSelected).toInt64());
    PathKind ePathKind = getCreatePathKind();

    // tdf#99137, the selected entry may also be a subcategory title, so not an effect
    // just leave in this case
    if ( !pPreset && ( ePathKind == PathKind::NONE ) )
        return;

    if ( ePathKind != PathKind::NONE )
    {
        std::vector< Any > aTargets;
@@ -2153,30 +2143,41 @@ IMPL_LINK_NOARG(CustomAnimationPane, AnimationSelectHdl, ListBox&, void)
    onPreview(false);
}

IMPL_LINK_NOARG(CustomAnimationPane, UpdateAnimationLB, ListBox&, void)
IMPL_LINK_NOARG(CustomAnimationPane, UpdateAnimationLB, weld::ComboBox&, void)
{
    //FIXME: first effect only? what if there is more?
    CustomAnimationEffectPtr pEffect = maListSelection.front();
    fillAnimationLB( pEffect->hasText() );
}

IMPL_LINK_NOARG(CustomAnimationPane, DurationModifiedHdl, Edit&, void)
IMPL_LINK_NOARG(CustomAnimationPane, DurationModifiedHdl, weld::MetricSpinButton&, void)
{
    if(!(mpCBXDuration->GetText()).isEmpty() )
    if (!mxCBXDuration->get_text().isEmpty())
    {
        double duration_value = static_cast<double>(mpCBXDuration->GetValue());
        double duration_value = static_cast<double>(mxCBXDuration->get_value(FieldUnit::NONE));
        if(duration_value <= 0.0)
        {
            mpCBXDuration->SetValue(1);
            mxCBXDuration->set_value(1, FieldUnit::NONE);
        }
        onChangeSpeed();
    }
}

sal_uInt32 CustomAnimationPane::fillAnimationLB( bool bHasText )
namespace
{
    void InsertCategory(weld::TreeView& rLBAnimation, const OUString& rMotionPathLabel)
    {
        int nRow = rLBAnimation.n_children();
        rLBAnimation.append_text(rMotionPathLabel);
        rLBAnimation.set_text_emphasis(nRow, true, 0);
        rLBAnimation.set_text_align(nRow, 0.5, 0);
    }
}

sal_Int32 CustomAnimationPane::fillAnimationLB( bool bHasText )
{
    PresetCategoryList rCategoryList;
    sal_uInt16 nPosition = mpLBCategory->GetSelectedEntryPos();
    sal_uInt16 nPosition = mxLBCategory->get_active();
    const CustomAnimationPresets& rPresets (CustomAnimationPresets::getCustomAnimationPresets());
    switch(nPosition)
    {
@@ -2187,24 +2188,37 @@ sal_uInt32 CustomAnimationPane::fillAnimationLB( bool bHasText )
        case 4:rCategoryList = rPresets.getMiscPresets();break;
    }

    sal_uInt32 nFirstEffect = LISTBOX_ENTRY_NOTFOUND;
    sal_Int32 nFirstEffect = -1;

    mpLBAnimation->Clear();
    int nOldEntryCount = mxLBAnimation->n_children();
    int nOldScrollPos = mxLBAnimation->vadjustment_get_value();

    if(nPosition == gnMotionPathPos)
    mxLBAnimation->freeze();
    mxLBAnimation->clear();
    mnLastSelectedAnimation = -1;

    if (nPosition == gnMotionPathPos)
    {
        OUString sMotionPathLabel( SdResId( STR_CUSTOMANIMATION_USERPATH ) );
        mpLBAnimation->InsertCategory( sMotionPathLabel );
        mnCurvePathPos = mpLBAnimation->InsertEntry( SvxResId(STR_ObjNameSingulCOMBLINE) );
        mnPolygonPathPos = mpLBAnimation->InsertEntry( SvxResId(STR_ObjNameSingulPOLY) );
        mnFreeformPathPos = mpLBAnimation->InsertEntry( SvxResId(STR_ObjNameSingulFREELINE) );
        InsertCategory(*mxLBAnimation, sMotionPathLabel);
        mnCurvePathPos = mxLBAnimation->n_children();
        mxLBAnimation->append_text( SvxResId(STR_ObjNameSingulCOMBLINE) );
        mxLBAnimation->set_text_emphasis(mnCurvePathPos, false, 0);
        mnPolygonPathPos = mnCurvePathPos + 1;
        mxLBAnimation->append_text( SvxResId(STR_ObjNameSingulPOLY) );
        mxLBAnimation->set_text_emphasis(mnPolygonPathPos, false, 0);
        mnFreeformPathPos = mnPolygonPathPos + 1;
        mxLBAnimation->append_text( SvxResId(STR_ObjNameSingulFREELINE) );
        mxLBAnimation->set_text_emphasis(mnFreeformPathPos, false, 0);
    }

    for (PresetCategoryPtr& pCategory : rCategoryList)
    {
        if( pCategory.get() )
        {
            mpLBAnimation->InsertCategory( pCategory->maLabel );
            InsertCategory(*mxLBAnimation, pCategory->maLabel);

            int nPos = mxLBAnimation->n_children();

            std::vector< CustomAnimationPresetPtr > aSortedVector(pCategory->maEffects.size());
            std::copy( pCategory->maEffects.begin(), pCategory->maEffects.end(), aSortedVector.begin() );
@@ -2214,49 +2228,59 @@ sal_uInt32 CustomAnimationPane::fillAnimationLB( bool bHasText )
                // ( !isTextOnly || ( isTextOnly && bHasText ) ) <=> !isTextOnly || bHasText
                if( pDescriptor.get() && ( !pDescriptor->isTextOnly() || bHasText ) )
                {
                    sal_Int32 nPos = mpLBAnimation->InsertEntry( pDescriptor->getLabel() );
                    mpLBAnimation->SetEntryData(nPos, new CustomAnimationPresetPtr(pDescriptor));
                    auto pCustomPtr = new CustomAnimationPresetPtr(pDescriptor);
                    OUString sId = OUString::number(reinterpret_cast<sal_Int64>(pCustomPtr));
                    mxLBAnimation->append(sId, pDescriptor->getLabel());
                    mxLBAnimation->set_text_emphasis(nPos, false, 0);

                    if( nFirstEffect == LISTBOX_ENTRY_NOTFOUND )
                    if (nFirstEffect == -1)
                        nFirstEffect = nPos;

                    ++nPos;
                }
            }
        }
    }

    mxLBAnimation->thaw();

    if (mxLBAnimation->n_children() == nOldEntryCount)
        mxLBAnimation->vadjustment_set_value(nOldScrollPos);

    return nFirstEffect;
}


IMPL_LINK( CustomAnimationPane, implClickHdl, Button*, pBtn, void )
IMPL_LINK(CustomAnimationPane, implClickHdl, weld::Button&, rBtn, void)
{
    implControlHdl(pBtn);
    implControlHdl(&rBtn);
}
IMPL_LINK( CustomAnimationPane, implControlListBoxHdl, ListBox&, rListBox, void )

IMPL_LINK( CustomAnimationPane, implControlListBoxHdl, weld::ComboBox&, rListBox, void )
{
    implControlHdl(&rListBox);
}

/// this link is called when one of the controls is modified
void CustomAnimationPane::implControlHdl(Control const * pControl )
void CustomAnimationPane::implControlHdl(const weld::Widget* pControl)
{
    if( pControl == mpPBAddEffect )
    if (pControl == mxPBAddEffect.get())
        onAdd();
    else if( pControl == mpPBRemoveEffect )
    else if (pControl == mxPBRemoveEffect.get())
        onRemove();
    else if( pControl == mpLBStart )
    else if (pControl == mxLBStart.get())
        onChangeStart();
    else if( pControl == mpPBPropertyMore )
    else if (pControl == mxPBPropertyMore.get())
        showOptions();
    else if( pControl == mpPBMoveUp )
    else if (pControl == mxPBMoveUp.get())
        moveSelection( true );
    else if( pControl == mpPBMoveDown )
    else if (pControl == mxPBMoveDown.get())
        moveSelection( false );
    else if( pControl == mpPBPlay )
    else if (pControl == mxPBPlay.get())
        onPreview( true );
    else if( pControl == mpCBAutoPreview )
    else if (pControl == mxCBAutoPreview.get())
    {
        SdOptions* pOptions = SD_MOD()->GetSdOptions(DocumentType::Impress);
        pOptions->SetPreviewChangedEffects( mpCBAutoPreview->IsChecked() );
        pOptions->SetPreviewChangedEffects(mxCBAutoPreview->get_active());
    }
}

@@ -2299,9 +2323,8 @@ void CustomAnimationPane::moveSelection( bool bUp )
                if( aInsertPos != rEffectSequence.begin() )
                {
                    --aInsertPos;
                    while( (aInsertPos != rEffectSequence.begin()) && !mpCustomAnimationList->isExpanded(*aInsertPos))
                    while( (aInsertPos != rEffectSequence.begin()) && !mxCustomAnimationList->isExpanded(*aInsertPos))
                        --aInsertPos;

                    rEffectSequence.insert( aInsertPos, pEffect );
                }
                else
@@ -2332,12 +2355,11 @@ void CustomAnimationPane::moveSelection( bool bUp )
                    ++aInsertPos;
                    // Advance over rolled-up (un-expanded) items, unless we just moved it there.
                    while( (aInsertPos != rEffectSequence.end())
                        && !mpCustomAnimationList->isExpanded(*aInsertPos)
                        && !mxCustomAnimationList->isExpanded(*aInsertPos)
                        && (std::find(maListSelection.begin(), maListSelection.end(), *aInsertPos)
                                == maListSelection.end())
                    )
                        ++aInsertPos;

                    rEffectSequence.insert( aInsertPos, pEffect );
                }
                else
@@ -2359,7 +2381,7 @@ void CustomAnimationPane::moveSelection( bool bUp )

void CustomAnimationPane::onPreview( bool bForcePreview )
{
    if( !bForcePreview && !mpCBAutoPreview->IsChecked() )
    if (!bForcePreview && !mxCBAutoPreview->get_active())
        return;

    // No preview in LOK.
@@ -2416,7 +2438,7 @@ void CustomAnimationPane::preview( const Reference< XAnimationNode >& xAnimation
// ICustomAnimationListController
void CustomAnimationPane::onSelect()
{
    maListSelection = mpCustomAnimationList->getSelection();
    maListSelection = mxCustomAnimationList->getSelection();
    updateControls();

    // mark shapes from selected effects
@@ -2466,20 +2488,16 @@ void CustomAnimationPane::onDragNDropComplete(std::vector< CustomAnimationEffect
            // Update model with new location (function triggers a rebuild)
            // target may be null, which will insert at the end.
            mpMainSequence->moveToBeforeEffect( pEffect, pEffectInsertBefore );

            // Done moving effect and its hidden sub-effects when *next* effect is visible.
            if (aIter != aEnd && mpCustomAnimationList->isVisible(*aIter))
            if (aIter != aEnd && mxCustomAnimationList->isVisible(*aIter))
                break;
        }

    }


    updateControls();
    mrBase.GetDocShell()->SetModified();
}


void CustomAnimationPane::updatePathFromMotionPathTag( const rtl::Reference< MotionPathTag >& xTag )
{
    MainSequenceRebuildGuard aGuard( mpMainSequence );
diff --git a/sd/source/ui/animations/CustomAnimationPane.hxx b/sd/source/ui/animations/CustomAnimationPane.hxx
index 7aa2931..472a4df 100644
--- a/sd/source/ui/animations/CustomAnimationPane.hxx
+++ b/sd/source/ui/animations/CustomAnimationPane.hxx
@@ -20,13 +20,9 @@
#ifndef INCLUDED_SD_SOURCE_UI_ANIMATIONS_CUSTOMANIMATIONPANE_HXX
#define INCLUDED_SD_SOURCE_UI_ANIMATIONS_CUSTOMANIMATIONPANE_HXX

#include <vcl/button.hxx>
#include <vcl/field.hxx>
#include <vcl/fixed.hxx>
#include <svx/sidebar/PanelLayout.hxx>
#include "CustomAnimationDialog.hxx"
#include "CustomAnimationList.hxx"
#include "CategoryListBox.hxx"
#include "motionpathtag.hxx"
#include <misc/scopelock.hxx>

@@ -110,52 +106,53 @@ private:
    static css::uno::Any getProperty1Value( sal_Int32 nType, const CustomAnimationEffectPtr& pEffect );
    static bool setProperty1Value( sal_Int32 nType, const CustomAnimationEffectPtr& pEffect, const css::uno::Any& rValue );
    void UpdateLook();
    sal_uInt32 fillAnimationLB( bool bHasText );
    sal_Int32 fillAnimationLB( bool bHasText );
    PathKind getCreatePathKind() const;
    void createPath( PathKind eKind, std::vector< ::com::sun::star::uno::Any >& rTargets, double fDuration );

    DECL_LINK( implControlListBoxHdl, ListBox&, void );
    DECL_LINK( implClickHdl, Button*, void );
    DECL_LINK( implControlListBoxHdl, weld::ComboBox&, void );
    DECL_LINK( implClickHdl, weld::Button&, void );
    DECL_LINK( implPropertyHdl, LinkParamNone*, void );
    DECL_LINK( EventMultiplexerListener, tools::EventMultiplexerEvent&, void );
    DECL_LINK( lateInitCallback, Timer *, void );
    DECL_LINK( DurationModifiedHdl, Edit&, void );
    DECL_LINK( DelayModifiedHdl, Edit&, void );
    DECL_LINK( DelayLoseFocusHdl, Control&, void );
    DECL_LINK( UpdateAnimationLB, ListBox&, void );
    DECL_LINK( AnimationSelectHdl, ListBox&, void );
    void implControlHdl(Control const *);
    DECL_LINK( DurationModifiedHdl, weld::MetricSpinButton&, void );
    DECL_LINK( DelayModifiedHdl, weld::MetricSpinButton&, void );
    DECL_LINK( DelayLoseFocusHdl, weld::Widget&, void );
    DECL_LINK( UpdateAnimationLB, weld::ComboBox&, void );
    DECL_LINK( AnimationSelectHdl, weld::TreeView&, void );
    void implControlHdl(const weld::Widget* pControl);

private:
    ViewShellBase& mrBase;

    // UI Elements
    VclPtr<FixedText>   mpFTAnimation;
    VclPtr<CustomAnimationList> mpCustomAnimationList;
    VclPtr<PushButton>  mpPBAddEffect;
    VclPtr<PushButton>  mpPBRemoveEffect;
    VclPtr<PushButton>  mpPBMoveUp;
    VclPtr<PushButton>  mpPBMoveDown;
    VclPtr<FixedText>   mpFTCategory;
    VclPtr<ListBox>     mpLBCategory;
    VclPtr<FixedText>   mpFTEffect;
    VclPtr<CategoryListBox> mpLBAnimation;
    VclPtr<FixedText>   mpFTStart;
    VclPtr<ListBox>     mpLBStart;
    VclPtr<FixedText>   mpFTProperty;
    VclPtr<PropertyControl> mpLBProperty;
    VclPtr<vcl::Window> mpPlaceholderBox;
    VclPtr<PushButton>  mpPBPropertyMore;
    VclPtr<FixedText>   mpFTDuration;
    VclPtr<MetricBox>   mpCBXDuration;
    VclPtr<FixedText>   mpFTStartDelay;
    VclPtr<MetricField> mpMFStartDelay;
    VclPtr<CheckBox>    mpCBAutoPreview;
    VclPtr<PushButton>  mpPBPlay;
    std::unique_ptr<weld::Label> mxFTAnimation;
    std::unique_ptr<CustomAnimationList> mxCustomAnimationList;
    std::unique_ptr<weld::Button> mxPBAddEffect;
    std::unique_ptr<weld::Button> mxPBRemoveEffect;
    std::unique_ptr<weld::Button> mxPBMoveUp;
    std::unique_ptr<weld::Button> mxPBMoveDown;
    std::unique_ptr<weld::Label> mxFTCategory;
    std::unique_ptr<weld::ComboBox> mxLBCategory;
    std::unique_ptr<weld::Label> mxFTEffect;
    std::unique_ptr<weld::TreeView> mxLBAnimation;
    std::unique_ptr<weld::Label> mxFTStart;
    std::unique_ptr<weld::ComboBox> mxLBStart;
    std::unique_ptr<weld::Label> mxFTProperty;
    std::unique_ptr<SdPropertySubControl> mxLBSubControl;
    std::unique_ptr<weld::Container> mxPlaceholderBox;
    std::unique_ptr<weld::Button> mxPBPropertyMore;
    std::unique_ptr<weld::Label> mxFTDuration;
    std::unique_ptr<weld::MetricSpinButton> mxCBXDuration;
    std::unique_ptr<weld::Label> mxFTStartDelay;
    std::unique_ptr<weld::MetricSpinButton> mxMFStartDelay;
    std::unique_ptr<weld::CheckButton> mxCBAutoPreview;
    std::unique_ptr<weld::Button> mxPBPlay;

    OUString    maStrModify;
    OUString    maStrProperty;

    sal_Int32   mnLastSelectedAnimation;
    sal_Int32   mnPropertyType;
    static sal_Int32 const gnMotionPathPos = 3;
    sal_Int32   mnCurvePathPos;
diff --git a/sd/uiconfig/simpress/ui/customanimationfragment.ui b/sd/uiconfig/simpress/ui/customanimationfragment.ui
index b7cb9c5..c033ad5 100644
--- a/sd/uiconfig/simpress/ui/customanimationfragment.ui
+++ b/sd/uiconfig/simpress/ui/customanimationfragment.ui
@@ -220,8 +220,6 @@
    <property name="can_focus">False</property>
    <property name="no_show_all">True</property>
    <property name="hexpand">True</property>
    <property name="vexpand">True</property>
    <property name="border_width">6</property>
    <property name="spacing">3</property>
    <child>
      <object class="GtkComboBoxText" id="combo">
@@ -276,6 +274,7 @@
      <object class="GtkSpinButton" id="fontsize">
        <property name="can_focus">True</property>
        <property name="no_show_all">True</property>
        <property name="hexpand">True</property>
        <property name="adjustment">adjustment1</property>
      </object>
      <packing>
@@ -306,6 +305,7 @@
      <object class="GtkSpinButton" id="rotate">
        <property name="can_focus">True</property>
        <property name="no_show_all">True</property>
        <property name="hexpand">True</property>
        <property name="adjustment">adjustment2</property>
      </object>
      <packing>
@@ -336,6 +336,7 @@
      <object class="GtkSpinButton" id="transparent">
        <property name="can_focus">True</property>
        <property name="no_show_all">True</property>
        <property name="hexpand">True</property>
        <property name="adjustment">adjustment3</property>
      </object>
      <packing>
@@ -366,6 +367,7 @@
      <object class="GtkSpinButton" id="scale">
        <property name="can_focus">True</property>
        <property name="no_show_all">True</property>
        <property name="hexpand">True</property>
        <property name="adjustment">adjustment4</property>
      </object>
      <packing>
diff --git a/sd/uiconfig/simpress/ui/customanimationspanel.ui b/sd/uiconfig/simpress/ui/customanimationspanel.ui
index 61eb643..9418432 100644
--- a/sd/uiconfig/simpress/ui/customanimationspanel.ui
+++ b/sd/uiconfig/simpress/ui/customanimationspanel.ui
@@ -1,72 +1,173 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.20.4 -->
<!-- Generated with glade 3.22.2 -->
<interface domain="sd">
  <requires lib="gtk+" version="3.18"/>
  <requires lib="LibreOffice" version="1.0"/>
  <object class="GtkAdjustment" id="adjustment1">
    <property name="upper">999.99000000000001</property>
    <property name="step_increment">1</property>
    <property name="page_increment">10</property>
    <property name="upper">100000</property>
    <property name="step_increment">0.5</property>
    <property name="page_increment">5</property>
  </object>
  <object class="GtkAdjustment" id="adjustment2">
    <property name="upper">100000</property>
    <property name="step_increment">0.5</property>
    <property name="page_increment">5</property>
  </object>
  <object class="GtkImage" id="image1">
    <property name="visible">True</property>
    <property name="can_focus">False</property>
    <property name="pixbuf">sfx2/res/symphony/sidebar-property-small.png</property>
    <property name="icon_name">sfx2/res/symphony/sidebar-property-small.png</property>
  </object>
  <object class="GtkImage" id="image_add">
    <property name="visible">True</property>
    <property name="can_focus">False</property>
    <property name="pixbuf">svtools/res/list_add.png</property>
    <property name="icon_name">svtools/res/list_add.png</property>
    <property name="icon_size">2</property>
  </object>
  <object class="GtkImage" id="image_down">
    <property name="visible">True</property>
    <property name="can_focus">False</property>
    <property name="pixbuf">cmd/sc_movedown.png</property>
    <property name="icon_name">cmd/sc_movedown.png</property>
    <property name="icon_size">2</property>
  </object>
  <object class="GtkImage" id="image_play">
    <property name="visible">True</property>
    <property name="can_focus">False</property>
    <property name="pixbuf">sd/res/playblue_16.png</property>
    <property name="icon_name">sd/res/playblue_16.png</property>
  </object>
  <object class="GtkImage" id="image_remove">
    <property name="visible">True</property>
    <property name="can_focus">False</property>
    <property name="pixbuf">extensions/res/buttonminus.png</property>
    <property name="icon_name">extensions/res/buttonminus.png</property>
    <property name="icon_size">2</property>
  </object>
  <object class="GtkImage" id="image_up">
    <property name="visible">True</property>
    <property name="can_focus">False</property>
    <property name="pixbuf">cmd/sc_moveup.png</property>
    <property name="icon_name">cmd/sc_moveup.png</property>
    <property name="icon_size">2</property>
  </object>
  <object class="GtkTreeStore" id="liststore1">
    <columns>
      <!-- column-name expander -->
      <column type="GdkPixbuf"/>
      <!-- column-name text -->
      <column type="gchararray"/>
      <!-- column-name id -->
      <column type="gchararray"/>
    </columns>
  </object>
  <object class="GtkTreeStore" id="liststore2">
    <columns>
      <!-- column-name text -->
      <column type="gchararray"/>
      <!-- column-name id -->
      <column type="gchararray"/>
      <!-- column-name weight1 -->
      <column type="gint"/>
      <!-- column-name sensitive1 -->
      <column type="gboolean"/>
      <!-- column-name extraindent1 -->
      <column type="gint"/>
      <!-- column-name align1 -->
      <column type="gdouble"/>
    </columns>
  </object>
  <object class="GtkBox" id="CustomAnimationsPanel">
    <property name="visible">True</property>
    <property name="can_focus">False</property>
    <property name="vexpand">True</property>
    <property name="border_width">6</property>
    <property name="orientation">vertical</property>
    <property name="spacing">12</property>
    <child>
      <object class="GtkBox" id="box1">
        <property name="visible">True</property>
        <property name="can_focus">False</property>
        <property name="margin_left">6</property>
        <property name="margin_right">6</property>
        <property name="margin_top">6</property>
        <property name="margin_bottom">3</property>
        <property name="vexpand">True</property>
        <property name="orientation">vertical</property>
        <property name="spacing">3</property>
        <property name="spacing">12</property>
        <child>
          <object class="sdlo-CustomAnimationList" id="custom_animation_list">
            <property name="width_request">260</property>
          <object class="GtkBox">
            <property name="visible">True</property>
            <property name="can_focus">True</property>
            <property name="vexpand">True</property>
            <child internal-child="selection">
              <object class="GtkTreeSelection" id="Animation ListBox-selection1"/>
            <property name="can_focus">False</property>
            <child>
              <object class="GtkScrolledWindow">
                <property name="can_focus">True</property>
                <property name="hexpand">True</property>
                <property name="vexpand">True</property>
                <property name="shadow_type">in</property>
                <child>
                  <object class="GtkTreeView" id="custom_animation_list">
                    <property name="width_request">260</property>
                    <property name="can_focus">True</property>
                    <property name="receives_default">True</property>
                    <property name="no_show_all">True</property>
                    <property name="hexpand">True</property>
                    <property name="vexpand">True</property>
                    <property name="model">liststore1</property>
                    <property name="headers_visible">False</property>
                    <property name="reorderable">True</property>
                    <property name="search_column">1</property>
                    <child internal-child="selection">
                      <object class="GtkTreeSelection" id="Macro Library List-selection1"/>
                    </child>
                    <child>
                      <object class="GtkTreeViewColumn" id="treeviewcolumn2">
                        <property name="spacing">6</property>
                        <child>
                          <object class="GtkCellRendererPixbuf" id="cellrenderertext4"/>
                          <attributes>
                            <attribute name="pixbuf">0</attribute>
                          </attributes>
                        </child>
                        <child>
                          <object class="GtkCellRendererText" id="cellrenderertext2"/>
                          <attributes>
                            <attribute name="text">1</attribute>
                          </attributes>
                        </child>
                      </object>
                    </child>
                  </object>
                </child>
              </object>
              <packing>
                <property name="expand">False</property>
                <property name="fill">True</property>
                <property name="position">0</property>
              </packing>
            </child>
            <child>
              <object class="GtkScrolledWindow" id="custom_animation_label_parent">
                <property name="visible">True</property>
                <property name="can_focus">True</property>
                <property name="hexpand">True</property>
                <property name="vexpand">True</property>
                <property name="shadow_type">in</property>
                <child>
                  <object class="GtkViewport">
                    <property name="visible">True</property>
                    <property name="can_focus">False</property>
                    <child>
                      <object class="GtkLabel" id="custom_animation_label">
                        <property name="width_request">260</property>
                        <property name="visible">True</property>
                        <property name="can_focus">False</property>
                        <property name="no_show_all">True</property>
                        <property name="hexpand">True</property>
                        <property name="vexpand">True</property>
                        <property name="label" translatable="yes" context="customanimationspanel|STR_CUSTOMANIMATION_LIST_HELPTEXT">First select the slide element and then click 'Add...' to add an animation effect.</property>
                        <property name="justify">center</property>
                        <property name="wrap">True</property>
                      </object>
                    </child>
                  </object>
                </child>
              </object>
              <packing>
                <property name="expand">False</property>
                <property name="fill">True</property>
                <property name="position">1</property>
              </packing>
            </child>
          </object>
          <packing>
@@ -80,7 +181,6 @@
            <property name="visible">True</property>
            <property name="can_focus">False</property>
            <property name="halign">center</property>
            <property name="margin_top">3</property>
            <property name="spacing">12</property>
            <child>
              <object class="GtkButton" id="add_effect">
@@ -149,7 +249,6 @@
          <object class="GtkGrid" id="grid2">
            <property name="visible">True</property>
            <property name="can_focus">False</property>
            <property name="margin_bottom">3</property>
            <property name="row_spacing">6</property>
            <property name="column_spacing">6</property>
            <child>
@@ -199,11 +298,40 @@
              </packing>
            </child>
            <child>
              <object class="sdlo-CategoryListBox" id="effect_list">
              <object class="GtkScrolledWindow">
                <property name="visible">True</property>
                <property name="can_focus">True</property>
                <property name="no_show_all">True</property>
                <property name="hexpand">True</property>
                <property name="vexpand">True</property>
                <property name="shadow_type">in</property>
                <child>
                  <object class="GtkTreeView" id="effect_list">
                    <property name="visible">True</property>
                    <property name="can_focus">True</property>
                    <property name="receives_default">True</property>
                    <property name="no_show_all">True</property>
                    <property name="hexpand">True</property>
                    <property name="model">liststore2</property>
                    <property name="headers_visible">False</property>
                    <property name="search_column">0</property>
                    <property name="show_expanders">False</property>
                    <child internal-child="selection">
                      <object class="GtkTreeSelection" id="Macro Library List-selection11"/>
                    </child>
                    <child>
                      <object class="GtkTreeViewColumn" id="treeviewcolumn21">
                        <child>
                          <object class="GtkCellRendererText" id="cellrenderertext22"/>
                          <attributes>
                            <attribute name="xalign">5</attribute>
                            <attribute name="text">0</attribute>
                            <attribute name="weight">2</attribute>
                          </attributes>
                        </child>
                      </object>
                    </child>
                  </object>
                </child>
              </object>
              <packing>
                <property name="left_attach">1</property>
@@ -265,7 +393,7 @@
                    <property name="halign">end</property>
                    <property name="label" translatable="yes" context="customanimationspanel|effect_duration">D_uration:</property>
                    <property name="use_underline">True</property>
                    <property name="mnemonic_widget">anim_duration:0.00sec</property>
                    <property name="mnemonic_widget">anim_duration</property>
                  </object>
                  <packing>
                    <property name="left_attach">0</property>
@@ -295,16 +423,7 @@
                    <property name="hexpand">True</property>
                    <property name="spacing">6</property>
                    <child>
                      <object class="sdlo-PropertyControl" id="effect_property_list">
                        <property name="visible">True</property>
                        <property name="can_focus">True</property>
                        <property name="hexpand">True</property>
                      </object>
                      <packing>
                        <property name="expand">False</property>
                        <property name="fill">True</property>
                        <property name="position">0</property>
                      </packing>
                      <placeholder/>
                    </child>
                  </object>
                  <packing>
@@ -327,10 +446,12 @@
                  </packing>
                </child>
                <child>
                  <object class="VclComboBoxNumeric" id="anim_duration:0.00sec">
                  <object class="GtkSpinButton" id="anim_duration">
                    <property name="visible">True</property>
                    <property name="can_focus">True</property>
                    <property name="hexpand">True</property>
                    <property name="adjustment">adjustment2</property>
                    <property name="digits">2</property>
                  </object>
                  <packing>
                    <property name="left_attach">1</property>
@@ -344,7 +465,7 @@
                    <property name="halign">end</property>
                    <property name="label" translatable="yes" context="customanimationspanel|delay_label">_Delay:</property>
                    <property name="use_underline">True</property>
                    <property name="mnemonic_widget">delay_value:0.0sec</property>
                    <property name="mnemonic_widget">delay_value</property>
                  </object>
                  <packing>
                    <property name="left_attach">0</property>
@@ -352,7 +473,7 @@
                  </packing>
                </child>
                <child>
                  <object class="GtkSpinButton" id="delay_value:0.0sec">
                  <object class="GtkSpinButton" id="delay_value">
                    <property name="visible">True</property>
                    <property name="can_focus">True</property>
                    <property name="hexpand">True</property>
@@ -398,13 +519,11 @@
            <property name="visible">True</property>
            <property name="can_focus">False</property>
            <property name="orientation">vertical</property>
            <property name="spacing">6</property>
            <child>
              <object class="GtkSeparator" id="separator1">
                <property name="height_request">2</property>
                <property name="visible">True</property>
                <property name="can_focus">False</property>
                <property name="margin_top">3</property>
                <property name="margin_bottom">3</property>
              </object>
              <packing>
                <property name="expand">False</property>
@@ -416,9 +535,6 @@
              <object class="GtkBox" id="box4">
                <property name="visible">True</property>
                <property name="can_focus">False</property>
                <property name="margin_left">6</property>
                <property name="margin_right">6</property>
                <property name="margin_top">3</property>
                <property name="spacing">18</property>
                <child>
                  <object class="GtkCheckButton" id="auto_preview">
@@ -426,7 +542,6 @@
                    <property name="visible">True</property>
                    <property name="can_focus">True</property>
                    <property name="receives_default">False</property>
                    <property name="margin_left">6</property>
                    <property name="use_underline">True</property>
                    <property name="xalign">0</property>
                    <property name="draw_indicator">True</property>
@@ -445,6 +560,7 @@
                    <property name="receives_default">True</property>
                    <property name="tooltip_text" translatable="yes" context="customanimationspanel|play|tooltip_text">Preview Effect</property>
                    <property name="image">image_play</property>
                    <property name="use_underline">True</property>
                    <property name="image_position">right</property>
                    <property name="always_show_image">True</property>
                  </object>
diff --git a/sd/uiconfig/simpress/ui/customanimationtimingtab.ui b/sd/uiconfig/simpress/ui/customanimationtimingtab.ui
index ec45713..318b731 100644
--- a/sd/uiconfig/simpress/ui/customanimationtimingtab.ui
+++ b/sd/uiconfig/simpress/ui/customanimationtimingtab.ui
@@ -1,16 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.1 -->
<!-- Generated with glade 3.22.2 -->
<interface domain="sd">
  <requires lib="gtk+" version="3.18"/>
  <object class="GtkAdjustment" id="adjustment1">
    <property name="upper">1000</property>
    <property name="step_increment">1</property>
    <property name="page_increment">10</property>
    <property name="upper">100000</property>
    <property name="step_increment">0.5</property>
    <property name="page_increment">5</property>
  </object>
  <object class="GtkAdjustment" id="adjustment2">
    <property name="upper">1000</property>
    <property name="step_increment">1</property>
    <property name="page_increment">10</property>
    <property name="upper">100000</property>
    <property name="step_increment">0.5</property>
    <property name="page_increment">5</property>
  </object>
  <object class="GtkBox" id="TimingTab">
    <property name="visible">True</property>
diff --git a/sd/uiconfig/simpress/ui/effectmenu.ui b/sd/uiconfig/simpress/ui/effectmenu.ui
index 9d26107..2233510 100644
--- a/sd/uiconfig/simpress/ui/effectmenu.ui
+++ b/sd/uiconfig/simpress/ui/effectmenu.ui
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.20.0 -->
<!-- Generated with glade 3.22.2 -->
<interface domain="sd">
  <requires lib="gtk+" version="3.18"/>
  <object class="GtkMenu" id="menu">
@@ -11,6 +11,8 @@
        <property name="can_focus">False</property>
        <property name="label" translatable="yes" context="effectmenu|onclick">Start On _Click</property>
        <property name="use_underline">True</property>
        <property name="active">True</property>
        <property name="draw_as_radio">True</property>
      </object>
    </child>
    <child>
@@ -19,6 +21,8 @@
        <property name="can_focus">False</property>
        <property name="label" translatable="yes" context="effectmenu|withprev">Start _With Previous</property>
        <property name="use_underline">True</property>
        <property name="draw_as_radio">True</property>
        <property name="group">onclick</property>
      </object>
    </child>
    <child>
@@ -26,6 +30,9 @@
        <property name="visible">True</property>
        <property name="can_focus">False</property>
        <property name="label" translatable="yes" context="effectmenu|afterprev">Start _After Previous</property>
        <property name="use_underline">True</property>
        <property name="draw_as_radio">True</property>
        <property name="group">onclick</property>
      </object>
    </child>
    <child>
@@ -55,6 +62,7 @@
        <property name="visible">True</property>
        <property name="can_focus">False</property>
        <property name="label" translatable="yes" context="effectmenu|remove">_Remove</property>
        <property name="use_underline">True</property>
      </object>
    </child>
  </object>
diff --git a/sd/uiconfig/simpress/ui/slidetransitionspanel.ui b/sd/uiconfig/simpress/ui/slidetransitionspanel.ui
index 126ba98..d51dcaf 100644
--- a/sd/uiconfig/simpress/ui/slidetransitionspanel.ui
+++ b/sd/uiconfig/simpress/ui/slidetransitionspanel.ui
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.1 -->
<!-- Generated with glade 3.22.2 -->
<interface domain="sd">
  <requires lib="gtk+" version="3.18"/>
  <object class="GtkAdjustment" id="adjustment1">
@@ -36,35 +36,24 @@
            <property name="can_focus">False</property>
            <property name="vexpand">True</property>
            <child>
              <object class="GtkAlignment">
              <object class="GtkScrolledWindow" id="transitions_iconswin">
                <property name="visible">True</property>
                <property name="can_focus">False</property>
                <property name="can_focus">True</property>
                <property name="hexpand">True</property>
                <property name="vexpand">True</property>
                <property name="top_padding">6</property>
                <property name="left_padding">6</property>
                <property name="right_padding">6</property>
                <property name="hscrollbar_policy">never</property>
                <property name="shadow_type">in</property>
                <child>
                  <object class="GtkScrolledWindow" id="transitions_iconswin">
                  <object class="GtkViewport">
                    <property name="visible">True</property>
                    <property name="can_focus">True</property>
                    <property name="hexpand">True</property>
                    <property name="vexpand">True</property>
                    <property name="hscrollbar_policy">never</property>
                    <property name="shadow_type">in</property>
                    <property name="can_focus">False</property>
                    <child>
                      <object class="GtkViewport">
                      <object class="GtkDrawingArea" id="transitions_icons">
                        <property name="visible">True</property>
                        <property name="can_focus">False</property>
                        <child>
                          <object class="GtkDrawingArea" id="transitions_icons">
                            <property name="visible">True</property>
                            <property name="can_focus">True</property>
                            <property name="events">GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | GDK_STRUCTURE_MASK</property>
                            <property name="hexpand">True</property>
                            <property name="vexpand">True</property>
                          </object>
                        </child>
                        <property name="can_focus">True</property>
                        <property name="events">GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | GDK_STRUCTURE_MASK</property>
                        <property name="hexpand">True</property>
                        <property name="vexpand">True</property>
                      </object>
                    </child>
                  </object>
@@ -360,7 +349,6 @@
              <object class="GtkBox" id="box3">
                <property name="visible">True</property>
                <property name="can_focus">False</property>
                <property name="margin_left">12</property>
                <property name="spacing">18</property>
                <child>
                  <object class="GtkCheckButton" id="auto_preview">
@@ -384,7 +372,9 @@
                    <property name="visible">True</property>
                    <property name="can_focus">True</property>
                    <property name="receives_default">True</property>
                    <property name="tooltip_text" translatable="yes" context="slidetransitionspanel|play|tooltip_text">Preview Effect</property>
                    <property name="image">image1</property>
                    <property name="use_underline">True</property>
                    <property name="image_position">right</property>
                    <property name="always_show_image">True</property>
                  </object>
diff --git a/solenv/clang-format/blacklist b/solenv/clang-format/blacklist
index ad9b37d..3744ab4 100644
--- a/solenv/clang-format/blacklist
+++ b/solenv/clang-format/blacklist
@@ -11151,8 +11151,6 @@ sd/source/ui/accessibility/AccessibleSlideSorterObject.cxx
sd/source/ui/accessibility/AccessibleSlideSorterView.cxx
sd/source/ui/accessibility/AccessibleViewForwarder.cxx
sd/source/ui/accessibility/SdShapeTypes.cxx
sd/source/ui/animations/CategoryListBox.cxx
sd/source/ui/animations/CategoryListBox.hxx
sd/source/ui/animations/CustomAnimationDialog.cxx
sd/source/ui/animations/CustomAnimationDialog.hxx
sd/source/ui/animations/CustomAnimationList.cxx
diff --git a/solenv/sanitizers/ui/modules/simpress.suppr b/solenv/sanitizers/ui/modules/simpress.suppr
index 6e3de94..67cefb2 100644
--- a/solenv/sanitizers/ui/modules/simpress.suppr
+++ b/solenv/sanitizers/ui/modules/simpress.suppr
@@ -1,4 +1,3 @@
sd/uiconfig/simpress/ui/customanimationspanel.ui://sdlo-PropertyControl[@id='effect_property_list'] no-labelled-by
sd/uiconfig/simpress/ui/customanimationfragment.ui://GtkSpinButton[@id='fontsize'] no-labelled-by
sd/uiconfig/simpress/ui/customanimationfragment.ui://GtkSpinButton[@id='rotate'] no-labelled-by
sd/uiconfig/simpress/ui/customanimationfragment.ui://GtkSpinButton[@id='transparent'] no-labelled-by