1245 lines
43 KiB
C++
1245 lines
43 KiB
C++
/* -*- 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 <com/sun/star/document/XActionLockable.hpp>
|
|
#include <com/sun/star/drawing/XDrawPage.hpp>
|
|
#include <com/sun/star/drawing/XShapes.hpp>
|
|
#include <com/sun/star/presentation/ShapeAnimationSubType.hpp>
|
|
#include <com/sun/star/presentation/EffectNodeType.hpp>
|
|
#include <com/sun/star/presentation/ParagraphTarget.hpp>
|
|
#include <com/sun/star/container/XEnumerationAccess.hpp>
|
|
#include <com/sun/star/presentation/EffectPresetClass.hpp>
|
|
#include <com/sun/star/presentation/EffectCommands.hpp>
|
|
#include <com/sun/star/text/XTextRange.hpp>
|
|
#include <com/sun/star/beans/XPropertySet.hpp>
|
|
#include <comphelper/scopeguard.hxx>
|
|
#include <CustomAnimationList.hxx>
|
|
#include <CustomAnimationPreset.hxx>
|
|
#include <utility>
|
|
#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 <comphelper/diagnose_ex.hxx>
|
|
|
|
#include <sdresid.hxx>
|
|
|
|
#include <strings.hrc>
|
|
#include <bitmaps.hlst>
|
|
|
|
#include <algorithm>
|
|
#include <memory>
|
|
|
|
using namespace ::com::sun::star;
|
|
using namespace ::com::sun::star::presentation;
|
|
|
|
using ::com::sun::star::uno::UNO_QUERY;
|
|
using ::com::sun::star::uno::UNO_QUERY_THROW;
|
|
using ::com::sun::star::uno::Any;
|
|
using ::com::sun::star::uno::Reference;
|
|
using ::com::sun::star::uno::Exception;
|
|
using ::com::sun::star::uno::XInterface;
|
|
using ::com::sun::star::text::XTextRange;
|
|
using ::com::sun::star::drawing::XShape;
|
|
using ::com::sun::star::drawing::XShapes;
|
|
using ::com::sun::star::drawing::XDrawPage;
|
|
using ::com::sun::star::container::XChild;
|
|
using ::com::sun::star::container::XIndexAccess;
|
|
using ::com::sun::star::container::XEnumerationAccess;
|
|
using ::com::sun::star::container::XEnumeration;
|
|
using ::com::sun::star::beans::XPropertySet;
|
|
using ::com::sun::star::beans::XPropertySetInfo;
|
|
|
|
namespace sd {
|
|
|
|
// go recursively through all shapes in the given XShapes collection and return true as soon as the
|
|
// given shape is found. nIndex is incremented for each shape with the same shape type as the given
|
|
// shape is found until the given shape is found.
|
|
static bool getShapeIndex( const Reference< XShapes >& xShapes, const Reference< XShape >& xShape, sal_Int32& nIndex )
|
|
{
|
|
const sal_Int32 nCount = xShapes->getCount();
|
|
sal_Int32 n;
|
|
for( n = 0; n < nCount; n++ )
|
|
{
|
|
Reference< XShape > xChild;
|
|
xShapes->getByIndex( n ) >>= xChild;
|
|
if( xChild == xShape )
|
|
return true;
|
|
|
|
if( xChild->getShapeType() == xShape->getShapeType() )
|
|
nIndex++;
|
|
|
|
Reference< XShapes > xChildContainer( xChild, UNO_QUERY );
|
|
if( xChildContainer.is() )
|
|
{
|
|
if( getShapeIndex( xChildContainer, xShape, nIndex ) )
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// returns the index of the shape type from the given shape
|
|
static sal_Int32 getShapeIndex( const Reference< XShape >& xShape )
|
|
{
|
|
Reference< XChild > xChild( xShape, UNO_QUERY );
|
|
Reference< XShapes > xPage;
|
|
|
|
while( xChild.is() && !xPage.is() )
|
|
{
|
|
Reference< XInterface > x( xChild->getParent() );
|
|
xChild.set( x, UNO_QUERY );
|
|
Reference< XDrawPage > xTestPage( x, UNO_QUERY );
|
|
if( xTestPage.is() )
|
|
xPage.set( x, UNO_QUERY );
|
|
}
|
|
|
|
sal_Int32 nIndex = 1;
|
|
|
|
if( xPage.is() && getShapeIndex( xPage, xShape, nIndex ) )
|
|
return nIndex;
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
OUString getShapeDescription( const Reference< XShape >& xShape, bool bWithText )
|
|
{
|
|
OUString aDescription;
|
|
Reference< XPropertySet > xSet( xShape, UNO_QUERY );
|
|
bool bAppendIndex = true;
|
|
|
|
if(xSet.is()) try
|
|
{
|
|
Reference<XPropertySetInfo> xInfo(xSet->getPropertySetInfo());
|
|
if (xInfo.is())
|
|
{
|
|
static constexpr OUString aPropName1(u"Name"_ustr);
|
|
if(xInfo->hasPropertyByName(aPropName1))
|
|
xSet->getPropertyValue(aPropName1) >>= aDescription;
|
|
|
|
bAppendIndex = aDescription.isEmpty();
|
|
|
|
static constexpr OUString aPropName2(u"UINameSingular"_ustr);
|
|
if(xInfo->hasPropertyByName(aPropName2))
|
|
xSet->getPropertyValue(aPropName2) >>= aDescription;
|
|
}
|
|
}
|
|
catch( Exception& )
|
|
{
|
|
TOOLS_WARN_EXCEPTION( "sd", "sd::getShapeDescription()" );
|
|
}
|
|
|
|
if (bAppendIndex)
|
|
{
|
|
aDescription += " " + OUString::number(getShapeIndex(xShape));
|
|
}
|
|
|
|
if( bWithText )
|
|
{
|
|
Reference< XTextRange > xText( xShape, UNO_QUERY );
|
|
if( xText.is() )
|
|
{
|
|
OUString aText( xText->getString() );
|
|
if( !aText.isEmpty() )
|
|
{
|
|
aDescription += ": ";
|
|
|
|
aText = aText.replace( '\n', ' ' );
|
|
aText = aText.replace( '\r', ' ' );
|
|
|
|
aDescription += aText;
|
|
}
|
|
}
|
|
}
|
|
return aDescription;
|
|
}
|
|
|
|
static OUString getDescription( const Any& rTarget, bool bWithText )
|
|
{
|
|
OUString aDescription;
|
|
|
|
if( rTarget.getValueType() == ::cppu::UnoType<ParagraphTarget>::get() )
|
|
{
|
|
ParagraphTarget aParaTarget;
|
|
rTarget >>= aParaTarget;
|
|
|
|
css::uno::Reference<css::document::XActionLockable> xLockable(aParaTarget.Shape, css::uno::UNO_QUERY);
|
|
if (xLockable.is())
|
|
xLockable->addActionLock();
|
|
comphelper::ScopeGuard aGuard([&xLockable]()
|
|
{
|
|
if (xLockable.is())
|
|
xLockable->removeActionLock();
|
|
});
|
|
|
|
Reference< XEnumerationAccess > xText( aParaTarget.Shape, UNO_QUERY_THROW );
|
|
Reference< XEnumeration > xEnumeration( xText->createEnumeration(), css::uno::UNO_SET_THROW );
|
|
sal_Int32 nPara = aParaTarget.Paragraph;
|
|
|
|
while( xEnumeration->hasMoreElements() && nPara )
|
|
{
|
|
xEnumeration->nextElement();
|
|
nPara--;
|
|
}
|
|
|
|
DBG_ASSERT( xEnumeration->hasMoreElements(), "sd::CustomAnimationEffect::prepareText(), paragraph out of range!" );
|
|
|
|
if( xEnumeration->hasMoreElements() )
|
|
{
|
|
Reference< XTextRange > xParagraph;
|
|
xEnumeration->nextElement() >>= xParagraph;
|
|
|
|
if( xParagraph.is() )
|
|
aDescription = xParagraph->getString();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Reference< XShape > xShape;
|
|
rTarget >>= xShape;
|
|
if( xShape.is() )
|
|
aDescription = getShapeDescription( xShape, bWithText );
|
|
}
|
|
|
|
return aDescription;
|
|
}
|
|
|
|
class CustomAnimationListEntryItem
|
|
{
|
|
public:
|
|
CustomAnimationListEntryItem(OUString aDescription,
|
|
CustomAnimationEffectPtr pEffect);
|
|
const CustomAnimationEffectPtr& getEffect() const { return mpEffect; }
|
|
|
|
Size GetSize(const 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:
|
|
OUString msDescription;
|
|
OUString msEffectName;
|
|
CustomAnimationEffectPtr mpEffect;
|
|
|
|
public:
|
|
static const ::tools::Long nIconWidth = 19;
|
|
static const ::tools::Long nItemMinHeight = 38;
|
|
};
|
|
|
|
CustomAnimationListEntryItem::CustomAnimationListEntryItem(OUString aDescription, CustomAnimationEffectPtr pEffect)
|
|
: msDescription(std::move(aDescription))
|
|
, mpEffect(std::move(pEffect))
|
|
{
|
|
if (!mpEffect)
|
|
return;
|
|
switch (mpEffect->getPresetClass())
|
|
{
|
|
case EffectPresetClass::ENTRANCE:
|
|
msEffectName = SdResId(STR_CUSTOMANIMATION_ENTRANCE); break;
|
|
case EffectPresetClass::EXIT:
|
|
msEffectName = SdResId(STR_CUSTOMANIMATION_EXIT); break;
|
|
case EffectPresetClass::EMPHASIS:
|
|
msEffectName = SdResId(STR_CUSTOMANIMATION_EMPHASIS); break;
|
|
case EffectPresetClass::MOTIONPATH:
|
|
msEffectName = SdResId(STR_CUSTOMANIMATION_MOTION_PATHS); break;
|
|
default:
|
|
msEffectName = SdResId(STR_CUSTOMANIMATION_MISC); break;
|
|
}
|
|
msEffectName = msEffectName.replaceFirst( "%1" , CustomAnimationPresets::getCustomAnimationPresets().getUINameForPresetId(mpEffect->getPresetId()));
|
|
}
|
|
|
|
IMPL_STATIC_LINK(CustomAnimationList, CustomRenderHdl, weld::TreeView::render_args, aPayload, void)
|
|
{
|
|
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);
|
|
|
|
CustomAnimationListEntryItem* pItem = weld::fromId<CustomAnimationListEntryItem*>(rId);
|
|
|
|
pItem->Paint(rRenderContext, rRect, bSelected);
|
|
}
|
|
|
|
IMPL_STATIC_LINK(CustomAnimationList, CustomGetSizeHdl, weld::TreeView::get_size_args, aPayload, Size)
|
|
{
|
|
vcl::RenderContext& rRenderContext = aPayload.first;
|
|
const OUString& rId = aPayload.second;
|
|
|
|
CustomAnimationListEntryItem* pItem = weld::fromId<CustomAnimationListEntryItem*>(rId);
|
|
if (!pItem)
|
|
return Size(CustomAnimationListEntryItem::nIconWidth, CustomAnimationListEntryItem::nItemMinHeight);
|
|
return pItem->GetSize(rRenderContext);
|
|
}
|
|
|
|
Size CustomAnimationListEntryItem::GetSize(const 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() * 2);
|
|
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);
|
|
|
|
rRenderContext.Push();
|
|
rRenderContext.SetFillColor(rRenderContext.GetSettings().GetStyleSettings().GetDialogColor());
|
|
rRenderContext.SetLineColor();
|
|
// fill the background with the dialog bg color
|
|
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 );
|
|
|
|
// Draw the text with the dialog text color
|
|
rRenderContext.SetTextColor(rRenderContext.GetSettings().GetStyleSettings().GetDialogTextColor());
|
|
rRenderContext.DrawText(aOutRect, rRenderContext.GetEllipsisString(msDescription, aOutRect.GetWidth()));
|
|
rRenderContext.Pop();
|
|
}
|
|
|
|
void CustomAnimationListEntryItem::PaintEffect(vcl::RenderContext& rRenderContext, const ::tools::Rectangle& rRect, bool bSelected)
|
|
{
|
|
rRenderContext.Push(vcl::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();
|
|
|
|
Size nImageSize = Image(StockImage::Yes, BMP_CUSTOMANIMATION_AFTER_PREVIOUS).GetSizePixel();
|
|
|
|
sal_Int16 nNodeType = mpEffect->getNodeType();
|
|
if (nNodeType == EffectNodeType::ON_CLICK)
|
|
{
|
|
Image aImage(Image(StockImage::Yes, BMP_CUSTOMANIMATION_ON_CLICK));
|
|
nImageSize = aImage.GetSizePixel();
|
|
aPos.AdjustY(nItemHeight / 4 - nImageSize.Height() / 2);
|
|
rRenderContext.DrawImage(aPos, aImage);
|
|
}
|
|
else if (nNodeType == EffectNodeType::AFTER_PREVIOUS)
|
|
{
|
|
Image aImage(Image(StockImage::Yes, BMP_CUSTOMANIMATION_AFTER_PREVIOUS));
|
|
nImageSize = aImage.GetSizePixel();
|
|
aPos.AdjustY(nItemHeight / 4 - nImageSize.Height() / 2);
|
|
rRenderContext.DrawImage(aPos, aImage);
|
|
}
|
|
else if (nNodeType == EffectNodeType::WITH_PREVIOUS)
|
|
{
|
|
//FIXME With previous image not defined in CustomAnimation.src
|
|
}
|
|
|
|
aPos = rRect.TopLeft();
|
|
aPos.AdjustX(nImageSize.Width() + 5);
|
|
|
|
//TODO, full width of widget ?
|
|
rRenderContext.DrawText(aPos, rRenderContext.GetEllipsisString(msDescription, rRect.GetWidth()));
|
|
|
|
OUString sImage;
|
|
switch (mpEffect->getPresetClass())
|
|
{
|
|
case EffectPresetClass::ENTRANCE:
|
|
sImage = BMP_CUSTOMANIMATION_ENTRANCE_EFFECT; break;
|
|
case EffectPresetClass::EXIT:
|
|
sImage = BMP_CUSTOMANIMATION_EXIT_EFFECT; break;
|
|
case EffectPresetClass::EMPHASIS:
|
|
sImage = BMP_CUSTOMANIMATION_EMPHASIS_EFFECT; break;
|
|
case EffectPresetClass::MOTIONPATH:
|
|
sImage = BMP_CUSTOMANIMATION_MOTION_PATH; break;
|
|
case EffectPresetClass::OLEACTION:
|
|
sImage = BMP_CUSTOMANIMATION_OLE; break;
|
|
case EffectPresetClass::MEDIACALL:
|
|
switch (mpEffect->getCommand())
|
|
{
|
|
case EffectCommands::TOGGLEPAUSE:
|
|
sImage = BMP_CUSTOMANIMATION_MEDIA_PAUSE; break;
|
|
case EffectCommands::STOP:
|
|
sImage = BMP_CUSTOMANIMATION_MEDIA_STOP; break;
|
|
case EffectCommands::PLAY:
|
|
default:
|
|
sImage = BMP_CUSTOMANIMATION_MEDIA_PLAY; break;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (!sImage.isEmpty())
|
|
{
|
|
Image aImage(StockImage::Yes, sImage);
|
|
nImageSize = aImage.GetSizePixel();
|
|
Point aImagePos(aPos);
|
|
aImagePos.AdjustY(nItemHeight * 3 / 4 - nImageSize.Height() / 2);
|
|
rRenderContext.DrawImage(aImagePos, aImage);
|
|
}
|
|
else
|
|
{
|
|
Image aImage(StockImage::Yes, BMP_CUSTOMANIMATION_ENTRANCE_EFFECT);
|
|
nImageSize = aImage.GetSizePixel();
|
|
}
|
|
|
|
aPos.AdjustX(nImageSize.Width() + 5);
|
|
aPos.AdjustY(nItemHeight / 2);
|
|
|
|
rRenderContext.DrawText(aPos, rRenderContext.GetEllipsisString(msEffectName, rRect.GetWidth()));
|
|
rRenderContext.Pop();
|
|
}
|
|
|
|
void CustomAnimationListEntryItem::Paint(vcl::RenderContext& rRenderContext, const ::tools::Rectangle& rRect, bool bSelected)
|
|
{
|
|
if (mpEffect)
|
|
PaintEffect(rRenderContext, rRect, bSelected);
|
|
else
|
|
PaintTrigger(rRenderContext, rRect);
|
|
}
|
|
|
|
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)
|
|
, mnPostExpandEvent(nullptr)
|
|
, mnPostCollapseEvent(nullptr)
|
|
{
|
|
mxEmptyLabel->set_stack_background();
|
|
|
|
mxTreeView->set_selection_mode(SelectionMode::Multiple);
|
|
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));
|
|
mxTreeView->set_column_custom_renderer(1, true);
|
|
}
|
|
|
|
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, true);
|
|
}
|
|
|
|
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.
|
|
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();
|
|
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)
|
|
mxDndEffectDragging = mxTreeView->make_iterator();
|
|
if (!mxTreeView->get_cursor(mxDndEffectDragging.get()))
|
|
mxDndEffectDragging.reset();
|
|
|
|
// Allow normal processing.
|
|
return false;
|
|
}
|
|
|
|
// D'n'D #3: Called each time mouse moves during drag
|
|
sal_Int8 CustomAnimationList::AcceptDrop( const AcceptDropEvent& rEvt )
|
|
{
|
|
sal_Int8 ret = DND_ACTION_NONE;
|
|
|
|
const bool bIsMove = DND_ACTION_MOVE == rEvt.mnAction;
|
|
if (mxDndEffectDragging && !rEvt.mbLeaving && bIsMove)
|
|
ret = DND_ACTION_MOVE;
|
|
return ret;
|
|
}
|
|
|
|
// D'n'D #5: Tell model to update effect order.
|
|
sal_Int8 CustomAnimationList::ExecuteDrop(const ExecuteDropEvent& rEvt)
|
|
{
|
|
std::unique_ptr<weld::TreeIter> xDndEffectInsertBefore(mxTreeView->make_iterator());
|
|
if (!mxTreeView->get_dest_row_at_pos(rEvt.maPosPixel, xDndEffectInsertBefore.get(), true))
|
|
xDndEffectInsertBefore.reset();
|
|
|
|
const bool bMovingEffect = ( mxDndEffectDragging != nullptr );
|
|
const bool bMoveNotSelf = !xDndEffectInsertBefore || (mxDndEffectDragging && mxTreeView->iter_compare(*xDndEffectInsertBefore, *mxDndEffectDragging) != 0);
|
|
const bool bHaveSequence(mpMainSequence);
|
|
|
|
if( bMovingEffect && bMoveNotSelf && bHaveSequence )
|
|
{
|
|
CustomAnimationListEntryItem* pTarget = xDndEffectInsertBefore ?
|
|
weld::fromId<CustomAnimationListEntryItem*>(mxTreeView->get_id(*xDndEffectInsertBefore)) :
|
|
nullptr;
|
|
|
|
// Build list of effects
|
|
std::vector< CustomAnimationEffectPtr > aEffects;
|
|
for( const auto &pEntry : mDndEffectsSelected )
|
|
{
|
|
CustomAnimationListEntryItem* pCustomAnimationEffect = weld::fromId<CustomAnimationListEntryItem*>(mxTreeView->get_id(*pEntry));
|
|
aEffects.push_back(pCustomAnimationEffect->getEffect());
|
|
}
|
|
|
|
// Callback to observer to have it update the model.
|
|
// If pTarget is null, pass nullptr to indicate end of list.
|
|
mpController->onDragNDropComplete(
|
|
std::move(aEffects),
|
|
pTarget ? pTarget->getEffect() : nullptr );
|
|
|
|
// Reset selection
|
|
mxTreeView->select(*mxDndEffectDragging);
|
|
Select();
|
|
}
|
|
|
|
// NOTE: Don't call default handler because all required
|
|
// move operations have been completed here to update the model.
|
|
return DND_ACTION_NONE;
|
|
}
|
|
|
|
CustomAnimationList::~CustomAnimationList()
|
|
{
|
|
if (mnPostExpandEvent)
|
|
{
|
|
Application::RemoveUserEvent(mnPostExpandEvent);
|
|
mnPostExpandEvent = nullptr;
|
|
}
|
|
|
|
if (mnPostCollapseEvent)
|
|
{
|
|
Application::RemoveUserEvent(mnPostCollapseEvent);
|
|
mnPostCollapseEvent = nullptr;
|
|
}
|
|
|
|
if( mpMainSequence )
|
|
mpMainSequence->removeListener( this );
|
|
|
|
clear();
|
|
}
|
|
|
|
IMPL_LINK(CustomAnimationList, KeyInputHdl, const KeyEvent&, rKEvt, bool)
|
|
{
|
|
const int nKeyCode = rKEvt.GetKeyCode().GetCode();
|
|
switch (nKeyCode)
|
|
{
|
|
case KEY_DELETE:
|
|
mpController->onContextMenu(u"remove"_ustr);
|
|
return true;
|
|
case KEY_INSERT:
|
|
mpController->onContextMenu(u"create"_ustr);
|
|
return true;
|
|
case KEY_SPACE:
|
|
{
|
|
std::unique_ptr<weld::TreeIter> xEntry = mxTreeView->make_iterator();
|
|
if (mxTreeView->get_cursor(xEntry.get()))
|
|
{
|
|
auto aRect = mxTreeView->get_row_area(*xEntry);
|
|
const Point aPos(aRect.getOpenWidth() / 2, aRect.getOpenHeight() / 2);
|
|
const CommandEvent aCEvt(aPos, CommandEventId::ContextMenu);
|
|
CommandHdl(aCEvt);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/** selects or deselects the given effect.
|
|
Selections of other effects are not changed */
|
|
void CustomAnimationList::select( const CustomAnimationEffectPtr& pEffect )
|
|
{
|
|
CustomAnimationListEntryItem* pEntry = nullptr;
|
|
|
|
std::unique_ptr<weld::TreeIter> xEntry = mxTreeView->make_iterator();
|
|
if (mxTreeView->get_iter_first(*xEntry))
|
|
{
|
|
do
|
|
{
|
|
CustomAnimationListEntryItem* pTestEntry = weld::fromId<CustomAnimationListEntryItem*>(mxTreeView->get_id(*xEntry));
|
|
if (pTestEntry->getEffect() == pEffect)
|
|
{
|
|
mxTreeView->select(*xEntry);
|
|
mxTreeView->scroll_to_row(*xEntry);
|
|
pEntry = pTestEntry;
|
|
break;
|
|
}
|
|
} while (mxTreeView->iter_next(*xEntry));
|
|
}
|
|
|
|
if( !pEntry )
|
|
{
|
|
append( pEffect );
|
|
select( pEffect );
|
|
}
|
|
}
|
|
|
|
void CustomAnimationList::clear()
|
|
{
|
|
mxEntries.clear();
|
|
mxTreeView->clear();
|
|
|
|
mxEmptyLabelParent->show();
|
|
mxTreeView->hide();
|
|
|
|
mxLastParentEntry.reset();
|
|
mxLastTargetShape = nullptr;
|
|
}
|
|
|
|
void CustomAnimationList::update( const MainSequencePtr& pMainSequence )
|
|
{
|
|
if( mpMainSequence )
|
|
mpMainSequence->removeListener( this );
|
|
|
|
mpMainSequence = pMainSequence;
|
|
update();
|
|
|
|
if( mpMainSequence )
|
|
mpMainSequence->addListener( this );
|
|
}
|
|
|
|
struct stl_append_effect_func
|
|
{
|
|
explicit stl_append_effect_func( CustomAnimationList& rList ) : mrList( rList ) {}
|
|
void operator()(const CustomAnimationEffectPtr& pEffect);
|
|
CustomAnimationList& mrList;
|
|
};
|
|
|
|
void stl_append_effect_func::operator()(const CustomAnimationEffectPtr& pEffect)
|
|
{
|
|
mrList.append( pEffect );
|
|
}
|
|
|
|
void CustomAnimationList::update()
|
|
{
|
|
mbIgnorePaint = true;
|
|
|
|
std::vector< CustomAnimationEffectPtr > aVisible;
|
|
std::vector< CustomAnimationEffectPtr > aSelected;
|
|
CustomAnimationEffectPtr aCurrent;
|
|
|
|
CustomAnimationEffectPtr pFirstSelEffect;
|
|
CustomAnimationEffectPtr pLastSelEffect;
|
|
::tools::Long nFirstVis = -1;
|
|
::tools::Long nLastVis = -1;
|
|
::tools::Long nFirstSelOld = -1;
|
|
::tools::Long nLastSelOld = -1;
|
|
|
|
std::unique_ptr<weld::TreeIter> xEntry = mxTreeView->make_iterator();
|
|
|
|
if( mpMainSequence )
|
|
{
|
|
std::unique_ptr<weld::TreeIter> xLastSelectedEntry;
|
|
std::unique_ptr<weld::TreeIter> xLastVisibleEntry;
|
|
|
|
// save selection, current, and expand (visible) states
|
|
mxTreeView->all_foreach([this, &aVisible, &nFirstVis, &xLastVisibleEntry,
|
|
&aSelected, &nFirstSelOld, &pFirstSelEffect, &xLastSelectedEntry](weld::TreeIter& rEntry){
|
|
CustomAnimationListEntryItem* pEntry = weld::fromId<CustomAnimationListEntryItem*>(mxTreeView->get_id(rEntry));
|
|
CustomAnimationEffectPtr pEffect(pEntry->getEffect());
|
|
if (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 (mxTreeView->is_selected(rEntry))
|
|
{
|
|
aSelected.push_back(pEffect);
|
|
if (nFirstSelOld == -1)
|
|
{
|
|
pFirstSelEffect = std::move(pEffect);
|
|
nFirstSelOld = weld::GetAbsPos(*mxTreeView, rEntry);
|
|
}
|
|
if (!xLastSelectedEntry)
|
|
xLastSelectedEntry = mxTreeView->make_iterator(&rEntry);
|
|
else
|
|
mxTreeView->copy_iterator(rEntry, *xLastSelectedEntry);
|
|
}
|
|
}
|
|
|
|
return false;
|
|
});
|
|
|
|
if (xLastSelectedEntry)
|
|
{
|
|
CustomAnimationListEntryItem* pEntry = weld::fromId<CustomAnimationListEntryItem*>(mxTreeView->get_id(*xLastSelectedEntry));
|
|
pLastSelEffect = pEntry->getEffect();
|
|
nLastSelOld = weld::GetAbsPos(*mxTreeView, *xLastSelectedEntry);
|
|
}
|
|
|
|
if (xLastVisibleEntry)
|
|
nLastVis = weld::GetAbsPos(*mxTreeView, *xLastVisibleEntry);
|
|
|
|
if (mxTreeView->get_cursor(xEntry.get()))
|
|
{
|
|
CustomAnimationListEntryItem* pEntry = weld::fromId<CustomAnimationListEntryItem*>(mxTreeView->get_id(*xEntry));
|
|
aCurrent = pEntry->getEffect();
|
|
}
|
|
}
|
|
|
|
// rebuild list
|
|
|
|
mxTreeView->freeze();
|
|
|
|
clear();
|
|
|
|
if (mpMainSequence)
|
|
{
|
|
std::for_each( mpMainSequence->getBegin(), mpMainSequence->getEnd(), stl_append_effect_func( *this ) );
|
|
mxLastParentEntry.reset();
|
|
|
|
auto rInteractiveSequenceVector = mpMainSequence->getInteractiveSequenceVector();
|
|
|
|
for (InteractiveSequencePtr const& pIS : rInteractiveSequenceVector)
|
|
{
|
|
Reference< XShape > xShape( pIS->getTriggerShape() );
|
|
if( xShape.is() )
|
|
{
|
|
OUString aDescription = SdResId(STR_CUSTOMANIMATION_TRIGGER) + ": " +
|
|
getShapeDescription( xShape, false );
|
|
|
|
mxEntries.emplace_back(std::make_unique<CustomAnimationListEntryItem>(aDescription, nullptr));
|
|
|
|
OUString sId(weld::toId(mxEntries.back().get()));
|
|
mxTreeView->insert(nullptr, -1, &aDescription, &sId, nullptr, nullptr, false, nullptr);
|
|
std::for_each( pIS->getBegin(), pIS->getEnd(), stl_append_effect_func( *this ) );
|
|
mxLastParentEntry.reset();
|
|
}
|
|
}
|
|
}
|
|
|
|
mxTreeView->thaw();
|
|
|
|
if (mxTreeView->n_children())
|
|
{
|
|
mxEmptyLabelParent->hide();
|
|
mxTreeView->show();
|
|
}
|
|
|
|
if (mpMainSequence)
|
|
{
|
|
::tools::Long nFirstSelNew = -1;
|
|
::tools::Long nLastSelNew = -1;
|
|
|
|
std::vector<std::unique_ptr<weld::TreeIter>> aNewSelection;
|
|
|
|
// restore selection state, expand state, and current-entry (under cursor)
|
|
if (mxTreeView->get_iter_first(*xEntry))
|
|
{
|
|
do
|
|
{
|
|
CustomAnimationListEntryItem* pEntry = weld::fromId<CustomAnimationListEntryItem*>(mxTreeView->get_id(*xEntry));
|
|
|
|
CustomAnimationEffectPtr pEffect( pEntry->getEffect() );
|
|
if (pEffect)
|
|
{
|
|
// 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);
|
|
}
|
|
} while (mxTreeView->iter_next(*xEntry));
|
|
}
|
|
|
|
// tdf#147032 unselect what previous set_cursor may have caused to get selected as a side-effect
|
|
mxTreeView->unselect_all();
|
|
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;
|
|
const bool bMovedDown = nFirstSelNew > nFirstSelOld;
|
|
|
|
if( bMoved && nLastSelOld < nFirstVis && nLastSelNew < nFirstVis )
|
|
{
|
|
// The selection is above the visible area.
|
|
// Scroll up to show the last few selected entries.
|
|
if( nLastSelNew - (nLastVis - nFirstVis) > nFirstSelNew)
|
|
{
|
|
// The entries in the selection range can't fit in view.
|
|
// Scroll so the last selected entry is last in view.
|
|
mxTreeView->vadjustment_set_value(nLastSelNew - (nLastVis - nFirstVis));
|
|
}
|
|
else
|
|
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.
|
|
mxTreeView->vadjustment_set_value(nFirstSelNew);
|
|
}
|
|
else if( bMovedUp && nFirstSelOld <= nFirstVis )
|
|
{
|
|
// A visible entry has moved up out of view; scroll up one.
|
|
mxTreeView->vadjustment_set_value(nFirstVis - 1);
|
|
}
|
|
else if( bMovedDown && nLastSelOld >= nLastVis )
|
|
{
|
|
// An entry has moved down out of view; scroll down one.
|
|
mxTreeView->vadjustment_set_value(nFirstVis + 1);
|
|
}
|
|
else if ( nFirstVis != -1 )
|
|
{
|
|
// The selection is still in view, or it hasn't moved.
|
|
mxTreeView->vadjustment_set_value(nFirstVis);
|
|
}
|
|
}
|
|
|
|
mbIgnorePaint = false;
|
|
|
|
Select();
|
|
}
|
|
|
|
void CustomAnimationList::append( CustomAnimationEffectPtr pEffect )
|
|
{
|
|
Any aTarget( pEffect->getTarget() );
|
|
if( !aTarget.hasValue() )
|
|
return;
|
|
|
|
try
|
|
{
|
|
// create a ui description
|
|
OUString aDescription = getDescription(aTarget, pEffect->getTargetSubItem() != ShapeAnimationSubType::ONLY_BACKGROUND);
|
|
|
|
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 (mxLastParentEntry && nGroupId != -1 && mxLastTargetShape == xTargetShape && mnLastGroupId == nGroupId)
|
|
xParentEntry = mxTreeView->make_iterator(mxLastParentEntry.get());
|
|
|
|
// create an entry for the effect
|
|
std::unique_ptr<weld::TreeIter> xEntry = mxTreeView->make_iterator();
|
|
|
|
mxEntries.emplace_back(std::make_unique<CustomAnimationListEntryItem>(aDescription, pEffect));
|
|
|
|
OUString sId(weld::toId(mxEntries.back().get()));
|
|
|
|
if (xParentEntry)
|
|
{
|
|
// add a subentry
|
|
mxTreeView->insert(xParentEntry.get(), -1, &aDescription, &sId, nullptr, nullptr, false, xEntry.get());
|
|
}
|
|
else
|
|
{
|
|
// add a root entry
|
|
mxTreeView->insert(nullptr, -1, &aDescription, &sId, nullptr, nullptr, false, xEntry.get());
|
|
|
|
// and the new root entry becomes the possible next group header
|
|
mxLastTargetShape = std::move(xTargetShape);
|
|
mnLastGroupId = nGroupId;
|
|
mxLastParentEntry = std::move(xEntry);
|
|
}
|
|
}
|
|
catch (const Exception&)
|
|
{
|
|
TOOLS_WARN_EXCEPTION( "sd", "sd::CustomAnimationList::append()" );
|
|
}
|
|
}
|
|
|
|
static void selectShape(weld::TreeView* pTreeList, const Reference< XShape >& xShape )
|
|
{
|
|
std::unique_ptr<weld::TreeIter> xEntry = pTreeList->make_iterator();
|
|
if (!pTreeList->get_iter_first(*xEntry))
|
|
return;
|
|
|
|
bool bFirstEntry = true;
|
|
|
|
do
|
|
{
|
|
CustomAnimationListEntryItem* pEntry = weld::fromId<CustomAnimationListEntryItem*>(pTreeList->get_id(*xEntry));
|
|
CustomAnimationEffectPtr pEffect(pEntry->getEffect());
|
|
if (pEffect)
|
|
{
|
|
if (pEffect->getTarget() == xShape)
|
|
{
|
|
pTreeList->select(*xEntry);
|
|
if (bFirstEntry)
|
|
{
|
|
pTreeList->scroll_to_row(*xEntry);
|
|
bFirstEntry = false;
|
|
}
|
|
}
|
|
}
|
|
} while (pTreeList->iter_next(*xEntry));
|
|
}
|
|
|
|
void CustomAnimationList::onSelectionChanged(const Any& rSelection)
|
|
{
|
|
try
|
|
{
|
|
mxTreeView->unselect_all();
|
|
|
|
if (rSelection.hasValue())
|
|
{
|
|
Reference< XIndexAccess > xShapes(rSelection, UNO_QUERY);
|
|
if( xShapes.is() )
|
|
{
|
|
sal_Int32 nCount = xShapes->getCount();
|
|
sal_Int32 nIndex;
|
|
for( nIndex = 0; nIndex < nCount; nIndex++ )
|
|
{
|
|
Reference< XShape > xShape( xShapes->getByIndex( nIndex ), UNO_QUERY );
|
|
if( xShape.is() )
|
|
selectShape(mxTreeView.get(), xShape);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Reference< XShape > xShape(rSelection, UNO_QUERY);
|
|
if( xShape.is() )
|
|
selectShape(mxTreeView.get(), xShape);
|
|
}
|
|
}
|
|
|
|
Select();
|
|
}
|
|
catch( Exception& )
|
|
{
|
|
TOOLS_WARN_EXCEPTION( "sd", "sd::CustomAnimationList::onSelectionChanged()" );
|
|
}
|
|
}
|
|
|
|
IMPL_LINK_NOARG(CustomAnimationList, SelectHdl, weld::TreeView&, void)
|
|
{
|
|
Select();
|
|
}
|
|
|
|
// Notify controller to refresh UI when we are notified of selection change from base class
|
|
void CustomAnimationList::Select()
|
|
{
|
|
if( mbIgnorePaint )
|
|
return;
|
|
mpController->onSelect();
|
|
}
|
|
|
|
IMPL_LINK_NOARG(CustomAnimationList, PostExpandHdl, void*, void)
|
|
{
|
|
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 (!mxTreeView->is_selected(*xEntry))
|
|
mxTreeView->select(*xEntry);
|
|
}
|
|
}
|
|
|
|
// Notify controller that selection has changed (it should update the UI)
|
|
mpController->onSelect();
|
|
|
|
mnPostExpandEvent = nullptr;
|
|
}
|
|
|
|
IMPL_LINK(CustomAnimationList, ExpandHdl, const weld::TreeIter&, rParent, bool)
|
|
{
|
|
// 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 (const auto &pEntry : lastSelectedEntries)
|
|
{
|
|
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
|
|
return true;
|
|
}
|
|
|
|
bool CustomAnimationList::isExpanded( const CustomAnimationEffectPtr& pEffect ) const
|
|
{
|
|
bool bExpanded = true; // we assume expanded by default
|
|
|
|
std::unique_ptr<weld::TreeIter> xEntry = mxTreeView->make_iterator();
|
|
if (mxTreeView->get_iter_first(*xEntry))
|
|
{
|
|
do
|
|
{
|
|
CustomAnimationListEntryItem* pEntry =
|
|
weld::fromId<CustomAnimationListEntryItem*>(mxTreeView->get_id(*xEntry));
|
|
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));
|
|
}
|
|
|
|
return bExpanded;
|
|
}
|
|
|
|
bool CustomAnimationList::isVisible(const CustomAnimationEffectPtr& pEffect) const
|
|
{
|
|
std::unique_ptr<weld::TreeIter> xEntry = mxTreeView->make_iterator();
|
|
if (mxTreeView->get_iter_first(*xEntry))
|
|
{
|
|
do
|
|
{
|
|
CustomAnimationListEntryItem* pTestEntry = weld::fromId<CustomAnimationListEntryItem*>(mxTreeView->get_id(*xEntry));
|
|
if (pTestEntry->getEffect() == pEffect)
|
|
return weld::IsEntryVisible(*mxTreeView, *xEntry);
|
|
} while (mxTreeView->iter_next(*xEntry));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
EffectSequence CustomAnimationList::getSelection() const
|
|
{
|
|
EffectSequence aSelection;
|
|
|
|
mxTreeView->selected_foreach([this, &aSelection](weld::TreeIter& rEntry){
|
|
CustomAnimationListEntryItem* pEntry = weld::fromId<CustomAnimationListEntryItem*>(mxTreeView->get_id(rEntry));
|
|
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 (!mxTreeView->get_row_expanded(rEntry) && mxTreeView->iter_has_child(rEntry))
|
|
{
|
|
std::unique_ptr<weld::TreeIter> xChild = mxTreeView->make_iterator(&rEntry);
|
|
(void)mxTreeView->iter_children(*xChild);
|
|
|
|
do
|
|
{
|
|
if (!mxTreeView->is_selected(*xChild))
|
|
{
|
|
CustomAnimationListEntryItem* pChild = weld::fromId<CustomAnimationListEntryItem*>(mxTreeView->get_id(*xChild));
|
|
const CustomAnimationEffectPtr& pChildEffect( pChild->getEffect() );
|
|
if( pChildEffect )
|
|
aSelection.push_back( pChildEffect );
|
|
}
|
|
} while (mxTreeView->iter_next_sibling(*xChild));
|
|
}
|
|
|
|
return false;
|
|
});
|
|
|
|
return aSelection;
|
|
}
|
|
|
|
IMPL_LINK_NOARG(CustomAnimationList, DoubleClickHdl, weld::TreeView&, bool)
|
|
{
|
|
mpController->onDoubleClick();
|
|
return false;
|
|
}
|
|
|
|
IMPL_LINK(CustomAnimationList, CommandHdl, const CommandEvent&, rCEvt, bool)
|
|
{
|
|
if (rCEvt.GetCommand() != CommandEventId::ContextMenu)
|
|
return false;
|
|
|
|
if (rCEvt.IsMouseEvent())
|
|
{
|
|
::Point aPos = rCEvt.GetMousePosPixel();
|
|
std::unique_ptr<weld::TreeIter> xIter(mxTreeView->make_iterator());
|
|
if (mxTreeView->get_dest_row_at_pos(aPos, xIter.get(), false) && !mxTreeView->is_selected(*xIter))
|
|
{
|
|
mxTreeView->unselect_all();
|
|
mxTreeView->set_cursor(*xIter);
|
|
mxTreeView->select(*xIter);
|
|
SelectHdl(*mxTreeView);
|
|
}
|
|
}
|
|
|
|
if (!mxTreeView->get_selected(nullptr))
|
|
return false;
|
|
|
|
std::unique_ptr<weld::Builder> xBuilder(Application::CreateBuilder(mxTreeView.get(), u"modules/simpress/ui/effectmenu.ui"_ustr));
|
|
std::unique_ptr<weld::Menu> xMenu = xBuilder->weld_menu(u"menu"_ustr);
|
|
|
|
sal_Int16 nNodeType = -1;
|
|
sal_Int16 nEntries = 0;
|
|
|
|
mxTreeView->selected_foreach([this, &nNodeType, &nEntries](weld::TreeIter& rEntry){
|
|
CustomAnimationListEntryItem* pEntry = weld::fromId<CustomAnimationListEntryItem*>(mxTreeView->get_id(rEntry));
|
|
CustomAnimationEffectPtr pEffect(pEntry->getEffect());
|
|
|
|
nEntries++;
|
|
if (pEffect)
|
|
{
|
|
if( nNodeType == -1 )
|
|
{
|
|
nNodeType = pEffect->getNodeType();
|
|
}
|
|
else
|
|
{
|
|
if( nNodeType != pEffect->getNodeType() )
|
|
{
|
|
nNodeType = -1;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
});
|
|
|
|
xMenu->set_active(u"onclick"_ustr, nNodeType == EffectNodeType::ON_CLICK);
|
|
xMenu->set_active(u"withprev"_ustr, nNodeType == EffectNodeType::WITH_PREVIOUS);
|
|
xMenu->set_active(u"afterprev"_ustr, nNodeType == EffectNodeType::AFTER_PREVIOUS);
|
|
xMenu->set_sensitive(u"options"_ustr, nEntries == 1);
|
|
xMenu->set_sensitive(u"timing"_ustr, nEntries == 1);
|
|
|
|
OUString sCommand = xMenu->popup_at_rect(mxTreeView.get(), ::tools::Rectangle(rCEvt.GetMousePosPixel(), Size(1,1)));
|
|
if (!sCommand.isEmpty())
|
|
ExecuteContextMenuAction(sCommand);
|
|
|
|
return true;
|
|
}
|
|
|
|
void CustomAnimationList::ExecuteContextMenuAction(const OUString& rIdent)
|
|
{
|
|
mpController->onContextMenu(rIdent);
|
|
}
|
|
|
|
void CustomAnimationList::notify_change()
|
|
{
|
|
update();
|
|
mpController->onSelect();
|
|
}
|
|
|
|
}
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|