summaryrefslogtreecommitdiffstats
path: root/sd/source/ui/animations/CustomAnimationPane.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'sd/source/ui/animations/CustomAnimationPane.cxx')
-rw-r--r--sd/source/ui/animations/CustomAnimationPane.cxx2578
1 files changed, 2578 insertions, 0 deletions
diff --git a/sd/source/ui/animations/CustomAnimationPane.cxx b/sd/source/ui/animations/CustomAnimationPane.cxx
new file mode 100644
index 000000000..0910ba96e
--- /dev/null
+++ b/sd/source/ui/animations/CustomAnimationPane.cxx
@@ -0,0 +1,2578 @@
+/* -*- 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/presentation/EffectPresetClass.hpp>
+#include <com/sun/star/animations/XAnimationNodeSupplier.hpp>
+#include <com/sun/star/animations/AnimationNodeType.hpp>
+#include <com/sun/star/animations/ParallelTimeContainer.hpp>
+#include <com/sun/star/view/XSelectionSupplier.hpp>
+#include <com/sun/star/document/XActionLockable.hpp>
+#include <com/sun/star/drawing/XDrawView.hpp>
+#include <com/sun/star/drawing/XShape.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/presentation/EffectNodeType.hpp>
+#include <com/sun/star/presentation/EffectCommands.hpp>
+#include <com/sun/star/animations/AnimationTransformType.hpp>
+#include <com/sun/star/text/XTextRangeCompare.hpp>
+#include <com/sun/star/container/XEnumerationAccess.hpp>
+#include <com/sun/star/container/XIndexAccess.hpp>
+#include <com/sun/star/presentation/ParagraphTarget.hpp>
+#include <com/sun/star/text/XText.hpp>
+#include <com/sun/star/drawing/LineStyle.hpp>
+#include <com/sun/star/drawing/FillStyle.hpp>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/scopeguard.hxx>
+#include <sfx2/dispatch.hxx>
+#include <sfx2/viewfrm.hxx>
+#include <tools/debug.hxx>
+#include "STLPropertySet.hxx"
+#include <CustomAnimationPane.hxx>
+#include "CustomAnimationDialog.hxx"
+#include <CustomAnimationList.hxx>
+#include "motionpathtag.hxx"
+#include <CustomAnimationPreset.hxx>
+
+#include <comphelper/lok.hxx>
+#include <comphelper/sequence.hxx>
+#include <sfx2/frame.hxx>
+#include <tools/diagnose_ex.h>
+
+#include <svx/svxids.hrc>
+#include <DrawDocShell.hxx>
+#include <ViewShellBase.hxx>
+#include <DrawViewShell.hxx>
+#include <DrawController.hxx>
+#include <sdresid.hxx>
+#include <drawview.hxx>
+#include <slideshow.hxx>
+#include <undoanim.hxx>
+#include <optsitem.hxx>
+#include <sdmod.hxx>
+#include <framework/FrameworkHelper.hxx>
+
+#include <EventMultiplexer.hxx>
+
+#include <strings.hrc>
+#include <sdpage.hxx>
+#include <app.hrc>
+
+#include <svx/strings.hrc>
+#include <svx/dialmgr.hxx>
+
+#include <algorithm>
+#include <memory>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::animations;
+using namespace ::com::sun::star::presentation;
+using namespace ::com::sun::star::text;
+
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::drawing;
+using ::com::sun::star::view::XSelectionSupplier;
+using ::com::sun::star::beans::XPropertySet;
+using ::com::sun::star::container::XIndexAccess;
+using ::com::sun::star::container::XEnumerationAccess;
+using ::com::sun::star::container::XEnumeration;
+using ::com::sun::star::text::XText;
+using ::sd::framework::FrameworkHelper;
+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;
+
+namespace sd {
+
+void fillRepeatComboBox(weld::ComboBox& rBox)
+{
+ OUString aNone( SdResId( STR_CUSTOMANIMATION_REPEAT_NONE ) );
+ rBox.append_text(aNone);
+ rBox.append_text(OUString::number(2));
+ rBox.append_text(OUString::number(3));
+ rBox.append_text(OUString::number(4));
+ rBox.append_text(OUString::number(5));
+ rBox.append_text(OUString::number(10));
+
+ OUString aUntilClick( SdResId( STR_CUSTOMANIMATION_REPEAT_UNTIL_NEXT_CLICK ) );
+ rBox.append_text(aUntilClick);
+
+ OUString aEndOfSlide( SdResId( STR_CUSTOMANIMATION_REPEAT_UNTIL_END_OF_SLIDE ) );
+ rBox.append_text(aEndOfSlide);
+}
+
+CustomAnimationPane::CustomAnimationPane( weld::Widget* pParent, ViewShellBase& rBase )
+ : PanelLayout(pParent, "CustomAnimationsPanel", "modules/simpress/ui/customanimationspanel.ui")
+ , 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"))
+ , maIdle("sd idle treeview select")
+ , mnLastSelectedAnimation(-1)
+ , mnPropertyType(nPropertyTypeNone)
+ , mnCurvePathPos(-1)
+ , mnPolygonPathPos(-1)
+ , mnFreeformPathPos(-1)
+ , maLateInitTimer("sd CustomAnimationPane maLateInitTimer")
+{
+ initialize();
+}
+
+css::ui::LayoutSize CustomAnimationPane::GetHeightForWidth(const sal_Int32 /*nWidth*/)
+{
+ sal_Int32 nMinimumHeight = get_preferred_size().Height();
+ return css::ui::LayoutSize(nMinimumHeight, -1, nMinimumHeight);
+}
+
+void CustomAnimationPane::initialize()
+{
+ mxLBAnimation->connect_changed(LINK(this, CustomAnimationPane, AnimationSelectHdl));
+ mxCustomAnimationList->setController( static_cast<ICustomAnimationListController*> ( this ) );
+ mxCustomAnimationList->set_size_request(mxCustomAnimationList->get_approximate_digit_width() * 15,
+ mxCustomAnimationList->get_height_rows(4));
+
+ mxLBAnimation->set_size_request(mxLBAnimation->get_approximate_digit_width() * 15,
+ mxLBAnimation->get_height_rows(4));
+
+ maStrProperty = mxFTProperty->get_label();
+
+ 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_toggled( LINK( this, CustomAnimationPane, implToggleHdl ) );
+ mxLBCategory->connect_changed( LINK(this, CustomAnimationPane, UpdateAnimationLB) );
+ mxMFStartDelay->connect_value_changed( LINK(this, CustomAnimationPane, DelayModifiedHdl) );
+ mxMFStartDelay->connect_focus_out(LINK( this, CustomAnimationPane, DelayLoseFocusHdl));
+
+ maIdle.SetPriority(TaskPriority::DEFAULT);
+ maIdle.SetInvokeHandler(LINK(this, CustomAnimationPane, SelectionHandler));
+
+ maStrModify = mxFTEffect->get_label();
+
+ // get current controller and initialize listeners
+ try
+ {
+ mxView.set(mrBase.GetController(), UNO_QUERY);
+ addListener();
+ }
+ catch( Exception& )
+ {
+ TOOLS_WARN_EXCEPTION( "sd", "sd::CustomAnimationPane::CustomAnimationPane()" );
+ }
+
+ // tdf#137637 keep user selection during initialization
+ ScopeLockGuard aGuard(maSelectionLock);
+ // get current page and update custom animation list
+ onChangeCurrentPage();
+
+ // Wait a short time before the presets list is created. This gives the
+ // system time to paint the control.
+ maLateInitTimer.SetTimeout(100);
+ maLateInitTimer.SetInvokeHandler(LINK(this, CustomAnimationPane, lateInitCallback));
+ maLateInitTimer.Start();
+}
+
+CustomAnimationPane::~CustomAnimationPane()
+{
+ maLateInitTimer.Stop();
+
+ removeListener();
+
+ MotionPathTagVector aTags;
+ aTags.swap( maMotionPathTags );
+ for (auto const& tag : aTags)
+ tag->Dispose();
+
+ 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();
+}
+
+void CustomAnimationPane::addUndo()
+{
+ SfxUndoManager* pManager = mrBase.GetDocShell()->GetUndoManager();
+ if( pManager )
+ {
+ SdPage* pPage = SdPage::getImplementation( mxCurrentPage );
+ if( pPage )
+ pManager->AddUndoAction( std::make_unique<UndoAnimation>( mrBase.GetDocShell()->GetDoc(), pPage ) );
+ }
+}
+
+void CustomAnimationPane::addListener()
+{
+ Link<tools::EventMultiplexerEvent&,void> aLink( LINK(this,CustomAnimationPane,EventMultiplexerListener) );
+ mrBase.GetEventMultiplexer()->AddEventListener(aLink);
+}
+
+void CustomAnimationPane::removeListener()
+{
+ Link<tools::EventMultiplexerEvent&,void> aLink( LINK(this,CustomAnimationPane,EventMultiplexerListener) );
+ mrBase.GetEventMultiplexer()->RemoveEventListener( aLink );
+}
+
+IMPL_LINK(CustomAnimationPane,EventMultiplexerListener,
+ tools::EventMultiplexerEvent&, rEvent, void)
+{
+ switch (rEvent.meEventId)
+ {
+ case EventMultiplexerEventId::EditViewSelection:
+ onSelectionChanged();
+ break;
+
+ case EventMultiplexerEventId::CurrentPageChanged:
+ onChangeCurrentPage();
+ break;
+
+ case EventMultiplexerEventId::MainViewAdded:
+ // At this moment the controller may not yet been set at model
+ // or ViewShellBase. Take it from the view shell passed with
+ // the event.
+ if (mrBase.GetMainViewShell() != nullptr)
+ {
+ if( mrBase.GetMainViewShell()->GetShellType() == ViewShell::ST_IMPRESS )
+ {
+ mxView.set(mrBase.GetDrawController(), UNO_QUERY);
+ onSelectionChanged();
+ onChangeCurrentPage();
+ break;
+ }
+ }
+ [[fallthrough]];
+ case EventMultiplexerEventId::MainViewRemoved:
+ mxView = nullptr;
+ mxCurrentPage = nullptr;
+ updateControls();
+ break;
+
+ case EventMultiplexerEventId::Disposing:
+ mxView.clear();
+ onSelectionChanged();
+ onChangeCurrentPage();
+ break;
+ case EventMultiplexerEventId::EndTextEdit:
+ if (mpMainSequence && rEvent.mpUserData)
+ mxCustomAnimationList->update( mpMainSequence );
+ break;
+ default: break;
+ }
+}
+
+static sal_Int32 getPropertyType( std::u16string_view rProperty )
+{
+ if ( rProperty == u"Direction" )
+ return nPropertyTypeDirection;
+
+ if ( rProperty == u"Spokes" )
+ return nPropertyTypeSpokes;
+
+ if ( rProperty == u"Zoom" )
+ return nPropertyTypeZoom;
+
+ if ( rProperty == u"Accelerate" )
+ return nPropertyTypeAccelerate;
+
+ if ( rProperty == u"Decelerate" )
+ return nPropertyTypeDecelerate;
+
+ if ( rProperty == u"Color1" )
+ return nPropertyTypeFirstColor;
+
+ if ( rProperty == u"Color2" )
+ return nPropertyTypeSecondColor;
+
+ if ( rProperty == u"FillColor" )
+ return nPropertyTypeFillColor;
+
+ if ( rProperty == u"ColorStyle" )
+ return nPropertyTypeColorStyle;
+
+ if ( rProperty == u"AutoReverse" )
+ return nPropertyTypeAutoReverse;
+
+ if ( rProperty == u"FontStyle" )
+ return nPropertyTypeFont;
+
+ if ( rProperty == u"CharColor" )
+ return nPropertyTypeCharColor;
+
+ if ( rProperty == u"CharHeight" )
+ return nPropertyTypeCharHeight;
+
+ if ( rProperty == u"CharDecoration" )
+ return nPropertyTypeCharDecoration;
+
+ if ( rProperty == u"LineColor" )
+ return nPropertyTypeLineColor;
+
+ if ( rProperty == u"Rotate" )
+ return nPropertyTypeRotate;
+
+ if ( rProperty == u"Transparency" )
+ return nPropertyTypeTransparency;
+
+ if ( rProperty == u"Color" )
+ return nPropertyTypeColor;
+
+ if ( rProperty == u"Scale" )
+ return nPropertyTypeScale;
+
+ return nPropertyTypeNone;
+}
+
+OUString getPropertyName( sal_Int32 nPropertyType )
+{
+ switch( nPropertyType )
+ {
+ case nPropertyTypeDirection:
+ return SdResId(STR_CUSTOMANIMATION_DIRECTION_PROPERTY);
+
+ case nPropertyTypeSpokes:
+ return SdResId(STR_CUSTOMANIMATION_SPOKES_PROPERTY);
+
+ case nPropertyTypeFirstColor:
+ return SdResId(STR_CUSTOMANIMATION_FIRST_COLOR_PROPERTY);
+
+ case nPropertyTypeSecondColor:
+ return SdResId(STR_CUSTOMANIMATION_SECOND_COLOR_PROPERTY);
+
+ case nPropertyTypeZoom:
+ return SdResId(STR_CUSTOMANIMATION_ZOOM_PROPERTY);
+
+ case nPropertyTypeFillColor:
+ return SdResId(STR_CUSTOMANIMATION_FILL_COLOR_PROPERTY);
+
+ case nPropertyTypeColorStyle:
+ return SdResId(STR_CUSTOMANIMATION_STYLE_PROPERTY);
+
+ case nPropertyTypeFont:
+ return SdResId(STR_CUSTOMANIMATION_FONT_PROPERTY);
+
+ case nPropertyTypeCharHeight:
+ return SdResId(STR_CUSTOMANIMATION_SIZE_PROPERTY);
+
+ case nPropertyTypeCharColor:
+ return SdResId(STR_CUSTOMANIMATION_FONT_COLOR_PROPERTY);
+
+ case nPropertyTypeCharHeightStyle:
+ return SdResId(STR_CUSTOMANIMATION_FONT_SIZE_STYLE_PROPERTY);
+
+ case nPropertyTypeCharDecoration:
+ return SdResId(STR_CUSTOMANIMATION_FONT_STYLE_PROPERTY);
+
+ case nPropertyTypeLineColor:
+ return SdResId(STR_CUSTOMANIMATION_LINE_COLOR_PROPERTY);
+
+ case nPropertyTypeRotate:
+ return SdResId(STR_CUSTOMANIMATION_AMOUNT_PROPERTY);
+
+ case nPropertyTypeColor:
+ return SdResId(STR_CUSTOMANIMATION_COLOR_PROPERTY);
+
+ case nPropertyTypeTransparency:
+ return SdResId(STR_CUSTOMANIMATION_AMOUNT_PROPERTY);
+
+ case nPropertyTypeScale:
+ return SdResId(STR_CUSTOMANIMATION_SCALE_PROPERTY);
+ }
+
+ return OUString();
+}
+
+void CustomAnimationPane::updateControls()
+{
+ mxFTDuration->set_sensitive(mxView.is());
+ mxCBXDuration->set_sensitive(mxView.is());
+ mxCustomAnimationList->set_sensitive(mxView.is());
+ if (comphelper::LibreOfficeKit::isActive())
+ {
+ mxPBPlay->hide();
+ mxCBAutoPreview->set_active(false);
+ mxCBAutoPreview->hide();
+ }
+ else
+ {
+ mxPBPlay->set_sensitive(mxView.is());
+ mxCBAutoPreview->set_sensitive(mxView.is());
+ }
+
+ if (!mxView.is())
+ {
+ 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();
+
+ mxPBAddEffect->set_sensitive( maViewSelection.hasValue() );
+ mxPBRemoveEffect->set_sensitive(nSelectionCount != 0);
+ bool bIsSelected = (nSelectionCount > 0);
+
+ if(bIsSelected)
+ {
+ mxFTAnimation->set_sensitive(true);
+ mxLBAnimation->set_sensitive(true);
+ }
+ else
+ {
+ mxFTAnimation->set_sensitive(false);
+ mxLBAnimation->set_sensitive(false);
+ mxLBAnimation->clear();
+ mnLastSelectedAnimation = -1;
+ }
+
+ mxLBCategory->set_sensitive(bIsSelected);
+ mxFTCategory->set_sensitive(bIsSelected);
+
+ 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);
+
+ mxFTProperty->set_label(maStrProperty);
+
+ sal_Int32 nOldPropertyType = mnPropertyType;
+
+ mnPropertyType = nPropertyTypeNone;
+
+ if(bIsSelected)
+ {
+ CustomAnimationEffectPtr pEffect = maListSelection.front();
+
+ OUString aUIName( CustomAnimationPresets::getCustomAnimationPresets().getUINameForPresetId( pEffect->getPresetId() ) );
+
+ OUString aTemp( maStrModify );
+
+ if( !aUIName.isEmpty() )
+ {
+ aTemp += " " + aUIName;
+ mxFTEffect->set_label( aTemp );
+ }
+
+ Any aValue;
+ CustomAnimationPresetPtr pDescriptor = CustomAnimationPresets::getCustomAnimationPresets().getEffectDescriptor( pEffect->getPresetId() );
+ if (pDescriptor)
+ {
+ std::vector<OUString> aProperties( pDescriptor->getProperties() );
+ if( !aProperties.empty() )
+ {
+ mnPropertyType = getPropertyType( aProperties.front() );
+
+ mxFTProperty->set_label( getPropertyName( mnPropertyType ) );
+
+ aValue = getProperty1Value( mnPropertyType, pEffect );
+ }
+ }
+
+ 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;
+
+ if (!mxLBSubControl || nOldPropertyType != nNewPropertyType)
+ {
+ // for LOK destroy old widgets first
+ mxLBSubControl.reset(nullptr);
+ // then create new control, to keep correct pointers for actions
+ mxLBSubControl = SdPropertySubControl::create(nNewPropertyType, mxFTProperty.get(), mxPlaceholderBox.get(), GetFrameWeld(), aValue, pEffect->getPresetId(), LINK(this, CustomAnimationPane, implPropertyHdl));
+ }
+ else
+ {
+ mxLBSubControl->setValue(aValue, pEffect->getPresetId());
+ }
+
+ bool bEnable = aValue.hasValue();
+ mxPlaceholderBox->set_sensitive( bEnable );
+ mxFTProperty->set_sensitive( bEnable );
+
+ if (!pDescriptor)
+ {
+ 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;
+ case EffectPresetClass::EMPHASIS: nCategoryPos = 1; break;
+ case EffectPresetClass::EXIT: nCategoryPos = 2; break;
+ case EffectPresetClass::MOTIONPATH: nCategoryPos = 3; break;
+ default:
+ break;
+ }
+ switch(pEffect->getCommand())
+ {
+ case EffectCommands::TOGGLEPAUSE:
+ case EffectCommands::STOP:
+ case EffectCommands::PLAY:
+ nCategoryPos = 4; break;
+ default:
+ break;
+ }
+ mxLBCategory->set_active(nCategoryPos);
+
+ fillAnimationLB( pEffect->hasText() );
+
+ OUString rsPresetId = pEffect->getPresetId();
+ sal_Int32 nAnimationPos = mxLBAnimation->n_children();
+ while( nAnimationPos-- )
+ {
+ auto pEntryData = weld::fromId<CustomAnimationPresetPtr*>(mxLBAnimation->get_id(nAnimationPos));
+ if (pEntryData)
+ {
+ CustomAnimationPresetPtr& pPtr = *pEntryData;
+ if( pPtr && pPtr->getPresetId() == rsPresetId )
+ {
+ mxLBAnimation->select( nAnimationPos );
+ mnLastSelectedAnimation = nAnimationPos;
+ break;
+ }
+ }
+ }
+
+ // If preset id is missing and category is motion path.
+ if (nAnimationPos < 0 && nCategoryPos == 3)
+ {
+ if (rsPresetId == "libo-motionpath-curve")
+ {
+ mxLBAnimation->select(mnCurvePathPos);
+ mnLastSelectedAnimation = mnCurvePathPos;
+ }
+ else if (rsPresetId == "libo-motionpath-polygon")
+ {
+ mxLBAnimation->select(mnPolygonPathPos);
+ mnLastSelectedAnimation = mnPolygonPathPos;
+ }
+ else if (rsPresetId == "libo-motionpath-freeform-line")
+ {
+ mxLBAnimation->select(mnFreeformPathPos);
+ mnLastSelectedAnimation = mnFreeformPathPos;
+ }
+ }
+
+ sal_uInt16 nPos = 0xffff;
+
+ sal_Int16 nNodeType = pEffect->getNodeType();
+ switch( nNodeType )
+ {
+ case EffectNodeType::ON_CLICK: nPos = 0; break;
+ case EffectNodeType::WITH_PREVIOUS: nPos = 1; break;
+ case EffectNodeType::AFTER_PREVIOUS: nPos = 2; break;
+ }
+
+ mxLBStart->set_active( nPos );
+
+ double fDuration = pEffect->getDuration();
+ const bool bHasSpeed = fDuration > 0.001;
+
+ mxFTDuration->set_sensitive(bHasSpeed);
+ mxCBXDuration->set_sensitive(bHasSpeed);
+
+ if( bHasSpeed )
+ {
+ mxCBXDuration->set_value(fDuration*100.0, FieldUnit::NONE);
+ }
+
+ mxPBPropertyMore->set_sensitive(true);
+
+ mxFTStartDelay->set_sensitive(true);
+ mxMFStartDelay->set_sensitive(true);
+ double fBegin = pEffect->getBegin();
+ mxMFStartDelay->set_value(fBegin*10.0, FieldUnit::NONE);
+ }
+ else
+ {
+ // use an empty direction box to fill the space
+ if (!mxLBSubControl || (nOldPropertyType != nPropertyTypeDirection && nOldPropertyType != nPropertyTypeNone))
+ {
+ // for LOK destroy old widgets first
+ mxLBSubControl.reset(nullptr);
+ // then create new control, to keep correct pointers for actions
+ mxLBSubControl = SdPropertySubControl::create(nPropertyTypeDirection, mxFTProperty.get(), mxPlaceholderBox.get(), GetFrameWeld(), uno::Any(), OUString(), LINK(this, CustomAnimationPane, implPropertyHdl));
+ }
+ else
+ mxLBSubControl->setValue(uno::Any(), OUString());
+
+ 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;
+ bool bEnableDown = true;
+ if( nSelectionCount == 0 )
+ {
+ bEnableUp = false;
+ bEnableDown = false;
+ }
+ else
+ {
+ if( mpMainSequence->find( maListSelection.front() ) == mpMainSequence->getBegin() )
+ bEnableUp = false;
+
+ EffectSequence::iterator aIter( mpMainSequence->find( maListSelection.back() ) );
+ if( aIter == mpMainSequence->getEnd() )
+ {
+ bEnableDown = false;
+ }
+ else
+ {
+ do
+ {
+ ++aIter;
+ }
+ while( (aIter != mpMainSequence->getEnd()) && !(mxCustomAnimationList->isExpanded(*aIter) ) );
+
+ if( aIter == mpMainSequence->getEnd() )
+ bEnableDown = false;
+ }
+
+ if( bEnableUp || bEnableDown )
+ {
+ MainSequenceRebuildGuard aGuard( mpMainSequence );
+
+ EffectSequenceHelper* pSequence = nullptr;
+ for( const CustomAnimationEffectPtr& pEffect : maListSelection )
+ {
+ if( pEffect )
+ {
+ if( pSequence == nullptr )
+ {
+ pSequence = pEffect->getEffectSequence();
+ }
+ else
+ {
+ if( pSequence != pEffect->getEffectSequence() )
+ {
+ bEnableUp = false;
+ bEnableDown = false;
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ mxPBMoveUp->set_sensitive(mxView.is() && bEnableUp);
+ mxPBMoveDown->set_sensitive(mxView.is() && bEnableDown);
+
+ SdOptions* pOptions = SD_MOD()->GetSdOptions(DocumentType::Impress);
+ mxCBAutoPreview->set_active(pOptions->IsPreviewChangedEffects());
+
+ updateMotionPathTags();
+}
+
+static bool updateMotionPathImpl( CustomAnimationPane& rPane, ::sd::View& rView, EffectSequence::iterator aIter, const EffectSequence::iterator& aEnd, MotionPathTagVector& rOldTags, MotionPathTagVector& rNewTags )
+{
+ bool bChanges = false;
+ while( aIter != aEnd )
+ {
+ CustomAnimationEffectPtr pEffect( *aIter++ );
+ if( pEffect && pEffect->getPresetClass() == css::presentation::EffectPresetClass::MOTIONPATH )
+ {
+ rtl::Reference< MotionPathTag > xMotionPathTag;
+ // first try to find if there is already a tag for this
+ auto aMIter = std::find_if(rOldTags.begin(), rOldTags.end(),
+ [&pEffect](const rtl::Reference<MotionPathTag>& xTag) { return xTag->getEffect() == pEffect; });
+ if (aMIter != rOldTags.end())
+ {
+ rtl::Reference< MotionPathTag > xTag( *aMIter );
+ if( !xTag->isDisposed() )
+ {
+ xMotionPathTag = xTag;
+ rOldTags.erase( aMIter );
+ }
+ }
+
+ // if not found, create new one
+ if( !xMotionPathTag.is() )
+ {
+ xMotionPathTag.set( new MotionPathTag( rPane, rView, pEffect ) );
+ bChanges = true;
+ }
+
+ if( xMotionPathTag.is() )
+ rNewTags.push_back( xMotionPathTag );
+ }
+ }
+
+ return bChanges;
+}
+
+void CustomAnimationPane::updateMotionPathTags()
+{
+ bool bChanges = false;
+
+ MotionPathTagVector aTags;
+ aTags.swap( maMotionPathTags );
+
+ ::sd::View* pView = nullptr;
+
+ if( mxView.is() )
+ {
+ std::shared_ptr<ViewShell> xViewShell( mrBase.GetMainViewShell() );
+ if( xViewShell )
+ pView = xViewShell->GetView();
+ }
+
+ if (mpMainSequence && pView)
+ {
+ bChanges = updateMotionPathImpl( *this, *pView, mpMainSequence->getBegin(), mpMainSequence->getEnd(), aTags, maMotionPathTags );
+
+ auto rInteractiveSequenceVector = mpMainSequence->getInteractiveSequenceVector();
+ for (InteractiveSequencePtr const& pIS : rInteractiveSequenceVector)
+ {
+ bChanges |= updateMotionPathImpl( *this, *pView, pIS->getBegin(), pIS->getEnd(), aTags, maMotionPathTags );
+ }
+ }
+
+ if( !aTags.empty() )
+ {
+ bChanges = true;
+ for( rtl::Reference< MotionPathTag >& xTag : aTags )
+ {
+ xTag->Dispose();
+ }
+ }
+
+ if( bChanges && pView )
+ pView->updateHandles();
+}
+
+void CustomAnimationPane::onSelectionChanged()
+{
+ if( maSelectionLock.isLocked() )
+ return;
+
+ ScopeLockGuard aGuard( maSelectionLock );
+
+ if( mxView.is() ) try
+ {
+ Reference< XSelectionSupplier > xSel( mxView, UNO_QUERY_THROW );
+ maViewSelection = xSel->getSelection();
+ mxCustomAnimationList->onSelectionChanged( maViewSelection );
+ updateControls();
+ }
+ catch( Exception& )
+ {
+ TOOLS_WARN_EXCEPTION( "sd", "sd::CustomAnimationPane::onSelectionChanged()" );
+ }
+}
+
+void CustomAnimationPane::onDoubleClick()
+{
+ showOptions();
+}
+
+void CustomAnimationPane::onContextMenu(const OString &rIdent)
+{
+ if (rIdent == "onclick")
+ onChangeStart( EffectNodeType::ON_CLICK );
+ else if (rIdent == "withprev")
+ onChangeStart( EffectNodeType::WITH_PREVIOUS );
+ else if (rIdent == "afterprev")
+ onChangeStart( EffectNodeType::AFTER_PREVIOUS );
+ else if (rIdent == "options")
+ showOptions();
+ else if (rIdent == "timing")
+ showOptions("timing");
+ else if (rIdent == "remove")
+ onRemove();
+ else if (rIdent == "create" && maViewSelection.hasValue())
+ onAdd();
+ updateControls();
+}
+
+static void addValue( const std::unique_ptr<STLPropertySet>& pSet, sal_Int32 nHandle, const Any& rValue )
+{
+ switch( pSet->getPropertyState( nHandle ) )
+ {
+ case STLPropertyState::Ambiguous:
+ // value is already ambiguous, do nothing
+ break;
+ case STLPropertyState::Direct:
+ // set to ambiguous if existing value is different
+ if( rValue != pSet->getPropertyValue( nHandle ) )
+ pSet->setPropertyState( nHandle, STLPropertyState::Ambiguous );
+ break;
+ case STLPropertyState::Default:
+ // just set new value
+ pSet->setPropertyValue( nHandle, rValue );
+ break;
+ }
+}
+
+static sal_Int32 calcMaxParaDepth( const Reference< XShape >& xTargetShape )
+{
+ sal_Int32 nMaxParaDepth = -1;
+
+ if( xTargetShape.is() )
+ {
+ Reference< XEnumerationAccess > xText( xTargetShape, UNO_QUERY );
+ if( xText.is() )
+ {
+ Reference< XPropertySet > xParaSet;
+
+ Reference< XEnumeration > xEnumeration( xText->createEnumeration(), UNO_SET_THROW );
+ while( xEnumeration->hasMoreElements() )
+ {
+ xEnumeration->nextElement() >>= xParaSet;
+ if( xParaSet.is() )
+ {
+ sal_Int32 nParaDepth = 0;
+ xParaSet->getPropertyValue( "NumberingLevel" ) >>= nParaDepth;
+
+ if( nParaDepth > nMaxParaDepth )
+ nMaxParaDepth = nParaDepth;
+ }
+ }
+ }
+ }
+
+ return nMaxParaDepth + 1;
+}
+
+Any CustomAnimationPane::getProperty1Value( sal_Int32 nType, const CustomAnimationEffectPtr& pEffect )
+{
+ switch( nType )
+ {
+ case nPropertyTypeDirection:
+ case nPropertyTypeSpokes:
+ case nPropertyTypeZoom:
+ return Any( pEffect->getPresetSubType() );
+
+ case nPropertyTypeColor:
+ case nPropertyTypeFillColor:
+ case nPropertyTypeFirstColor:
+ case nPropertyTypeSecondColor:
+ case nPropertyTypeCharColor:
+ case nPropertyTypeLineColor:
+ {
+ const sal_Int32 nIndex = (nPropertyTypeFirstColor == nType) ? 0 : 1;
+ return pEffect->getColor( nIndex );
+ }
+
+ case nPropertyTypeFont:
+ return pEffect->getProperty( AnimationNodeType::SET, u"CharFontName" , EValue::To );
+
+ case nPropertyTypeCharHeight:
+ {
+ static const OUStringLiteral aAttributeName( u"CharHeight" );
+ Any aValue( pEffect->getProperty( AnimationNodeType::SET, aAttributeName, EValue::To ) );
+ if( !aValue.hasValue() )
+ aValue = pEffect->getProperty( AnimationNodeType::ANIMATE, aAttributeName, EValue::To );
+ return aValue;
+ }
+
+ case nPropertyTypeRotate:
+ return pEffect->getTransformationProperty( AnimationTransformType::ROTATE, EValue::By);
+
+ case nPropertyTypeTransparency:
+ return pEffect->getProperty( AnimationNodeType::SET, u"Opacity" , EValue::To );
+
+ case nPropertyTypeScale:
+ return pEffect->getTransformationProperty( AnimationTransformType::SCALE, EValue::By );
+
+ case nPropertyTypeCharDecoration:
+ {
+ Sequence< Any > aValues{
+ pEffect->getProperty( AnimationNodeType::SET, u"CharWeight" , EValue::To ),
+ pEffect->getProperty( AnimationNodeType::SET, u"CharPosture" , EValue::To ),
+ pEffect->getProperty( AnimationNodeType::SET, u"CharUnderline" , EValue::To )
+ };
+ return Any( aValues );
+ }
+ }
+
+ Any aAny;
+ return aAny;
+}
+
+bool CustomAnimationPane::setProperty1Value( sal_Int32 nType, const CustomAnimationEffectPtr& pEffect, const Any& rValue )
+{
+ bool bEffectChanged = false;
+ switch( nType )
+ {
+ case nPropertyTypeDirection:
+ case nPropertyTypeSpokes:
+ case nPropertyTypeZoom:
+ {
+ OUString aPresetSubType;
+ rValue >>= aPresetSubType;
+ if( aPresetSubType != pEffect->getPresetSubType() )
+ {
+ CustomAnimationPresets::getCustomAnimationPresets().changePresetSubType( pEffect, aPresetSubType );
+ bEffectChanged = true;
+ }
+ }
+ break;
+
+ case nPropertyTypeFillColor:
+ case nPropertyTypeColor:
+ case nPropertyTypeFirstColor:
+ case nPropertyTypeSecondColor:
+ case nPropertyTypeCharColor:
+ case nPropertyTypeLineColor:
+ {
+ const sal_Int32 nIndex = (nPropertyTypeFirstColor == nType) ? 0 : 1;
+ Any aOldColor( pEffect->getColor( nIndex ) );
+ if( aOldColor != rValue )
+ {
+ pEffect->setColor( nIndex, rValue );
+ bEffectChanged = true;
+ }
+ }
+ break;
+
+ case nPropertyTypeFont:
+ bEffectChanged = pEffect->setProperty( AnimationNodeType::SET, u"CharFontName" , EValue::To, rValue );
+ break;
+
+ case nPropertyTypeCharHeight:
+ {
+ static const OUStringLiteral aAttributeName( u"CharHeight" );
+ bEffectChanged = pEffect->setProperty( AnimationNodeType::SET, aAttributeName, EValue::To, rValue );
+ if( !bEffectChanged )
+ bEffectChanged = pEffect->setProperty( AnimationNodeType::ANIMATE, aAttributeName, EValue::To, rValue );
+ }
+ break;
+ case nPropertyTypeRotate:
+ bEffectChanged = pEffect->setTransformationProperty( AnimationTransformType::ROTATE, EValue::By , rValue );
+ break;
+
+ case nPropertyTypeTransparency:
+ bEffectChanged = pEffect->setProperty( AnimationNodeType::SET, u"Opacity" , EValue::To, rValue );
+ break;
+
+ case nPropertyTypeScale:
+ bEffectChanged = pEffect->setTransformationProperty( AnimationTransformType::SCALE, EValue::By, rValue );
+ break;
+
+ case nPropertyTypeCharDecoration:
+ {
+ Sequence< Any > aValues(3);
+ rValue >>= aValues;
+ bEffectChanged = pEffect->setProperty( AnimationNodeType::SET, u"CharWeight" , EValue::To, aValues[0] );
+ bEffectChanged |= pEffect->setProperty( AnimationNodeType::SET, u"CharPosture" , EValue::To, aValues[1] );
+ bEffectChanged |= pEffect->setProperty( AnimationNodeType::SET, u"CharUnderline" , EValue::To, aValues[2] );
+ }
+ break;
+
+ }
+
+ return bEffectChanged;
+}
+
+static bool hasVisibleShape( const Reference< XShape >& xShape )
+{
+ try
+ {
+ const OUString sShapeType( xShape->getShapeType() );
+
+ if( sShapeType == "com.sun.star.presentation.TitleTextShape" || sShapeType == "com.sun.star.presentation.OutlinerShape" ||
+ sShapeType == "com.sun.star.presentation.SubtitleShape" || sShapeType == "com.sun.star.drawing.TextShape" )
+ {
+ Reference< XPropertySet > xSet( xShape, UNO_QUERY_THROW );
+
+ FillStyle eFillStyle;
+ xSet->getPropertyValue( "FillStyle" ) >>= eFillStyle;
+
+ css::drawing::LineStyle eLineStyle;
+ xSet->getPropertyValue( "LineStyle" ) >>= eLineStyle;
+
+ return eFillStyle != FillStyle_NONE || eLineStyle != css::drawing::LineStyle_NONE;
+ }
+ }
+ catch( Exception& )
+ {
+ }
+ return true;
+}
+
+std::unique_ptr<STLPropertySet> CustomAnimationPane::createSelectionSet()
+{
+ std::unique_ptr<STLPropertySet> pSet = CustomAnimationDialog::createDefaultSet();
+
+ pSet->setPropertyValue( nHandleCurrentPage, Any( mxCurrentPage ) );
+
+ sal_Int32 nMaxParaDepth = 0;
+
+ // get options from selected effects
+ const CustomAnimationPresets& rPresets (CustomAnimationPresets::getCustomAnimationPresets());
+ for( CustomAnimationEffectPtr& pEffect : maListSelection )
+ {
+ EffectSequenceHelper* pEffectSequence = pEffect->getEffectSequence();
+ if( !pEffectSequence )
+ pEffectSequence = mpMainSequence.get();
+
+ if( pEffect->hasText() )
+ {
+ sal_Int32 n = calcMaxParaDepth(pEffect->getTargetShape());
+ if( n > nMaxParaDepth )
+ nMaxParaDepth = n;
+ }
+
+ addValue( pSet, nHandleHasAfterEffect, Any( pEffect->hasAfterEffect() ) );
+ addValue( pSet, nHandleAfterEffectOnNextEffect, Any( pEffect->IsAfterEffectOnNext() ) );
+ addValue( pSet, nHandleDimColor, pEffect->getDimColor() );
+ addValue( pSet, nHandleIterateType, Any( pEffect->getIterateType() ) );
+
+ // convert absolute time to percentage value
+ // This calculation is done in float to avoid some rounding artifacts.
+ float fIterateInterval = static_cast<float>(pEffect->getIterateInterval());
+ if( pEffect->getDuration() )
+ fIterateInterval = static_cast<float>(fIterateInterval / pEffect->getDuration() );
+ fIterateInterval *= 100.0;
+ addValue( pSet, nHandleIterateInterval, Any( static_cast<double>(fIterateInterval) ) );
+
+ addValue( pSet, nHandleBegin, Any( pEffect->getBegin() ) );
+ addValue( pSet, nHandleDuration, Any( pEffect->getDuration() ) );
+ addValue( pSet, nHandleStart, Any( pEffect->getNodeType() ) );
+ addValue( pSet, nHandleRepeat, pEffect->getRepeatCount() );
+ addValue( pSet, nHandleEnd, pEffect->getEnd() );
+ addValue( pSet, nHandleRewind, Any( pEffect->getFill() ) );
+
+ addValue( pSet, nHandlePresetId, Any( pEffect->getPresetId() ) );
+
+ addValue( pSet, nHandleHasText, Any( pEffect->hasText() ) );
+
+ addValue( pSet, nHandleHasVisibleShape, Any( hasVisibleShape( pEffect->getTargetShape() ) ) );
+
+ Any aSoundSource;
+ if( pEffect->getAudio().is() )
+ {
+ aSoundSource = pEffect->getAudio()->getSource();
+ addValue( pSet, nHandleSoundVolume, Any( pEffect->getAudio()->getVolume() ) );
+// todo addValue( pSet, nHandleSoundEndAfterSlide, makeAny( pEffect->getAudio()->getEndAfterSlide() ) );
+// this is now stored at the XCommand parameter sequence
+ }
+ else if( pEffect->getCommand() == EffectCommands::STOPAUDIO )
+ {
+ aSoundSource <<= true;
+ }
+ addValue( pSet, nHandleSoundURL, aSoundSource );
+
+ sal_Int32 nGroupId = pEffect->getGroupId();
+ CustomAnimationTextGroupPtr pTextGroup;
+ if( nGroupId != -1 )
+ pTextGroup = pEffectSequence->findGroup( nGroupId );
+
+ addValue( pSet, nHandleTextGrouping, Any( pTextGroup ? pTextGroup->getTextGrouping() : sal_Int32(-1) ) );
+ addValue( pSet, nHandleAnimateForm, Any( !pTextGroup || pTextGroup->getAnimateForm() ) );
+ addValue( pSet, nHandleTextGroupingAuto, Any( pTextGroup ? pTextGroup->getTextGroupingAuto() : -1.0 ) );
+ addValue( pSet, nHandleTextReverse, Any( pTextGroup && pTextGroup->getTextReverse() ) );
+
+ if( pEffectSequence->getSequenceType() == EffectNodeType::INTERACTIVE_SEQUENCE )
+ {
+ InteractiveSequence* pIS = static_cast< InteractiveSequence* >( pEffectSequence );
+ addValue( pSet, nHandleTrigger, Any( pIS->getTriggerShape() ) );
+ }
+
+ CustomAnimationPresetPtr pDescriptor = rPresets.getEffectDescriptor( pEffect->getPresetId() );
+ if( pDescriptor )
+ {
+ sal_Int32 nType = nPropertyTypeNone;
+
+ std::vector<OUString> aProperties( pDescriptor->getProperties() );
+ if( !aProperties.empty() )
+ nType = getPropertyType( aProperties.front() );
+
+ if( nType != nPropertyTypeNone )
+ {
+ addValue( pSet, nHandleProperty1Type, Any( nType ) );
+ addValue( pSet, nHandleProperty1Value, getProperty1Value( nType, pEffect ) );
+ }
+
+ if( pDescriptor->hasProperty( u"Accelerate" ) )
+ {
+ addValue( pSet, nHandleAccelerate, Any( pEffect->getAcceleration() ) );
+ }
+
+ if( pDescriptor->hasProperty( u"Decelerate" ) )
+ {
+ addValue( pSet, nHandleDecelerate, Any( pEffect->getDecelerate() ) );
+ }
+
+ if( pDescriptor->hasProperty( u"AutoReverse" ) )
+ {
+ addValue( pSet, nHandleAutoReverse, Any( pEffect->getAutoReverse() ) );
+ }
+ }
+ }
+
+ addValue( pSet, nHandleMaxParaDepth, Any( nMaxParaDepth ) );
+
+ return pSet;
+}
+
+void CustomAnimationPane::changeSelection( STLPropertySet const * pResultSet, STLPropertySet const * pOldSet )
+{
+ // change selected effect
+ bool bChanged = false;
+
+ MainSequenceRebuildGuard aGuard( mpMainSequence );
+
+ for( CustomAnimationEffectPtr& pEffect : maListSelection )
+ {
+ DBG_ASSERT( pEffect->getEffectSequence(), "sd::CustomAnimationPane::changeSelection(), dead effect in selection!" );
+ if( !pEffect->getEffectSequence() )
+ continue;
+
+ double fDuration = 0.0; // we might need this for iterate-interval
+ if( pResultSet->getPropertyState( nHandleDuration ) == STLPropertyState::Direct )
+ {
+ pResultSet->getPropertyValue( nHandleDuration ) >>= fDuration;
+ }
+ else
+ {
+ fDuration = pEffect->getDuration();
+ }
+
+ if( pResultSet->getPropertyState( nHandleIterateType ) == STLPropertyState::Direct )
+ {
+ sal_Int16 nIterateType = 0;
+ pResultSet->getPropertyValue( nHandleIterateType ) >>= nIterateType;
+ if( pEffect->getIterateType() != nIterateType )
+ {
+ pEffect->setIterateType( nIterateType );
+ bChanged = true;
+ }
+ }
+
+ if( pEffect->getIterateType() )
+ {
+ if( pResultSet->getPropertyState( nHandleIterateInterval ) == STLPropertyState::Direct )
+ {
+ double fIterateInterval = 0.0;
+ pResultSet->getPropertyValue( nHandleIterateInterval ) >>= fIterateInterval;
+ if( pEffect->getIterateInterval() != fIterateInterval )
+ {
+ const double f = fIterateInterval * pEffect->getDuration() / 100;
+ pEffect->setIterateInterval( f );
+ bChanged = true;
+ }
+ }
+ }
+
+ double fBegin = 0.0;
+
+ if( pResultSet->getPropertyState( nHandleBegin ) == STLPropertyState::Direct )
+ pResultSet->getPropertyValue( nHandleBegin ) >>= fBegin;
+ else
+ fBegin = pEffect->getBegin();
+
+ if( pEffect->getBegin() != fBegin && pResultSet->getPropertyState( nHandleBegin ) == STLPropertyState::Direct)
+ {
+ pEffect->setBegin( fBegin );
+ bChanged = true;
+ }
+
+ if( pResultSet->getPropertyState( nHandleDuration ) == STLPropertyState::Direct )
+ {
+ if( pEffect->getDuration() != fDuration )
+ {
+ pEffect->setDuration( fDuration );
+ bChanged = true;
+ }
+ }
+
+ if( pResultSet->getPropertyState( nHandleStart ) == STLPropertyState::Direct )
+ {
+ sal_Int16 nNodeType = 0;
+ pResultSet->getPropertyValue( nHandleStart ) >>= nNodeType;
+ if( pEffect->getNodeType() != nNodeType )
+ {
+ pEffect->setNodeType( nNodeType );
+ bChanged = true;
+ }
+ }
+
+ if( pResultSet->getPropertyState( nHandleRepeat ) == STLPropertyState::Direct )
+ {
+ Any aRepeatCount( pResultSet->getPropertyValue( nHandleRepeat ) );
+ if( aRepeatCount != pEffect->getRepeatCount() )
+ {
+ pEffect->setRepeatCount( aRepeatCount );
+ bChanged = true;
+ }
+ }
+
+ if( pResultSet->getPropertyState( nHandleEnd ) == STLPropertyState::Direct )
+ {
+ Any aEndValue( pResultSet->getPropertyValue( nHandleEnd ) );
+ if( pEffect->getEnd() != aEndValue )
+ {
+ pEffect->setEnd( aEndValue );
+ bChanged = true;
+ }
+ }
+
+ if( pResultSet->getPropertyState( nHandleRewind ) == STLPropertyState::Direct )
+ {
+ sal_Int16 nFill = 0;
+ pResultSet->getPropertyValue( nHandleRewind ) >>= nFill;
+ if( pEffect->getFill() != nFill )
+ {
+ pEffect->setFill( nFill );
+ bChanged = true;
+ }
+ }
+
+ if( pResultSet->getPropertyState( nHandleHasAfterEffect ) == STLPropertyState::Direct )
+ {
+ bool bHasAfterEffect = false;
+ if( pResultSet->getPropertyValue( nHandleHasAfterEffect ) >>= bHasAfterEffect )
+ {
+ if( pEffect->hasAfterEffect() != bHasAfterEffect )
+ {
+ pEffect->setHasAfterEffect( bHasAfterEffect );
+ bChanged = true;
+ }
+ }
+ }
+
+ if( pResultSet->getPropertyState( nHandleAfterEffectOnNextEffect ) == STLPropertyState::Direct )
+ {
+ bool bAfterEffectOnNextEffect = false;
+ if( (pResultSet->getPropertyValue( nHandleAfterEffectOnNextEffect ) >>= bAfterEffectOnNextEffect)
+ && (pEffect->IsAfterEffectOnNext() != bAfterEffectOnNextEffect) )
+ {
+ pEffect->setAfterEffectOnNext( bAfterEffectOnNextEffect );
+ bChanged = true;
+ }
+ }
+
+ if( pResultSet->getPropertyState( nHandleDimColor ) == STLPropertyState::Direct )
+ {
+ Any aDimColor( pResultSet->getPropertyValue( nHandleDimColor ) );
+ if( pEffect->getDimColor() != aDimColor )
+ {
+ pEffect->setDimColor( aDimColor );
+ bChanged = true;
+ }
+ }
+
+ if( pResultSet->getPropertyState( nHandleAccelerate ) == STLPropertyState::Direct )
+ {
+ double fAccelerate = 0.0;
+ pResultSet->getPropertyValue( nHandleAccelerate ) >>= fAccelerate;
+ if( pEffect->getAcceleration() != fAccelerate )
+ {
+ pEffect->setAcceleration( fAccelerate );
+ bChanged = true;
+ }
+ }
+
+ if( pResultSet->getPropertyState( nHandleDecelerate ) == STLPropertyState::Direct )
+ {
+ double fDecelerate = 0.0;
+ pResultSet->getPropertyValue( nHandleDecelerate ) >>= fDecelerate;
+ if( pEffect->getDecelerate() != fDecelerate )
+ {
+ pEffect->setDecelerate( fDecelerate );
+ bChanged = true;
+ }
+ }
+
+ if( pResultSet->getPropertyState( nHandleAutoReverse ) == STLPropertyState::Direct )
+ {
+ bool bAutoReverse = false;
+ pResultSet->getPropertyValue( nHandleAutoReverse ) >>= bAutoReverse;
+ if( pEffect->getAutoReverse() != bAutoReverse )
+ {
+ pEffect->setAutoReverse( bAutoReverse );
+ bChanged = true;
+ }
+ }
+
+ if( pResultSet->getPropertyState( nHandleProperty1Value ) == STLPropertyState::Direct )
+ {
+ sal_Int32 nType = 0;
+ pOldSet->getPropertyValue( nHandleProperty1Type ) >>= nType;
+
+ bChanged |= setProperty1Value( nType, pEffect, pResultSet->getPropertyValue( nHandleProperty1Value ) );
+ }
+
+ if( pResultSet->getPropertyState( nHandleSoundURL ) == STLPropertyState::Direct )
+ {
+ const Any aSoundSource( pResultSet->getPropertyValue( nHandleSoundURL ) );
+
+ if( aSoundSource.getValueType() == ::cppu::UnoType<sal_Bool>::get() )
+ {
+ pEffect->setStopAudio();
+ bChanged = true;
+ }
+ else
+ {
+ OUString aSoundURL;
+ aSoundSource >>= aSoundURL;
+
+ if( !aSoundURL.isEmpty() )
+ {
+ if( !pEffect->getAudio().is() )
+ {
+ pEffect->createAudio( aSoundSource );
+ bChanged = true;
+ }
+ else
+ {
+ if( pEffect->getAudio()->getSource() != aSoundSource )
+ {
+ pEffect->getAudio()->setSource( aSoundSource );
+ bChanged = true;
+ }
+ }
+ }
+ else
+ {
+ if( pEffect->getAudio().is() || pEffect->getStopAudio() )
+ {
+ pEffect->removeAudio();
+ bChanged = true;
+ }
+ }
+ }
+ }
+
+ if( pResultSet->getPropertyState( nHandleTrigger ) == STLPropertyState::Direct )
+ {
+ Reference< XShape > xTriggerShape;
+ pResultSet->getPropertyValue( nHandleTrigger ) >>= xTriggerShape;
+ bChanged |= mpMainSequence->setTrigger( pEffect, xTriggerShape );
+ }
+ }
+
+ const bool bHasTextGrouping = pResultSet->getPropertyState( nHandleTextGrouping ) == STLPropertyState::Direct;
+ const bool bHasAnimateForm = pResultSet->getPropertyState( nHandleAnimateForm ) == STLPropertyState::Direct;
+ const bool bHasTextGroupingAuto = pResultSet->getPropertyState( nHandleTextGroupingAuto ) == STLPropertyState::Direct;
+ const bool bHasTextReverse = pResultSet->getPropertyState( nHandleTextReverse ) == STLPropertyState::Direct;
+
+ if( bHasTextGrouping || bHasAnimateForm || bHasTextGroupingAuto || bHasTextReverse )
+ {
+ // we need to do a second pass for text grouping options
+ // since changing them can cause effects to be removed
+ // or replaced, we do this after we applied all other options
+ // above
+
+ sal_Int32 nTextGrouping = 0;
+ bool bAnimateForm = true, bTextReverse = false;
+ double fTextGroupingAuto = -1.0;
+
+ if( bHasTextGrouping )
+ pResultSet->getPropertyValue(nHandleTextGrouping) >>= nTextGrouping;
+ else
+ pOldSet->getPropertyValue(nHandleTextGrouping) >>= nTextGrouping;
+
+ if( bHasAnimateForm )
+ pResultSet->getPropertyValue(nHandleAnimateForm) >>= bAnimateForm;
+ else
+ pOldSet->getPropertyValue(nHandleAnimateForm) >>= bAnimateForm;
+
+ if( bHasTextGroupingAuto )
+ pResultSet->getPropertyValue(nHandleTextGroupingAuto) >>= fTextGroupingAuto;
+ else
+ pOldSet->getPropertyValue(nHandleTextGroupingAuto) >>= fTextGroupingAuto;
+
+ if( bHasTextReverse )
+ pResultSet->getPropertyValue(nHandleTextReverse) >>= bTextReverse;
+ else
+ pOldSet->getPropertyValue(nHandleTextReverse) >>= bTextReverse;
+
+ EffectSequence const aSelectedEffects( maListSelection );
+ for( CustomAnimationEffectPtr const& pEffect : aSelectedEffects )
+ {
+ EffectSequenceHelper* pEffectSequence = pEffect->getEffectSequence();
+ if( !pEffectSequence )
+ pEffectSequence = mpMainSequence.get();
+
+ sal_Int32 nGroupId = pEffect->getGroupId();
+ CustomAnimationTextGroupPtr pTextGroup;
+ if( nGroupId != -1 )
+ {
+ // use existing group
+ pTextGroup = pEffectSequence->findGroup( nGroupId );
+ }
+ else
+ {
+ // somethings changed so we need a group now
+ pTextGroup = pEffectSequence->createTextGroup( pEffect, nTextGrouping, fTextGroupingAuto, bAnimateForm, bTextReverse );
+ bChanged = true;
+ }
+
+ //#i119988#
+ /************************************************************************/
+ /*
+ Note, the setAnimateForm means set the animation from TextGroup to Object's Shape
+ And on the UI in means "Animate attached shape" in "Effect Option" dialog
+ The setTextGrouping means set animation to Object's Text,
+ the nTextGrouping is Text Animation Type
+ nTextGrouping = -1 is "As one Object", means no text animation.
+
+ The previous call order first do the setTextGrouping and then do the setAnimateForm,
+ that will cause such defect: in the setTextGrouping, the effect has been removed,
+ but in setAnimateForm still need this effect, then a NULL pointer of that effect will
+ be gotten, and cause crash.
+
+ []bHasAnimateForm means the UI has changed, bAnimateForm is it value
+
+ So if create a new textgroup animation, the following animation will never be run!
+ Since the \A1\B0Animate attached shape\A1\B1 is default checked.
+ And the bHasAnimateForm default is false, and if user uncheck it the value bAnimateForm will be false,
+ it same as the TextGroup\A1\AFs default value, also could not be run setAnimateForm.
+ if( bHasAnimateForm )
+ {
+ if( pTextGroup->getAnimateForm() != bAnimateForm )
+ {
+ pEffectSequence->setAnimateForm( pTextGroup, bAnimateForm );
+ bChanged = true;
+ }
+ }
+
+ In setTextGrouping, there are three case:
+ 1. Create new text effects for empty TextGroup
+ 2. Remove all text effects of TextGroup (nTextGrouping == -1)
+ 3. Change all the text effects\A1\AF start type
+
+ So here is the right logic:
+ If set the animation from text to shape and remove text animation,
+ should do setAnimateForm first, then do setTextGrouping.
+ Other case,do setTextGrouping first, then do setAnimateForm.
+
+ */
+ /************************************************************************/
+
+ bool bDoSetAnimateFormFirst = false;
+ bool bNeedDoSetAnimateForm = false;
+
+ if( bHasAnimateForm )
+ {
+ if( pTextGroup && pTextGroup->getAnimateForm() != bAnimateForm )
+ {
+ if( (pTextGroup->getTextGrouping() >= 0) && (nTextGrouping == -1 ) )
+ {
+ bDoSetAnimateFormFirst = true;
+ }
+ bNeedDoSetAnimateForm = true;
+ }
+ }
+
+ if (bDoSetAnimateFormFirst)
+ {
+ pEffectSequence->setAnimateForm( pTextGroup, bAnimateForm );
+ bChanged = true;
+ }
+
+ if( bHasTextGrouping )
+ {
+ if( pTextGroup && pTextGroup->getTextGrouping() != nTextGrouping )
+ {
+ pEffectSequence->setTextGrouping( pTextGroup, nTextGrouping );
+
+ // All the effects of the outline object is removed so we need to
+ // put it back. OTOH, the shape object that still has effects
+ // in the text group is fine.
+ if (nTextGrouping == -1 && pTextGroup->getEffects().empty())
+ {
+ pEffect->setTarget(Any(pEffect->getTargetShape()));
+ pEffect->setGroupId(-1);
+ mpMainSequence->append(pEffect);
+ }
+
+ bChanged = true;
+ }
+ }
+
+ if (!bDoSetAnimateFormFirst && bNeedDoSetAnimateForm)
+ {
+ if( pTextGroup )
+ {
+ pEffectSequence->setAnimateForm( pTextGroup, bAnimateForm );
+ bChanged = true;
+ }
+ }
+
+ if( bHasTextGroupingAuto )
+ {
+ if( pTextGroup && pTextGroup->getTextGroupingAuto() != fTextGroupingAuto )
+ {
+ pEffectSequence->setTextGroupingAuto( pTextGroup, fTextGroupingAuto );
+ bChanged = true;
+ }
+ }
+
+ if( bHasTextReverse )
+ {
+ if( pTextGroup && pTextGroup->getTextReverse() != bTextReverse )
+ {
+ pEffectSequence->setTextReverse( pTextGroup, bTextReverse );
+ bChanged = true;
+ }
+ }
+ }
+ }
+
+ if( bChanged )
+ {
+ mpMainSequence->rebuild();
+ updateControls();
+ mrBase.GetDocShell()->SetModified();
+ }
+}
+
+void CustomAnimationPane::showOptions(const OString& rPage)
+{
+ std::unique_ptr<STLPropertySet> xSet = createSelectionSet();
+
+ auto xDlg = std::make_shared<CustomAnimationDialog>(GetFrameWeld(), std::move(xSet), rPage);
+
+ weld::DialogController::runAsync(xDlg, [xDlg, this](sal_Int32 nResult){
+ if (nResult )
+ {
+ addUndo();
+ changeSelection(xDlg->getResultSet(), xDlg->getPropertySet());
+ updateControls();
+ }
+ });
+}
+
+void CustomAnimationPane::onChangeCurrentPage()
+{
+ if( !mxView.is() )
+ return;
+
+ try
+ {
+ Reference< XDrawPage > xNewPage( mxView->getCurrentPage() );
+ if( xNewPage != mxCurrentPage )
+ {
+ mxCurrentPage = xNewPage;
+ SdPage* pPage = SdPage::getImplementation( mxCurrentPage );
+ if( pPage )
+ {
+ mpMainSequence = pPage->getMainSequence();
+ mxCustomAnimationList->update( mpMainSequence );
+ }
+ updateControls();
+ }
+ }
+ catch( Exception& )
+ {
+ TOOLS_WARN_EXCEPTION( "sd", "sd::CustomAnimationPane::onChangeCurrentPage()" );
+ }
+}
+
+static bool getTextSelection( const Any& rSelection, Reference< XShape >& xShape, std::vector< sal_Int16 >& rParaList )
+{
+ Reference< XTextRange > xSelectedText;
+ rSelection >>= xSelectedText;
+ if( xSelectedText.is() ) try
+ {
+ xShape.set( xSelectedText->getText(), UNO_QUERY_THROW );
+
+ css::uno::Reference<css::document::XActionLockable> xLockable(xShape, css::uno::UNO_QUERY);
+ if (xLockable.is())
+ xLockable->addActionLock();
+ comphelper::ScopeGuard aGuard([&xLockable]()
+ {
+ if (xLockable.is())
+ xLockable->removeActionLock();
+ });
+
+ Reference< XTextRangeCompare > xTextRangeCompare( xShape, UNO_QUERY_THROW );
+ Reference< XEnumerationAccess > xParaEnumAccess( xShape, UNO_QUERY_THROW );
+ Reference< XEnumeration > xParaEnum( xParaEnumAccess->createEnumeration(), UNO_SET_THROW );
+ Reference< XTextRange > xRange;
+ Reference< XTextRange > xStart( xSelectedText->getStart() );
+ Reference< XTextRange > xEnd( xSelectedText->getEnd() );
+
+ if( xTextRangeCompare->compareRegionEnds( xStart, xEnd ) < 0 )
+ {
+ Reference< XTextRange > xTemp( xStart );
+ xStart = xEnd;
+ xEnd = xTemp;
+ }
+
+ sal_Int16 nPara = 0;
+ while( xParaEnum->hasMoreElements() )
+ {
+ xParaEnum->nextElement() >>= xRange;
+
+ // break if start of selection is prior to end of current paragraph
+ if( xRange.is() && (xTextRangeCompare->compareRegionEnds( xStart, xRange ) >= 0 ) )
+ break;
+
+ nPara++;
+ }
+
+ while( xRange.is() )
+ {
+ if( xRange.is() && !xRange->getString().isEmpty() )
+ rParaList.push_back( nPara );
+
+ // break if end of selection is before or at end of current paragraph
+ if( xRange.is() && xTextRangeCompare->compareRegionEnds( xEnd, xRange ) >= 0 )
+ break;
+
+ nPara++;
+
+ if( xParaEnum->hasMoreElements() )
+ xParaEnum->nextElement() >>= xRange;
+ else
+ xRange.clear();
+ }
+
+ return true;
+ }
+ catch( Exception& )
+ {
+ TOOLS_WARN_EXCEPTION( "sd", "sd::CustomAnimationPane::getTextSelection()" );
+ }
+
+ return false;
+}
+
+namespace
+{
+ Reference<XShape> getTargetShape(const Any& rTarget)
+ {
+ Reference<XShape> xShape;
+ rTarget >>= xShape;
+ if( !xShape.is() )
+ {
+ ParagraphTarget aParaTarget;
+ if (rTarget >>= aParaTarget)
+ xShape = aParaTarget.Shape;
+ }
+ return xShape;
+ }
+}
+
+void CustomAnimationPane::onAdd()
+{
+ bool bHasText = true;
+
+ // first create vector of targets for dialog preview
+ std::vector< Any > aTargets;
+
+ // gather shapes from the selection
+ Reference< XSelectionSupplier > xSel( mxView, UNO_QUERY_THROW );
+ maViewSelection = xSel->getSelection();
+
+ if( maViewSelection.getValueType() == cppu::UnoType<XShapes>::get())
+ {
+ Reference< XIndexAccess > xShapes;
+ maViewSelection >>= xShapes;
+
+ sal_Int32 nCount = xShapes->getCount();
+ aTargets.reserve( nCount );
+ for( sal_Int32 nIndex = 0; nIndex < nCount; nIndex++ )
+ {
+ Any aTarget( xShapes->getByIndex( nIndex ) );
+ aTargets.push_back( aTarget );
+ if( bHasText )
+ {
+ Reference< XText > xText;
+ aTarget >>= xText;
+ if( !xText.is() || xText->getString().isEmpty() )
+ bHasText = false;
+ }
+ }
+ }
+ else if ( maViewSelection.getValueType() == cppu::UnoType<XShape>::get())
+ {
+ aTargets.push_back( maViewSelection );
+ Reference< XText > xText;
+ maViewSelection >>= xText;
+ if( !xText.is() || xText->getString().isEmpty() )
+ bHasText = false;
+ }
+ else if ( maViewSelection.getValueType() == cppu::UnoType<XTextCursor>::get())
+ {
+ Reference< XShape > xShape;
+ std::vector< sal_Int16 > aParaList;
+ if( getTextSelection( maViewSelection, xShape, aParaList ) )
+ {
+ ParagraphTarget aParaTarget;
+ aParaTarget.Shape = xShape;
+
+ for( const auto& rPara : aParaList )
+ {
+ aParaTarget.Paragraph = rPara;
+ aTargets.push_back( Any( aParaTarget ) );
+ }
+ }
+ }
+ else
+ {
+ OSL_FAIL("sd::CustomAnimationPane::onAdd(), unknown view selection!" );
+ return;
+ }
+
+ CustomAnimationPresetPtr pDescriptor;
+ mxFTCategory->set_sensitive(true);
+ mxFTAnimation->set_sensitive(true);
+
+ bool bCategoryReset = false;
+
+ if (!mxLBCategory->get_sensitive() || mxLBCategory->get_active() == -1)
+ {
+ mxLBCategory->set_sensitive(true);
+ mxLBCategory->set_active(0);
+ bCategoryReset = true;
+ }
+
+ if (bCategoryReset || !mxLBAnimation->get_sensitive() ||
+ mxLBAnimation->get_selected_index() == -1)
+ {
+ mxLBAnimation->set_sensitive(true);
+
+ sal_Int32 nFirstEffect = fillAnimationLB(bHasText);
+ if (nFirstEffect == -1)
+ return;
+
+ mxLBAnimation->select(nFirstEffect);
+ mnLastSelectedAnimation = nFirstEffect;
+ }
+
+ auto pEntryData = weld::fromId<CustomAnimationPresetPtr*>(mxLBAnimation->get_selected_id());
+ if (pEntryData)
+ pDescriptor = *pEntryData;
+
+ if( pDescriptor )
+ {
+ const double fDuration = pDescriptor->getDuration();
+ mxCBXDuration->set_value(fDuration*100.0, FieldUnit::NONE);
+ bool bHasSpeed = pDescriptor->getDuration() > 0.001;
+ mxCBXDuration->set_sensitive( bHasSpeed );
+ mxFTDuration->set_sensitive( bHasSpeed );
+
+ mxCustomAnimationList->unselect_all();
+
+ // gather shapes from the selection
+ bool bFirst = true;
+ for( const auto& rTarget : aTargets )
+ {
+ css::uno::Reference<css::document::XActionLockable> xLockable(getTargetShape(rTarget), css::uno::UNO_QUERY);
+ if (xLockable.is())
+ xLockable->addActionLock();
+ comphelper::ScopeGuard aGuard([&xLockable]()
+ {
+ if (xLockable.is())
+ xLockable->removeActionLock();
+ });
+
+ CustomAnimationEffectPtr pCreated = mpMainSequence->append( pDescriptor, rTarget, fDuration );
+
+ // if only one shape with text and no fill or outline is selected, animate only by first level paragraphs
+ if( bHasText && (aTargets.size() == 1) )
+ {
+ Reference< XShape > xShape( rTarget, UNO_QUERY );
+ if( xShape.is() && !hasVisibleShape( xShape ) )
+ {
+ mpMainSequence->createTextGroup( pCreated, 1, -1.0, false, false );
+ }
+ }
+
+ if( bFirst )
+ bFirst = false;
+ else
+ pCreated->setNodeType( EffectNodeType::WITH_PREVIOUS );
+
+ if( pCreated )
+ mxCustomAnimationList->select( pCreated );
+ }
+ }
+
+ PathKind ePathKind = getCreatePathKind();
+
+ if (ePathKind != PathKind::NONE)
+ {
+ createPath( ePathKind, aTargets, 0.0 );
+ updateMotionPathTags();
+ }
+
+ addUndo();
+ mrBase.GetDocShell()->SetModified();
+
+ updateControls();
+
+ SlideShow::Stop( mrBase );
+}
+
+void CustomAnimationPane::onRemove()
+{
+ if( maListSelection.empty() )
+ return;
+
+ addUndo();
+
+ MainSequenceRebuildGuard aGuard( mpMainSequence );
+
+ EffectSequence aList( maListSelection );
+
+ for( CustomAnimationEffectPtr& pEffect : aList )
+ {
+ if( pEffect->getEffectSequence() )
+ pEffect->getEffectSequence()->remove( pEffect );
+ }
+
+ maListSelection.clear();
+ mrBase.GetDocShell()->SetModified();
+}
+
+void CustomAnimationPane::remove( CustomAnimationEffectPtr const & pEffect )
+{
+ if( pEffect->getEffectSequence() )
+ {
+ addUndo();
+ pEffect->getEffectSequence()->remove( pEffect );
+ mrBase.GetDocShell()->SetModified();
+ }
+}
+
+void CustomAnimationPane::onChangeStart()
+{
+ sal_Int16 nNodeType;
+ switch( mxLBStart->get_active() )
+ {
+ case 0: nNodeType = EffectNodeType::ON_CLICK; break;
+ case 1: nNodeType = EffectNodeType::WITH_PREVIOUS; break;
+ case 2: nNodeType = EffectNodeType::AFTER_PREVIOUS; break;
+ default:
+ return;
+ }
+
+ onChangeStart( nNodeType );
+}
+
+void CustomAnimationPane::onChangeStart( sal_Int16 nNodeType )
+{
+ addUndo();
+
+ MainSequenceRebuildGuard aGuard( mpMainSequence );
+
+ bool bNeedRebuild = false;
+
+ for( CustomAnimationEffectPtr& pEffect : maListSelection )
+ {
+ if( pEffect->getNodeType() != nNodeType )
+ {
+ pEffect->setNodeType( nNodeType );
+ bNeedRebuild = true;
+ }
+ }
+
+ if( bNeedRebuild )
+ {
+ mpMainSequence->rebuild();
+ updateControls();
+ mrBase.GetDocShell()->SetModified();
+ }
+}
+
+void CustomAnimationPane::onChangeSpeed()
+{
+ double fDuration = getDuration();
+
+ if(fDuration < 0)
+ return;
+ else
+ {
+ addUndo();
+
+ MainSequenceRebuildGuard aGuard( mpMainSequence );
+
+ // change selected effect
+ for( CustomAnimationEffectPtr& pEffect : maListSelection )
+ {
+ pEffect->setDuration( fDuration );
+ }
+
+ mpMainSequence->rebuild();
+ updateControls();
+ mrBase.GetDocShell()->SetModified();
+ }
+}
+
+double CustomAnimationPane::getDuration() const
+{
+ double fDuration = 0;
+
+ if (!mxCBXDuration->get_text().isEmpty())
+ fDuration = mxCBXDuration->get_value(FieldUnit::NONE) / 100.0;
+
+ return fDuration;
+}
+
+PathKind CustomAnimationPane::getCreatePathKind() const
+{
+ PathKind eKind = PathKind::NONE;
+
+ if (mxLBAnimation->count_selected_rows() == 1 &&
+ mxLBCategory->get_active() == gnMotionPathPos)
+ {
+ const sal_Int32 nPos = mxLBAnimation->get_selected_index();
+ if( nPos == mnCurvePathPos )
+ {
+ eKind = PathKind::CURVE;
+ }
+ else if( nPos == mnPolygonPathPos )
+ {
+ eKind = PathKind::POLYGON;
+ }
+ else if( nPos == mnFreeformPathPos )
+ {
+ eKind = PathKind::FREEFORM;
+ }
+ }
+
+ return eKind;
+}
+
+void CustomAnimationPane::createPath( PathKind eKind, std::vector< Any >& rTargets, double fDuration)
+{
+ sal_uInt16 nSID = 0;
+
+ switch( eKind )
+ {
+ case PathKind::CURVE: nSID = SID_DRAW_BEZIER_NOFILL; break;
+ case PathKind::POLYGON: nSID = SID_DRAW_POLYGON_NOFILL; break;
+ case PathKind::FREEFORM: nSID = SID_DRAW_FREELINE_NOFILL; break;
+ default: break;
+ }
+
+ if( !nSID )
+ return;
+
+ DrawViewShell* pViewShell = dynamic_cast< DrawViewShell* >(
+ FrameworkHelper::Instance(mrBase)->GetViewShell(FrameworkHelper::msCenterPaneURL).get());
+
+ if( pViewShell )
+ {
+ DrawView* pView = pViewShell->GetDrawView();
+ if( pView )
+ pView->UnmarkAllObj();
+
+ std::vector< Any > aTargets( 1, Any( fDuration ) );
+ aTargets.insert( aTargets.end(), rTargets.begin(), rTargets.end() );
+ Sequence< Any > aTargetSequence( comphelper::containerToSequence( aTargets ) );
+ const SfxUnoAnyItem aItem( SID_ADD_MOTION_PATH, Any( aTargetSequence ) );
+ pViewShell->GetViewFrame()->GetDispatcher()->ExecuteList( nSID, SfxCallMode::ASYNCHRON, {&aItem} );
+ }
+}
+
+
+/// this link is called when the property box is modified by the user
+IMPL_LINK_NOARG(CustomAnimationPane, implPropertyHdl, LinkParamNone*, void)
+{
+ if (!mxLBSubControl)
+ return;
+
+ addUndo();
+
+ MainSequenceRebuildGuard aGuard( mpMainSequence );
+
+ const Any aValue(mxLBSubControl->getValue());
+
+ bool bNeedUpdate = false;
+
+ // change selected effect
+ for( const CustomAnimationEffectPtr& pEffect : maListSelection )
+ {
+ if( setProperty1Value( mnPropertyType, pEffect, aValue ) )
+ bNeedUpdate = true;
+ }
+
+ if( bNeedUpdate )
+ {
+ mpMainSequence->rebuild();
+ updateControls();
+ mrBase.GetDocShell()->SetModified();
+ }
+
+ onPreview( false );
+}
+
+IMPL_LINK_NOARG(CustomAnimationPane, DelayModifiedHdl, weld::MetricSpinButton&, void)
+{
+ addUndo();
+}
+
+IMPL_LINK_NOARG(CustomAnimationPane, DelayLoseFocusHdl, weld::Widget&, void)
+{
+ double fBegin = mxMFStartDelay->get_value(FieldUnit::NONE);
+
+ //sequence rebuild only when the control loses focus
+ MainSequenceRebuildGuard aGuard( mpMainSequence );
+
+ // change selected effect
+ for( CustomAnimationEffectPtr& pEffect : maListSelection )
+ {
+ pEffect->setBegin( fBegin/10.0 );
+ }
+
+ mpMainSequence->rebuild();
+ updateControls();
+ mrBase.GetDocShell()->SetModified();
+}
+
+IMPL_LINK_NOARG(CustomAnimationPane, AnimationSelectHdl, weld::TreeView&, void)
+{
+ maIdle.Start();
+}
+
+IMPL_LINK_NOARG(CustomAnimationPane, SelectionHandler, Timer*, void)
+{
+ if (mxLBAnimation->has_grab()) // tdf#136474 try again later
+ {
+ maIdle.Start();
+ return;
+ }
+
+ int nSelected = mxLBAnimation->get_selected_index();
+ if (nSelected == -1)
+ return;
+
+ // 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;
+
+ CustomAnimationPresetPtr* pPreset = weld::fromId<CustomAnimationPresetPtr*>(mxLBAnimation->get_id(nSelected));
+ PathKind ePathKind = getCreatePathKind();
+
+ if ( ePathKind != PathKind::NONE )
+ {
+ std::vector< Any > aTargets;
+ MainSequenceRebuildGuard aGuard( mpMainSequence );
+
+ for( const CustomAnimationEffectPtr& pEffect : maListSelection )
+ {
+ aTargets.push_back( pEffect->getTarget() );
+
+ EffectSequenceHelper* pEffectSequence = pEffect->getEffectSequence();
+ if( !pEffectSequence )
+ pEffectSequence = mpMainSequence.get();
+
+ // delete the old animation, new one will be appended
+ // by createPath and SID_ADD_MOTION_PATH therein
+ pEffectSequence->remove( pEffect );
+ }
+
+ createPath( ePathKind, aTargets, 0.0 );
+ updateMotionPathTags();
+ return;
+ }
+
+ CustomAnimationPresetPtr pDescriptor(*pPreset);
+ const double fDuration = (*pPreset)->getDuration();
+ MainSequenceRebuildGuard aGuard( mpMainSequence );
+
+ // get selected effect
+ for( const CustomAnimationEffectPtr& pEffect : maListSelection )
+ {
+ // Dispose the deprecated motion path tag. It will be rebuilt later.
+ if (pEffect->getPresetClass() == css::presentation::EffectPresetClass::MOTIONPATH)
+ {
+ for (auto const& xTag: maMotionPathTags)
+ {
+ if(xTag->getEffect() == pEffect && !xTag->isDisposed())
+ xTag->Dispose();
+ }
+ }
+
+ EffectSequenceHelper* pEffectSequence = pEffect->getEffectSequence();
+ if( !pEffectSequence )
+ pEffectSequence = mpMainSequence.get();
+
+ pEffectSequence->replace( pEffect, pDescriptor, fDuration );
+ }
+
+ addUndo();
+ onPreview(false);
+}
+
+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, weld::MetricSpinButton&, void)
+{
+ if (!mxCBXDuration->get_text().isEmpty())
+ {
+ double duration_value = static_cast<double>(mxCBXDuration->get_value(FieldUnit::NONE));
+ if(duration_value <= 0.0)
+ {
+ mxCBXDuration->set_value(1, FieldUnit::NONE);
+ }
+ onChangeSpeed();
+ }
+}
+
+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 = mxLBCategory->get_active();
+ const CustomAnimationPresets& rPresets (CustomAnimationPresets::getCustomAnimationPresets());
+ switch(nPosition)
+ {
+ case 0:rCategoryList = rPresets.getEntrancePresets();break;
+ case 1:rCategoryList = rPresets.getEmphasisPresets();break;
+ case 2:rCategoryList = rPresets.getExitPresets();break;
+ case 3:rCategoryList = rPresets.getMotionPathsPresets();break;
+ case 4:rCategoryList = rPresets.getMiscPresets();break;
+ }
+
+ sal_Int32 nFirstEffect = -1;
+
+ int nOldEntryCount = mxLBAnimation->n_children();
+ int nOldScrollPos = mxLBAnimation->vadjustment_get_value();
+
+ mxLBAnimation->freeze();
+ mxLBAnimation->clear();
+ mnLastSelectedAnimation = -1;
+
+ if (nPosition == gnMotionPathPos)
+ {
+ OUString sMotionPathLabel( SdResId( STR_CUSTOMANIMATION_USERPATH ) );
+ 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 (const PresetCategoryPtr& pCategory : rCategoryList)
+ {
+ if( pCategory )
+ {
+ InsertCategory(*mxLBAnimation, pCategory->maLabel);
+
+ int nPos = mxLBAnimation->n_children();
+
+ std::vector< CustomAnimationPresetPtr > aSortedVector =
+ pCategory->maEffects;
+
+ for( const CustomAnimationPresetPtr& pDescriptor : aSortedVector )
+ {
+ // ( !isTextOnly || ( isTextOnly && bHasText ) ) <=> !isTextOnly || bHasText
+ if( pDescriptor && ( !pDescriptor->isTextOnly() || bHasText ) )
+ {
+ auto pCustomPtr = new CustomAnimationPresetPtr(pDescriptor);
+ OUString sId = weld::toId(pCustomPtr);
+ mxLBAnimation->append(sId, pDescriptor->getLabel());
+ mxLBAnimation->set_text_emphasis(nPos, false, 0);
+
+ if (nFirstEffect == -1)
+ nFirstEffect = nPos;
+
+ ++nPos;
+ }
+ }
+ }
+ }
+
+ mxLBAnimation->thaw();
+
+ if (mxLBAnimation->n_children() == nOldEntryCount)
+ mxLBAnimation->vadjustment_set_value(nOldScrollPos);
+
+ return nFirstEffect;
+}
+
+IMPL_LINK(CustomAnimationPane, implToggleHdl, weld::Toggleable&, rBtn, void)
+{
+ implControlHdl(&rBtn);
+}
+
+IMPL_LINK(CustomAnimationPane, implClickHdl, weld::Button&, rBtn, void)
+{
+ implControlHdl(&rBtn);
+}
+
+IMPL_LINK( CustomAnimationPane, implControlListBoxHdl, weld::ComboBox&, rListBox, void )
+{
+ implControlHdl(&rListBox);
+}
+
+/// this link is called when one of the controls is modified
+void CustomAnimationPane::implControlHdl(const weld::Widget* pControl)
+{
+ if (pControl == mxPBAddEffect.get())
+ onAdd();
+ else if (pControl == mxPBRemoveEffect.get())
+ onRemove();
+ else if (pControl == mxLBStart.get())
+ onChangeStart();
+ else if (pControl == mxPBPropertyMore.get())
+ showOptions();
+ else if (pControl == mxPBMoveUp.get())
+ moveSelection( true );
+ else if (pControl == mxPBMoveDown.get())
+ moveSelection( false );
+ else if (pControl == mxPBPlay.get())
+ onPreview( true );
+ else if (pControl == mxCBAutoPreview.get())
+ {
+ SdOptions* pOptions = SD_MOD()->GetSdOptions(DocumentType::Impress);
+ pOptions->SetPreviewChangedEffects(mxCBAutoPreview->get_active());
+ }
+}
+
+IMPL_LINK_NOARG(CustomAnimationPane, lateInitCallback, Timer *, void)
+{
+ // Call getPresets() to initiate the (expensive) construction of the
+ // presets list.
+ CustomAnimationPresets::getCustomAnimationPresets();
+
+ // update selection and control states
+ onSelectionChanged();
+}
+
+void CustomAnimationPane::moveSelection( bool bUp )
+{
+ if( maListSelection.empty() )
+ return;
+
+ EffectSequenceHelper* pSequence = maListSelection.front()->getEffectSequence();
+ if( pSequence == nullptr )
+ return;
+
+ addUndo();
+
+ bool bChanged = false;
+
+ MainSequenceRebuildGuard aGuard( mpMainSequence );
+ EffectSequence& rEffectSequence = pSequence->getSequence();
+
+ if( bUp )
+ {
+ for( const CustomAnimationEffectPtr& pEffect : maListSelection )
+ {
+ EffectSequence::iterator aUpEffectPos( pSequence->find( pEffect ) );
+ // coverity[copy_paste_error : FALSE] - this is correct, checking if it exists
+ if( aUpEffectPos != rEffectSequence.end() )
+ {
+ EffectSequence::iterator aInsertPos( rEffectSequence.erase( aUpEffectPos ) );
+
+ if( aInsertPos != rEffectSequence.begin() )
+ {
+ --aInsertPos;
+ while( (aInsertPos != rEffectSequence.begin()) && !mxCustomAnimationList->isExpanded(*aInsertPos))
+ --aInsertPos;
+ rEffectSequence.insert( aInsertPos, pEffect );
+ }
+ else
+ {
+ rEffectSequence.push_front( pEffect );
+ }
+ bChanged = true;
+ }
+ }
+ }
+ else
+ {
+ EffectSequence::reverse_iterator aIter( maListSelection.rbegin() );
+ const EffectSequence::reverse_iterator aEnd( maListSelection.rend() );
+
+ while( aIter != aEnd )
+ {
+ CustomAnimationEffectPtr pEffect = *aIter++;
+
+ EffectSequence::iterator aDownEffectPos( pSequence->find( pEffect ) );
+ // coverity[copy_paste_error : FALSE] - this is correct, checking if it exists
+ if( aDownEffectPos != rEffectSequence.end() )
+ {
+ EffectSequence::iterator aInsertPos( rEffectSequence.erase( aDownEffectPos ) );
+
+ if( aInsertPos != rEffectSequence.end() )
+ {
+ ++aInsertPos;
+ // Advance over rolled-up (un-expanded) items, unless we just moved it there.
+ while( (aInsertPos != rEffectSequence.end())
+ && !mxCustomAnimationList->isExpanded(*aInsertPos)
+ && (std::find(maListSelection.begin(), maListSelection.end(), *aInsertPos)
+ == maListSelection.end())
+ )
+ ++aInsertPos;
+ rEffectSequence.insert( aInsertPos, pEffect );
+ }
+ else
+ {
+ rEffectSequence.push_back( pEffect );
+ }
+ bChanged = true;
+ }
+ }
+ }
+
+ if( bChanged )
+ {
+ mpMainSequence->rebuild();
+ updateControls();
+ mrBase.GetDocShell()->SetModified();
+ }
+}
+
+void CustomAnimationPane::onPreview( bool bForcePreview )
+{
+ if (!bForcePreview && !mxCBAutoPreview->get_active())
+ return;
+
+ // No preview in LOK.
+ if (comphelper::LibreOfficeKit::isActive())
+ return;
+
+ if( maListSelection.empty() )
+ {
+ rtl::Reference< MotionPathTag > xMotionPathTag;
+ auto aIter = std::find_if(maMotionPathTags.begin(), maMotionPathTags.end(),
+ [](const MotionPathTagVector::value_type& rxMotionPathTag) { return rxMotionPathTag->isSelected(); });
+ if (aIter != maMotionPathTags.end())
+ xMotionPathTag = *aIter;
+
+ if( xMotionPathTag.is() )
+ {
+ MainSequencePtr pSequence = std::make_shared<MainSequence>();
+ pSequence->append( xMotionPathTag->getEffect()->clone() );
+ preview( pSequence->getRootNode() );
+ }
+ else
+ {
+ Reference< XAnimationNodeSupplier > xNodeSupplier( mxCurrentPage, UNO_QUERY );
+ if( !xNodeSupplier.is() )
+ return;
+
+ preview( xNodeSupplier->getAnimationNode() );
+ }
+ }
+ else
+ {
+ MainSequencePtr pSequence = std::make_shared<MainSequence>();
+
+ for( const CustomAnimationEffectPtr& pEffect : maListSelection )
+ {
+ pSequence->append( pEffect->clone() );
+ }
+
+ preview( pSequence->getRootNode() );
+ }
+}
+
+void CustomAnimationPane::preview( const Reference< XAnimationNode >& xAnimationNode )
+{
+ Reference< XParallelTimeContainer > xRoot = ParallelTimeContainer::create( ::comphelper::getProcessComponentContext() );
+ Sequence< css::beans::NamedValue > aUserData
+ { { "node-type", css::uno::Any(css::presentation::EffectNodeType::TIMING_ROOT) } };
+ xRoot->setUserData( aUserData );
+ xRoot->appendChild( xAnimationNode );
+
+ SlideShow::StartPreview( mrBase, mxCurrentPage, xRoot );
+}
+
+// ICustomAnimationListController
+void CustomAnimationPane::onSelect()
+{
+ maListSelection = mxCustomAnimationList->getSelection();
+ updateControls();
+
+ // mark shapes from selected effects
+ if( maSelectionLock.isLocked() )
+ return;
+
+ // tdf#145030 if nothing is selected in the effects list, leave the selection of
+ // objects in the slide untouched
+ if (maListSelection.empty())
+ return;
+
+ ScopeLockGuard aGuard( maSelectionLock );
+ DrawViewShell* pViewShell = dynamic_cast< DrawViewShell* >(
+ FrameworkHelper::Instance(mrBase)->GetViewShell(FrameworkHelper::msCenterPaneURL).get());
+ DrawView* pView = pViewShell ? pViewShell->GetDrawView() : nullptr;
+
+ if( pView )
+ {
+ pView->UnmarkAllObj();
+ for( const CustomAnimationEffectPtr& pEffect : maListSelection )
+ {
+ Reference< XShape > xShape( pEffect->getTargetShape() );
+ SdrObject* pObj = SdrObject::getSdrObjectFromXShape(xShape);
+ if( pObj )
+ pView->MarkObj(pObj, pView->GetSdrPageView());
+ }
+ }
+}
+
+// ICustomAnimationListController
+// pEffectInsertBefore may be null if moving to end of list.
+void CustomAnimationPane::onDragNDropComplete(std::vector< CustomAnimationEffectPtr > pEffectsDragged, CustomAnimationEffectPtr pEffectInsertBefore)
+{
+ if ( !mpMainSequence )
+ return;
+
+ addUndo();
+
+ MainSequenceRebuildGuard aGuard( mpMainSequence );
+
+ // Move all selected effects
+ for( auto const& pEffectDragged : pEffectsDragged )
+ {
+ // Move this dragged effect and any hidden sub-effects
+ EffectSequence::iterator aIter = mpMainSequence->find( pEffectDragged );
+ const EffectSequence::iterator aEnd( mpMainSequence->getEnd() );
+
+ while( aIter != aEnd )
+ {
+ CustomAnimationEffectPtr pEffect = *aIter++;
+
+ // 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 && mxCustomAnimationList->isVisible(*aIter))
+ break;
+ }
+ }
+
+ updateControls();
+ mrBase.GetDocShell()->SetModified();
+}
+
+void CustomAnimationPane::updatePathFromMotionPathTag( const rtl::Reference< MotionPathTag >& xTag )
+{
+ MainSequenceRebuildGuard aGuard( mpMainSequence );
+ if( !xTag.is() )
+ return;
+
+ SdrPathObj* pPathObj = xTag->getPathObj();
+ CustomAnimationEffectPtr pEffect = xTag->getEffect();
+ if( (pPathObj != nullptr) && pEffect )
+ {
+ SfxUndoManager* pManager = mrBase.GetDocShell()->GetUndoManager();
+ if( pManager )
+ {
+ SdPage* pPage = SdPage::getImplementation( mxCurrentPage );
+ if( pPage )
+ pManager->AddUndoAction( std::make_unique<UndoAnimationPath>( mrBase.GetDocShell()->GetDoc(), pPage, pEffect->getNode() ) );
+ }
+
+ pEffect->updatePathFromSdrPathObj( *pPathObj );
+ }
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */