summaryrefslogtreecommitdiffstats
path: root/sd/source/ui/animations
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
commited5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch)
tree7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /sd/source/ui/animations
parentInitial commit. (diff)
downloadlibreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.tar.xz
libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.zip
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--sd/source/ui/animations/CustomAnimationDialog.cxx2090
-rw-r--r--sd/source/ui/animations/CustomAnimationDialog.hxx141
-rw-r--r--sd/source/ui/animations/CustomAnimationList.cxx1231
-rw-r--r--sd/source/ui/animations/CustomAnimationPane.cxx2578
-rw-r--r--sd/source/ui/animations/STLPropertySet.cxx113
-rw-r--r--sd/source/ui/animations/STLPropertySet.hxx73
-rw-r--r--sd/source/ui/animations/SlideTransitionPane.cxx1155
-rw-r--r--sd/source/ui/animations/motionpathtag.cxx1200
-rw-r--r--sd/source/ui/animations/motionpathtag.hxx114
9 files changed, 8695 insertions, 0 deletions
diff --git a/sd/source/ui/animations/CustomAnimationDialog.cxx b/sd/source/ui/animations/CustomAnimationDialog.cxx
new file mode 100644
index 000000000..7490a62c5
--- /dev/null
+++ b/sd/source/ui/animations/CustomAnimationDialog.cxx
@@ -0,0 +1,2090 @@
+/* -*- 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 <config_features.h>
+
+#include <com/sun/star/presentation/EffectNodeType.hpp>
+#include <com/sun/star/animations/Timing.hpp>
+#include <com/sun/star/animations/Event.hpp>
+#include <com/sun/star/animations/EventTrigger.hpp>
+#include <com/sun/star/animations/AnimationFill.hpp>
+#include <com/sun/star/presentation/TextAnimationType.hpp>
+#include <com/sun/star/animations/ValuePair.hpp>
+#include <com/sun/star/awt/FontSlant.hpp>
+#include <com/sun/star/awt/FontWeight.hpp>
+#include <com/sun/star/awt/FontUnderline.hpp>
+#include <com/sun/star/drawing/XDrawPage.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/media/XPlayer.hpp>
+
+#include <memory>
+
+#include <comphelper/lok.hxx>
+#include <i18nutil/unicode.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/stdtext.hxx>
+#include <vcl/weld.hxx>
+#include <vcl/settings.hxx>
+
+#include <svtools/ctrltool.hxx>
+#include <sfx2/objsh.hxx>
+#include <sfx2/viewsh.hxx>
+#include <tools/debug.hxx>
+#include <tools/urlobj.hxx>
+#include <tools/diagnose_ex.h>
+
+#include <editeng/flstitem.hxx>
+
+#include <svx/colorbox.hxx>
+#include <svx/gallery.hxx>
+
+#include <editeng/editids.hrc>
+#include <sdresid.hxx>
+
+#include "CustomAnimationDialog.hxx"
+#include <CustomAnimationPane.hxx>
+#include "STLPropertySet.hxx"
+#include <CustomAnimationPreset.hxx>
+
+#include <avmedia/mediawindow.hxx>
+
+#include <filedlg.hxx>
+#include <strings.hrc>
+#include <helpids.h>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::animations;
+using namespace ::com::sun::star::presentation;
+
+using ::com::sun::star::uno::UNO_QUERY;
+using ::com::sun::star::uno::Any;
+using ::com::sun::star::uno::Sequence;
+using ::com::sun::star::uno::Reference;
+using ::com::sun::star::uno::Exception;
+using ::com::sun::star::drawing::XShape;
+using ::com::sun::star::drawing::XDrawPage;
+using ::com::sun::star::beans::XPropertySet;
+
+namespace sd {
+
+SdPropertySubControl::SdPropertySubControl(weld::Container* pParent)
+ : mxBuilder(Application::CreateBuilder(pParent, "modules/simpress/ui/customanimationfragment.ui",
+ false, reinterpret_cast<sal_uInt64>(SfxViewShell::Current())))
+ , mxContainer(mxBuilder->weld_container("EffectFragment"))
+ , mpParent(pParent)
+{
+}
+
+SdPropertySubControl::~SdPropertySubControl()
+{
+ mpParent->move(mxContainer.get(), nullptr);
+}
+
+namespace {
+
+class SdPresetPropertyBox : public SdPropertySubControl
+{
+public:
+ SdPresetPropertyBox(weld::Label* pLabel, weld::Container* pParent, const Any& rValue, const OUString& aPresetId, const Link<LinkParamNone*,void>& rModifyHdl);
+
+ virtual Any getValue() override;
+ virtual void setValue( const Any& rValue, const OUString& rPresetId ) override;
+
+private:
+ std::vector<OUString> maPropertyValues;
+ Link<LinkParamNone*,void> maModifyLink;
+ std::unique_ptr<weld::ComboBox> mxControl;
+
+ DECL_LINK(OnSelect, weld::ComboBox&, void);
+};
+
+}
+
+SdPresetPropertyBox::SdPresetPropertyBox(weld::Label* pLabel, weld::Container* pParent, const Any& rValue, const OUString& aPresetId, const Link<LinkParamNone*,void>& rModifyHdl)
+ : SdPropertySubControl(pParent)
+ , maModifyLink(rModifyHdl)
+ , mxControl(mxBuilder->weld_combo_box("combo"))
+{
+ mxControl->connect_changed(LINK(this, SdPresetPropertyBox, OnSelect));
+ mxControl->set_help_id(HID_SD_CUSTOMANIMATIONPANE_PRESETPROPERTYBOX);
+ mxControl->show();
+ pLabel->set_mnemonic_widget(mxControl.get());
+ setValue(rValue, aPresetId);
+}
+
+IMPL_LINK_NOARG(SdPresetPropertyBox, OnSelect, weld::ComboBox&, void)
+{
+ maModifyLink.Call(nullptr);
+}
+
+void SdPresetPropertyBox::setValue( const Any& rValue, const OUString& rPresetId )
+{
+ if (!mxControl)
+ return;
+
+ mxControl->freeze();
+ mxControl->clear();
+ maPropertyValues.clear();
+ int nPos = -1;
+
+ const CustomAnimationPresets& rPresets = CustomAnimationPresets::getCustomAnimationPresets();
+ CustomAnimationPresetPtr pDescriptor = rPresets.getEffectDescriptor( rPresetId );
+ if( pDescriptor )
+ {
+
+ OUString aPropertyValue;
+ rValue >>= aPropertyValue;
+
+ std::vector<OUString> aSubTypes( pDescriptor->getSubTypes() );
+
+ mxControl->set_sensitive(!aSubTypes.empty());
+
+ for( const auto& aSubType : aSubTypes )
+ {
+ mxControl->append_text(rPresets.getUINameForProperty(aSubType));
+ maPropertyValues.push_back(aSubType);
+ if (aSubType == aPropertyValue)
+ nPos = maPropertyValues.size() - 1;
+ }
+ }
+ else
+ {
+ mxControl->set_sensitive(false);
+ }
+ mxControl->thaw();
+ if (nPos != -1)
+ mxControl->set_active(nPos);
+}
+
+Any SdPresetPropertyBox::getValue()
+{
+ const int nIndex = mxControl->get_active();
+ if (nIndex == -1)
+ return Any();
+ return Any(maPropertyValues[nIndex]);
+}
+
+namespace {
+
+class SdColorPropertyBox : public SdPropertySubControl
+{
+public:
+ SdColorPropertyBox(weld::Label* pLabel, weld::Container* pParent, weld::Window* pTopLevel, const Any& rValue, const Link<LinkParamNone*,void>& rModifyHdl);
+
+ virtual Any getValue() override;
+ virtual void setValue( const Any& rValue, const OUString& rPresetId ) override;
+
+private:
+ Link<LinkParamNone*,void> maModifyLink;
+ std::unique_ptr<ColorListBox> mxControl;
+
+ DECL_LINK(OnSelect, ColorListBox&, void);
+};
+
+}
+
+SdColorPropertyBox::SdColorPropertyBox(weld::Label* pLabel, weld::Container* pParent, weld::Window* pTopLevel, const Any& rValue, const Link<LinkParamNone*,void>& rModifyHdl)
+ : SdPropertySubControl(pParent)
+ , maModifyLink(rModifyHdl)
+ , mxControl(new ColorListBox(mxBuilder->weld_menu_button("color"), [pTopLevel]{ return pTopLevel; }))
+{
+ mxControl->SetSelectHdl(LINK(this, SdColorPropertyBox, OnSelect));
+ mxControl->set_help_id(HID_SD_CUSTOMANIMATIONPANE_COLORPROPERTYBOX);
+ pLabel->set_mnemonic_widget(&mxControl->get_widget());
+ mxControl->show();
+
+ Color nColor;
+ rValue >>= nColor;
+ mxControl->SelectEntry(nColor);
+}
+
+IMPL_LINK_NOARG(SdColorPropertyBox, OnSelect, ColorListBox&, void)
+{
+ maModifyLink.Call(nullptr);
+}
+
+void SdColorPropertyBox::setValue( const Any& rValue, const OUString& )
+{
+ if (mxControl)
+ {
+ Color nColor;
+ rValue >>= nColor;
+
+ mxControl->SetNoSelection();
+ mxControl->SelectEntry(nColor);
+ }
+}
+
+Any SdColorPropertyBox::getValue()
+{
+ return Any(sal_Int32(mxControl->GetSelectEntryColor().GetRGBColor()));
+}
+
+namespace {
+
+class SdFontPropertyBox : public SdPropertySubControl
+{
+public:
+ SdFontPropertyBox(weld::Label* pLabel, weld::Container* pParent, const Any& rValue, const Link<LinkParamNone*,void>& rModifyHdl);
+
+ virtual Any getValue() override;
+ virtual void setValue(const Any& rValue, const OUString& rPresetId) override;
+
+private:
+ Link<LinkParamNone*,void> maModifyHdl;
+ std::unique_ptr<weld::ComboBox> mxControl;
+
+ DECL_LINK(ControlSelectHdl, weld::ComboBox&, void);
+};
+
+}
+
+SdFontPropertyBox::SdFontPropertyBox(weld::Label* pLabel, weld::Container* pParent, const Any& rValue, const Link<LinkParamNone*,void>& rModifyHdl)
+ : SdPropertySubControl(pParent)
+ , maModifyHdl(rModifyHdl)
+ , mxControl(mxBuilder->weld_combo_box("fontname"))
+{
+ mxControl->connect_changed(LINK(this, SdFontPropertyBox, ControlSelectHdl));
+ mxControl->set_help_id(HID_SD_CUSTOMANIMATIONPANE_FONTPROPERTYBOX);
+ mxControl->show();
+ pLabel->set_mnemonic_widget(mxControl.get());
+
+ const FontList* pFontList = nullptr;
+ bool bMustDelete = false;
+
+ if (SfxObjectShell* pDocSh = SfxObjectShell::Current())
+ {
+ auto pItem = pDocSh->GetItem( SID_ATTR_CHAR_FONTLIST );
+ if (pItem)
+ pFontList = static_cast<const SvxFontListItem*>(pItem)->GetFontList();
+ }
+
+ if (!pFontList)
+ {
+ pFontList = new FontList(Application::GetDefaultDevice(), nullptr);
+ bMustDelete = true;
+ }
+
+ mxControl->freeze();
+
+ sal_uInt16 nFontCount = pFontList->GetFontNameCount();
+ for (sal_uInt16 i = 0; i < nFontCount; ++i)
+ {
+ const FontMetric& rFontMetric = pFontList->GetFontName(i);
+ mxControl->append_text(rFontMetric.GetFamilyName());
+ }
+
+ mxControl->thaw();
+
+ if( bMustDelete )
+ delete pFontList;
+
+ setValue( rValue, OUString() );
+}
+
+IMPL_LINK_NOARG(SdFontPropertyBox, ControlSelectHdl, weld::ComboBox&, void)
+{
+ maModifyHdl.Call(nullptr);
+}
+
+void SdFontPropertyBox::setValue( const Any& rValue, const OUString& )
+{
+ if (mxControl)
+ {
+ OUString aFontName;
+ rValue >>= aFontName;
+ mxControl->set_entry_text(aFontName);
+ }
+}
+
+Any SdFontPropertyBox::getValue()
+{
+ OUString aFontName(mxControl->get_active_text());
+ return Any(aFontName);
+}
+
+namespace {
+
+class SdCharHeightPropertyBox : public SdPropertySubControl
+{
+public:
+ SdCharHeightPropertyBox(weld::Label* pLabel, weld::Container* pParent, const Any& rValue, const Link<LinkParamNone*,void>& rModifyHdl);
+
+ virtual Any getValue() override;
+ virtual void setValue( const Any& rValue, const OUString& ) override;
+
+ DECL_LINK(implMenuSelectHdl, const OString& rIdent, void);
+
+private:
+ Link<LinkParamNone*,void> maModifyHdl;
+ std::unique_ptr<weld::MetricSpinButton> mxMetric;
+ std::unique_ptr<weld::MenuButton> mxControl;
+
+ DECL_LINK(EditModifyHdl, weld::MetricSpinButton&, void);
+};
+
+}
+
+SdCharHeightPropertyBox::SdCharHeightPropertyBox(weld::Label* pLabel, weld::Container* pParent, const Any& rValue, const Link<LinkParamNone*,void>& rModifyHdl)
+ : SdPropertySubControl(pParent)
+ , maModifyHdl(rModifyHdl)
+ , mxMetric(mxBuilder->weld_metric_spin_button("fontsize", FieldUnit::PERCENT))
+ , mxControl(mxBuilder->weld_menu_button("fontsizemenu"))
+{
+ mxMetric->connect_value_changed(LINK(this, SdCharHeightPropertyBox, EditModifyHdl));
+ mxMetric->set_help_id(HID_SD_CUSTOMANIMATIONPANE_CHARHEIGHTPROPERTYBOX);
+ mxMetric->show();
+ pLabel->set_mnemonic_widget(&mxMetric->get_widget());
+
+ mxControl->connect_selected(LINK(this, SdCharHeightPropertyBox, implMenuSelectHdl));
+ mxControl->set_help_id(HID_SD_CUSTOMANIMATIONPANE_CHARHEIGHTPROPERTYBOX);
+ mxControl->show();
+
+ setValue(rValue, OUString());
+}
+
+IMPL_LINK_NOARG(SdCharHeightPropertyBox, EditModifyHdl, weld::MetricSpinButton&, void)
+{
+ maModifyHdl.Call(nullptr);
+}
+
+IMPL_LINK(SdCharHeightPropertyBox, implMenuSelectHdl, const OString&, rIdent, void)
+{
+ sal_Int32 nValue = rIdent.toInt32();
+ mxMetric->set_value(nValue, FieldUnit::PERCENT);
+ EditModifyHdl(*mxMetric);
+}
+
+void SdCharHeightPropertyBox::setValue( const Any& rValue, const OUString& )
+{
+ if (mxMetric)
+ {
+ double fValue = 0.0;
+ rValue >>= fValue;
+ mxMetric->set_value(static_cast<::tools::Long>(fValue * 100.0), FieldUnit::PERCENT);
+ }
+}
+
+Any SdCharHeightPropertyBox::getValue()
+{
+ return Any(static_cast<double>(mxMetric->get_value(FieldUnit::PERCENT)) / 100.0);
+}
+
+namespace {
+
+class SdTransparencyPropertyBox : public SdPropertySubControl
+{
+public:
+ SdTransparencyPropertyBox(weld::Label* pLabel, weld::Container* pParent, const Any& rValue, const Link<LinkParamNone*,void>& rModifyHdl);
+
+ virtual Any getValue() override;
+ virtual void setValue( const Any& rValue, const OUString& rPresetId ) override;
+
+ DECL_LINK(implMenuSelectHdl, const OString&, void);
+ DECL_LINK(implModifyHdl, weld::MetricSpinButton&, void);
+
+ void updateMenu();
+
+private:
+ Link<LinkParamNone*,void> maModifyHdl;
+
+ std::unique_ptr<weld::MetricSpinButton> mxMetric;
+ std::unique_ptr<weld::MenuButton> mxControl;
+};
+
+}
+
+SdTransparencyPropertyBox::SdTransparencyPropertyBox(weld::Label* pLabel, weld::Container* pParent, const Any& rValue, const Link<LinkParamNone*,void>& rModifyHdl)
+ : SdPropertySubControl(pParent)
+ , maModifyHdl(rModifyHdl)
+ , mxMetric(mxBuilder->weld_metric_spin_button("transparent", FieldUnit::PERCENT))
+ , mxControl(mxBuilder->weld_menu_button("transparentmenu"))
+{
+ for (sal_Int32 i = 25; i < 101; i += 25)
+ {
+ OUString aStr(unicode::formatPercent(i,
+ Application::GetSettings().GetUILanguageTag()));
+ mxControl->append_item_check(OUString::number(i), aStr);
+ }
+
+ mxControl->connect_selected(LINK(this, SdTransparencyPropertyBox, implMenuSelectHdl));
+ mxControl->set_help_id(HID_SD_CUSTOMANIMATIONPANE_TRANSPARENCYPROPERTYBOX);
+ mxControl->show();
+
+ mxMetric->connect_value_changed(LINK(this, SdTransparencyPropertyBox, implModifyHdl));
+ mxMetric->set_help_id(HID_SD_CUSTOMANIMATIONPANE_TRANSPARENCYPROPERTYBOX);
+ mxMetric->show();
+ pLabel->set_mnemonic_widget(&mxMetric->get_widget());
+
+ setValue(rValue, OUString());
+}
+
+void SdTransparencyPropertyBox::updateMenu()
+{
+ sal_Int64 nValue = mxMetric->get_value(FieldUnit::PERCENT);
+ for (sal_uInt16 i = 25; i < 101; i += 25)
+ mxControl->set_item_active(OString::number(i), nValue == i);
+}
+
+IMPL_LINK_NOARG(SdTransparencyPropertyBox, implModifyHdl, weld::MetricSpinButton&, void)
+{
+ updateMenu();
+ maModifyHdl.Call(nullptr);
+}
+
+IMPL_LINK(SdTransparencyPropertyBox, implMenuSelectHdl, const OString&, rIdent, void)
+{
+ auto nValue = rIdent.toInt32();
+ if (nValue != mxMetric->get_value(FieldUnit::PERCENT))
+ {
+ mxMetric->set_value(nValue, FieldUnit::PERCENT);
+ implModifyHdl(*mxMetric);
+ }
+}
+
+void SdTransparencyPropertyBox::setValue(const Any& rValue, const OUString&)
+{
+ if (mxMetric)
+ {
+ double fValue = 0.0;
+ rValue >>= fValue;
+ ::tools::Long nValue = static_cast<::tools::Long>(fValue * 100);
+ mxMetric->set_value(nValue, FieldUnit::PERCENT);
+ updateMenu();
+ }
+}
+
+Any SdTransparencyPropertyBox::getValue()
+{
+ return Any(static_cast<double>(mxMetric->get_value(FieldUnit::PERCENT)) / 100.0);
+}
+
+namespace {
+
+class SdRotationPropertyBox : public SdPropertySubControl
+{
+public:
+ SdRotationPropertyBox(weld::Label* pLabel, weld::Container* pParent, const Any& rValue, const Link<LinkParamNone*,void>& rModifyHdl);
+
+ virtual Any getValue() override;
+ virtual void setValue( const Any& rValue, const OUString& ) override;
+
+ DECL_LINK(implMenuSelectHdl, const OString&, void);
+ DECL_LINK(implModifyHdl, weld::MetricSpinButton&, void);
+
+ void updateMenu();
+
+private:
+ Link<LinkParamNone*,void> maModifyHdl;
+
+ std::unique_ptr<weld::MetricSpinButton> mxMetric;
+ std::unique_ptr<weld::MenuButton> mxControl;
+};
+
+}
+
+SdRotationPropertyBox::SdRotationPropertyBox(weld::Label* pLabel, weld::Container* pParent, const Any& rValue, const Link<LinkParamNone*,void>& rModifyHdl)
+ : SdPropertySubControl(pParent)
+ , maModifyHdl(rModifyHdl)
+ , mxMetric(mxBuilder->weld_metric_spin_button("rotate", FieldUnit::DEGREE))
+ , mxControl(mxBuilder->weld_menu_button("rotatemenu"))
+{
+ mxMetric->connect_value_changed(LINK( this, SdRotationPropertyBox, implModifyHdl));
+ mxMetric->set_help_id(HID_SD_CUSTOMANIMATIONPANE_ROTATIONPROPERTYBOX);
+ mxMetric->show();
+ pLabel->set_mnemonic_widget(&mxMetric->get_widget());
+
+ mxControl->connect_selected(LINK(this, SdRotationPropertyBox, implMenuSelectHdl));
+ mxControl->set_help_id(HID_SD_CUSTOMANIMATIONPANE_ROTATIONPROPERTYBOX);
+ mxControl->show();
+
+ setValue(rValue, OUString());
+}
+
+void SdRotationPropertyBox::updateMenu()
+{
+ sal_Int64 nValue = mxMetric->get_value(FieldUnit::DEGREE);
+ bool bDirection = nValue >= 0;
+ nValue = (nValue < 0 ? -nValue : nValue);
+
+ mxControl->set_item_active("90", nValue == 90);
+ mxControl->set_item_active("180", nValue == 180);
+ mxControl->set_item_active("360", nValue == 360);
+ mxControl->set_item_active("720", nValue == 720);
+
+ mxControl->set_item_active("closewise", bDirection);
+ mxControl->set_item_active("counterclock", !bDirection);
+}
+
+IMPL_LINK_NOARG(SdRotationPropertyBox, implModifyHdl, weld::MetricSpinButton&, void)
+{
+ updateMenu();
+ maModifyHdl.Call(nullptr);
+}
+
+IMPL_LINK(SdRotationPropertyBox, implMenuSelectHdl, const OString&, rIdent, void)
+{
+ auto nValue = mxMetric->get_value(FieldUnit::DEGREE);
+ bool bDirection = nValue >= 0;
+ nValue = (nValue < 0 ? -nValue : nValue);
+
+ if (rIdent == "clockwise")
+ bDirection = true;
+ else if (rIdent == "counterclock")
+ bDirection = false;
+ else
+ nValue = rIdent.toInt32();
+
+ if( !bDirection )
+ nValue = -nValue;
+
+ if (nValue != mxMetric->get_value(FieldUnit::DEGREE))
+ {
+ mxMetric->set_value(nValue, FieldUnit::DEGREE);
+ implModifyHdl(*mxMetric);
+ }
+}
+
+void SdRotationPropertyBox::setValue( const Any& rValue, const OUString& )
+{
+ if (mxMetric)
+ {
+ double fValue = 0.0;
+ rValue >>= fValue;
+ ::tools::Long nValue = static_cast<::tools::Long>(fValue);
+ mxMetric->set_value(nValue, FieldUnit::DEGREE);
+ updateMenu();
+ }
+}
+
+Any SdRotationPropertyBox::getValue()
+{
+ return Any(static_cast<double>(mxMetric->get_value(FieldUnit::DEGREE)));
+}
+
+namespace {
+
+class SdScalePropertyBox : public SdPropertySubControl
+{
+public:
+ SdScalePropertyBox(weld::Label* pLabel, weld::Container* pParent, const Any& rValue, const Link<LinkParamNone*,void>& rModifyHdl);
+
+ virtual Any getValue() override;
+ virtual void setValue( const Any& rValue, const OUString& ) override;
+
+ DECL_LINK(implMenuSelectHdl, const OString&, void);
+ DECL_LINK(implModifyHdl, weld::MetricSpinButton&, void);
+
+ void updateMenu();
+
+private:
+ Link<LinkParamNone*,void> maModifyHdl;
+ int mnDirection;
+
+ std::unique_ptr<weld::MetricSpinButton> mxMetric;
+ std::unique_ptr<weld::MenuButton> mxControl;
+};
+
+}
+
+SdScalePropertyBox::SdScalePropertyBox(weld::Label* pLabel, weld::Container* pParent, const Any& rValue, const Link<LinkParamNone*,void>& rModifyHdl)
+ : SdPropertySubControl(pParent)
+ , maModifyHdl( rModifyHdl )
+ , mxMetric(mxBuilder->weld_metric_spin_button("scale", FieldUnit::PERCENT))
+ , mxControl(mxBuilder->weld_menu_button("scalemenu"))
+{
+ mxControl->connect_selected(LINK(this, SdScalePropertyBox, implMenuSelectHdl));
+ mxControl->set_help_id(HID_SD_CUSTOMANIMATIONPANE_SCALEPROPERTYBOX);
+ mxControl->show();
+
+ mxMetric->connect_value_changed(LINK(this, SdScalePropertyBox, implModifyHdl));
+ mxMetric->set_help_id(HID_SD_CUSTOMANIMATIONPANE_SCALEPROPERTYBOX);
+ mxMetric->show();
+ pLabel->set_mnemonic_widget(&mxMetric->get_widget());
+
+ setValue(rValue, OUString());
+}
+
+void SdScalePropertyBox::updateMenu()
+{
+ auto nValue = mxMetric->get_value(FieldUnit::PERCENT);
+
+ mxControl->set_item_active("25scale", nValue == 25);
+ mxControl->set_item_active("50scale", nValue == 50);
+ mxControl->set_item_active("150scale", nValue == 150);
+ mxControl->set_item_active("400scale", nValue == 400);
+
+ mxControl->set_item_active("hori", mnDirection == 1);
+ mxControl->set_item_active("vert", mnDirection == 2);
+ mxControl->set_item_active("both", mnDirection == 3);
+}
+
+IMPL_LINK_NOARG(SdScalePropertyBox, implModifyHdl, weld::MetricSpinButton&, void)
+{
+ updateMenu();
+ maModifyHdl.Call(nullptr);
+}
+
+IMPL_LINK(SdScalePropertyBox, implMenuSelectHdl, const OString&, rIdent, void)
+{
+ auto nValue = mxMetric->get_value(FieldUnit::PERCENT);
+
+ int nDirection = mnDirection;
+
+ if (rIdent == "hori")
+ nDirection = 1;
+ else if (rIdent == "vert")
+ nDirection = 2;
+ else if (rIdent == "both")
+ nDirection = 3;
+ else
+ nValue = rIdent.toInt32(); // Getting here indicates a UI bug and should be handled better
+
+ bool bModified = false;
+
+ if( nDirection != mnDirection )
+ {
+ mnDirection = nDirection;
+ bModified = true;
+ }
+
+ if (nValue != mxMetric->get_value(FieldUnit::PERCENT))
+ {
+ mxMetric->set_value(nValue, FieldUnit::PERCENT);
+ bModified = true;
+ }
+
+ if(bModified)
+ {
+ implModifyHdl(*mxMetric);
+ updateMenu();
+ }
+}
+
+void SdScalePropertyBox::setValue(const Any& rValue, const OUString&)
+{
+ if (!mxMetric)
+ return;
+
+ ValuePair aValues;
+ rValue >>= aValues;
+
+ double fValue1 = 0.0;
+ double fValue2 = 0.0;
+
+ aValues.First >>= fValue1;
+ aValues.Second >>= fValue2;
+
+ // 'Size' drop down menu set by mnDirection when loading Grow and Shrink Animation
+ // Shouldn't compare a float directly to zero... should be fixed with delta epsilon compare
+ // Might be better to just have a flag in the content.xml for this
+ if( (fValue1 == 0.0) && (fValue2 == 0.0) )
+ mnDirection = 3; // assume 'Both' scaling option when both are zero
+ else if( (fValue1 != 0.0) && (fValue2 == 0.0) )
+ mnDirection = 1;
+ else if( (fValue1 == 0.0) && (fValue2 != 0.0) )
+ mnDirection = 2;
+ else
+ mnDirection = 3;
+
+ // Grow and Shrink Animation is a relative change with value stored in content.xml under tag
+ // smil:by=*,*
+ // An offset of 1 must be added to properly translate from content.xml to UI value displayed
+ // e.g. if in content.xml smil:by=0.5,0.5 then 1 + (0.5,0.5) = (1.5,1.5) => grow by 150% of the
+ // size horizontal and vertical
+ // e.g. if in content.xml smil:by=-0.5,-0.5 then 1 + (-0.5,-0.5) = (0.5,0.5) => shrink by 50%
+ // of the size horizontal and vertical
+ fValue1 += 1;
+ fValue2 += 1;
+
+ // Determine value from file for UI 'Size' field based on determined mnDirection
+ ::tools::Long nValue;
+ if( mnDirection == 1 )
+ nValue = static_cast<::tools::Long>(fValue1 * 100.0);
+ else if( mnDirection == 2 )
+ nValue = static_cast<::tools::Long>(fValue2 * 100.0);
+ else if( mnDirection == 3 ){
+ if (fValue1 >= fValue2)
+ nValue = static_cast<::tools::Long>(fValue1 * 100.0);
+ else
+ nValue = static_cast<::tools::Long>(fValue2 * 100.0);
+ }
+ else
+ nValue = static_cast<::tools::Long>(100.0); // default to 100% in UI if something goes wrong
+
+ mxMetric->set_value(nValue, FieldUnit::PERCENT);
+ updateMenu();
+}
+
+Any SdScalePropertyBox::getValue()
+{
+ double fValue1 = static_cast<double>(mxMetric->get_value(FieldUnit::PERCENT)) / 100.0;
+
+ // Grow and Shrink Animation is a relative change with value stored in content.xml under tag
+ // smil:by=*,*
+ // An offset of 1 must be subtracted to properly translate UI value displayed and save to
+ // content.xml
+ // e.g. if UI value is 150% then 1.5 - 1 = 0.5 and is set to smil:by=0.5,0.5 in content.xml
+ // e.g. if UI value is 50% then 0.5 - 1 = -0.5 and is set to smil:by=-0.5,-0.5 in content.xml
+ fValue1 -= 1;
+
+ double fValue2 = fValue1;
+
+ // mnDirection set by 'Size' drop down menu and used to zero out either horizontal or vertical
+ // scaling depending on what option is selected
+ if( mnDirection == 1 )
+ fValue2 = 0.0;
+ else if( mnDirection == 2 )
+ fValue1 = 0.0;
+
+ ValuePair aValues;
+ aValues.First <<= fValue1;
+ aValues.Second <<= fValue2;
+
+ return Any( aValues );
+}
+
+namespace {
+
+class SdFontStylePropertyBox : public SdPropertySubControl
+{
+public:
+ SdFontStylePropertyBox(weld::Label* pLabel, weld::Container* pParent, const Any& rValue, const Link<LinkParamNone*,void>& rModifyHdl);
+
+ virtual Any getValue() override;
+ virtual void setValue( const Any& rValue, const OUString& ) override;
+
+ DECL_LINK(implMenuSelectHdl, const OString&, void);
+
+ void update();
+
+private:
+ float mfFontWeight;
+ awt::FontSlant meFontSlant;
+ sal_Int16 mnFontUnderline;
+ Link<LinkParamNone*,void> maModifyHdl;
+
+ std::unique_ptr<weld::Entry> mxEdit;
+ std::unique_ptr<weld::MenuButton> mxControl;
+};
+
+}
+
+SdFontStylePropertyBox::SdFontStylePropertyBox(weld::Label* pLabel, weld::Container* pParent, const Any& rValue, const Link<LinkParamNone*,void>& rModifyHdl )
+ : SdPropertySubControl(pParent)
+ , maModifyHdl( rModifyHdl )
+ , mxEdit(mxBuilder->weld_entry("entry"))
+ , mxControl(mxBuilder->weld_menu_button("entrymenu"))
+{
+ mxEdit->set_text(SdResId(STR_CUSTOMANIMATION_SAMPLE));
+ mxEdit->set_help_id(HID_SD_CUSTOMANIMATIONPANE_FONTSTYLEPROPERTYBOX);
+ pLabel->set_mnemonic_widget(mxEdit.get());
+ mxEdit->show();
+
+ mxControl->connect_selected(LINK(this, SdFontStylePropertyBox, implMenuSelectHdl));
+ mxControl->set_help_id(HID_SD_CUSTOMANIMATIONPANE_FONTSTYLEPROPERTYBOX);
+ mxControl->show();
+
+ setValue(rValue, OUString());
+}
+
+void SdFontStylePropertyBox::update()
+{
+ // update menu
+ mxControl->set_item_active("bold", mfFontWeight == awt::FontWeight::BOLD);
+ mxControl->set_item_active("italic", meFontSlant == awt::FontSlant_ITALIC);
+ mxControl->set_item_active("underline", mnFontUnderline != awt::FontUnderline::NONE );
+
+ // update sample edit
+ vcl::Font aFont(mxEdit->get_font());
+ aFont.SetWeight(mfFontWeight == awt::FontWeight::BOLD ? WEIGHT_BOLD : WEIGHT_NORMAL);
+ aFont.SetItalic(meFontSlant == awt::FontSlant_ITALIC ? ITALIC_NORMAL : ITALIC_NONE);
+ aFont.SetUnderline(mnFontUnderline == awt::FontUnderline::NONE ? LINESTYLE_NONE : LINESTYLE_SINGLE);
+ mxEdit->set_font(aFont);
+}
+
+IMPL_LINK(SdFontStylePropertyBox, implMenuSelectHdl, const OString&, rIdent, void)
+{
+ if (rIdent == "bold")
+ {
+ if( mfFontWeight == awt::FontWeight::BOLD )
+ mfFontWeight = awt::FontWeight::NORMAL;
+ else
+ mfFontWeight = awt::FontWeight::BOLD;
+ }
+ else if (rIdent == "italic")
+ {
+ if( meFontSlant == awt::FontSlant_ITALIC )
+ meFontSlant = awt::FontSlant_NONE;
+ else
+ meFontSlant = awt::FontSlant_ITALIC;
+ }
+ else if (rIdent == "underline")
+ {
+ if( mnFontUnderline == awt::FontUnderline::SINGLE )
+ mnFontUnderline = awt::FontUnderline::NONE;
+ else
+ mnFontUnderline = awt::FontUnderline::SINGLE;
+ }
+
+ update();
+ maModifyHdl.Call(nullptr);
+}
+
+void SdFontStylePropertyBox::setValue( const Any& rValue, const OUString& )
+{
+ Sequence<Any> aValues;
+ rValue >>= aValues;
+
+ aValues[0] >>= mfFontWeight;
+ aValues[1] >>= meFontSlant;
+ aValues[2] >>= mnFontUnderline;
+
+ update();
+}
+
+Any SdFontStylePropertyBox::getValue()
+{
+ Sequence<Any> aValues{ Any(mfFontWeight), Any(meFontSlant), Any(mnFontUnderline) };
+ return Any( aValues );
+}
+
+class CustomAnimationEffectTabPage
+{
+public:
+ CustomAnimationEffectTabPage(weld::Container* pParent, weld::Window* pDialog, const STLPropertySet* pSet);
+
+ void update( STLPropertySet* pSet );
+ DECL_LINK(implSelectHdl, weld::ComboBox&, void);
+ DECL_LINK(implClickHdl, weld::Button&, void);
+ void implHdl(const weld::Widget*);
+
+private:
+ void updateControlStates();
+ void fillSoundListBox();
+ void clearSoundListBox();
+ sal_Int32 getSoundObject( std::u16string_view rStr );
+ void openSoundFileDialog();
+ void onSoundPreview();
+ weld::Window* GetFrameWeld() const { return mpDialog; }
+
+private:
+ ::std::vector< OUString > maSoundList;
+ bool mbHasText;
+ const STLPropertySet* mpSet;
+ css::uno::Reference<css::media::XPlayer> mxPlayer;
+
+ weld::Window* mpDialog;
+ std::unique_ptr<weld::Builder> mxBuilder;
+ std::unique_ptr<weld::Container> mxContainer;
+ std::unique_ptr<weld::Widget> mxSettings;
+ std::unique_ptr<weld::Label> mxFTProperty1;
+ std::unique_ptr<weld::Container> mxPlaceholderBox;
+ std::unique_ptr<weld::CheckButton> mxCBSmoothStart;
+ std::unique_ptr<weld::CheckButton> mxCBSmoothEnd;
+ std::unique_ptr<weld::Label> mxFTSound;
+ std::unique_ptr<weld::ComboBox> mxLBSound;
+ std::unique_ptr<weld::Button> mxPBSoundPreview;
+ std::unique_ptr<weld::Label> mxFTAfterEffect;
+ std::unique_ptr<weld::ComboBox> mxLBAfterEffect;
+ std::unique_ptr<weld::Label> mxFTDimColor;
+ std::unique_ptr<ColorListBox> mxCLBDimColor;
+ std::unique_ptr<weld::Label> mxFTTextAnim;
+ std::unique_ptr<weld::ComboBox> mxLBTextAnim;
+ std::unique_ptr<weld::MetricSpinButton> mxMFTextDelay;
+ std::unique_ptr<weld::Label> mxFTTextDelay;
+ std::unique_ptr<SdPropertySubControl> mxLBSubControl;
+};
+
+CustomAnimationEffectTabPage::CustomAnimationEffectTabPage(weld::Container* pParent, weld::Window* pDialog, const STLPropertySet* pSet)
+ : mbHasText(false)
+ , mpSet(pSet)
+ , mpDialog(pDialog)
+ , mxBuilder(Application::CreateBuilder(pParent, "modules/simpress/ui/customanimationeffecttab.ui"))
+ , mxContainer(mxBuilder->weld_container("EffectTab"))
+ , mxSettings(mxBuilder->weld_widget("settings"))
+ , mxFTProperty1(mxBuilder->weld_label("prop_label1"))
+ , mxPlaceholderBox(mxBuilder->weld_container("placeholder"))
+ , mxCBSmoothStart(mxBuilder->weld_check_button("smooth_start"))
+ , mxCBSmoothEnd(mxBuilder->weld_check_button("smooth_end"))
+ , mxFTSound(mxBuilder->weld_label("sound_label"))
+ , mxLBSound(mxBuilder->weld_combo_box("sound_list"))
+ , mxPBSoundPreview(mxBuilder->weld_button("sound_preview"))
+ , mxFTAfterEffect(mxBuilder->weld_label("aeffect_label"))
+ , mxLBAfterEffect(mxBuilder->weld_combo_box("aeffect_list"))
+ , mxFTDimColor(mxBuilder->weld_label("dim_color_label"))
+ , mxCLBDimColor(new ColorListBox(mxBuilder->weld_menu_button("dim_color_list"), [pDialog]{ return pDialog; }))
+ , mxFTTextAnim(mxBuilder->weld_label("text_animation_label"))
+ , mxLBTextAnim(mxBuilder->weld_combo_box("text_animation_list"))
+ , mxMFTextDelay(mxBuilder->weld_metric_spin_button("text_delay", FieldUnit::PERCENT))
+ , mxFTTextDelay(mxBuilder->weld_label("text_delay_label"))
+{
+ mxCLBDimColor->SelectEntry(COL_BLACK);
+
+ // fill the soundbox
+ fillSoundListBox();
+
+ mxLBSound->connect_changed(LINK(this, CustomAnimationEffectTabPage, implSelectHdl));
+ mxPBSoundPreview->connect_clicked(LINK(this, CustomAnimationEffectTabPage, implClickHdl));
+
+ // only show settings if all selected effects have the same preset-id
+ if( pSet->getPropertyState( nHandlePresetId ) != STLPropertyState::Ambiguous )
+ {
+ OUString aPresetId;
+ pSet->getPropertyValue( nHandlePresetId ) >>= aPresetId;
+
+ // property 1
+
+ if( pSet->getPropertyState( nHandleProperty1Type ) != STLPropertyState::Ambiguous )
+ {
+ sal_Int32 nType = 0;
+ pSet->getPropertyValue( nHandleProperty1Type ) >>= nType;
+
+ if( nType != nPropertyTypeNone )
+ {
+ // set ui name for property at fixed text
+ OUString aPropertyName( getPropertyName( nType ) );
+
+ if( !aPropertyName.isEmpty() )
+ {
+ mxSettings->show();
+ mxFTProperty1->set_label(aPropertyName);
+ }
+
+ // get property value
+ const Any aValue( pSet->getPropertyValue( nHandleProperty1Value ) );
+
+ // create property sub control
+ mxLBSubControl = SdPropertySubControl::create(nType, mxFTProperty1.get(), mxPlaceholderBox.get(), mpDialog, aValue, aPresetId, Link<LinkParamNone*,void>());
+ }
+ }
+
+ mxFTProperty1->set_sensitive(mxPlaceholderBox->get_sensitive());
+
+ // accelerate & decelerate
+
+ if( pSet->getPropertyState( nHandleAccelerate ) == STLPropertyState::Direct )
+ {
+ mxCBSmoothStart->show();
+ mxCBSmoothEnd->show();
+
+ double fTemp = 0.0;
+ pSet->getPropertyValue( nHandleAccelerate ) >>= fTemp;
+ mxCBSmoothStart->set_active( fTemp > 0.0 );
+
+ pSet->getPropertyValue( nHandleDecelerate ) >>= fTemp;
+ mxCBSmoothEnd->set_active( fTemp > 0.0 );
+ }
+ }
+
+ // init after effect controls
+
+ mxLBAfterEffect->connect_changed(LINK(this, CustomAnimationEffectTabPage, implSelectHdl));
+ mxLBTextAnim->connect_changed(LINK(this, CustomAnimationEffectTabPage, implSelectHdl));
+
+ if( (pSet->getPropertyState( nHandleHasAfterEffect ) != STLPropertyState::Ambiguous) &&
+ (pSet->getPropertyState( nHandleAfterEffectOnNextEffect ) != STLPropertyState::Ambiguous) &&
+ (pSet->getPropertyState( nHandleDimColor ) != STLPropertyState::Ambiguous))
+ {
+ bool bHasAfterEffect = false;
+ pSet->getPropertyValue( nHandleHasAfterEffect ) >>= bHasAfterEffect;
+
+ sal_Int32 nPos = 0;
+ if( bHasAfterEffect )
+ {
+ nPos++;
+
+ bool bAfterEffectOnNextClick = false;
+ pSet->getPropertyValue( nHandleAfterEffectOnNextEffect ) >>= bAfterEffectOnNextClick;
+ Any aDimColor( pSet->getPropertyValue( nHandleDimColor ) );
+
+ if( aDimColor.hasValue() )
+ {
+ Color aColor;
+ aDimColor >>= aColor;
+ mxCLBDimColor->SelectEntry(aColor);
+ }
+ else
+ {
+ nPos++;
+ if( bAfterEffectOnNextClick )
+ nPos++;
+ }
+ }
+
+ mxLBAfterEffect->set_active(nPos);
+ }
+
+ if( pSet->getPropertyState( nHandleHasText ) != STLPropertyState::Ambiguous )
+ pSet->getPropertyValue( nHandleHasText ) >>= mbHasText;
+
+ if( mbHasText )
+ {
+ if( pSet->getPropertyState( nHandleIterateType ) != STLPropertyState::Ambiguous)
+ {
+ int nPos = -1;
+
+ sal_Int32 nIterateType = 0;
+ pSet->getPropertyValue( nHandleIterateType ) >>= nIterateType;
+ switch( nIterateType )
+ {
+ case TextAnimationType::BY_PARAGRAPH: nPos = 0; break;
+ case TextAnimationType::BY_WORD: nPos = 1; break;
+ case TextAnimationType::BY_LETTER: nPos = 2; break;
+ }
+
+ mxLBTextAnim->set_active(nPos);
+ }
+
+ if( pSet->getPropertyState( nHandleIterateInterval ) != STLPropertyState::Default )
+ {
+ double fIterateInterval = 0.0;
+ pSet->getPropertyValue( nHandleIterateInterval ) >>= fIterateInterval;
+ mxMFTextDelay->set_value(static_cast<::tools::Long>(fIterateInterval*10), FieldUnit::NONE);
+ }
+ }
+ else
+ {
+ mxFTTextAnim->set_sensitive(false);
+ mxLBTextAnim->set_sensitive(false);
+ mxMFTextDelay->set_sensitive(false);
+ mxFTTextDelay->set_sensitive(false);
+
+ }
+
+ if( pSet->getPropertyState( nHandleSoundURL ) != STLPropertyState::Ambiguous )
+ {
+ sal_Int32 nPos = 0;
+
+ const Any aValue( pSet->getPropertyValue( nHandleSoundURL ) );
+
+ if( aValue.getValueType() == ::cppu::UnoType<sal_Bool>::get() )
+ {
+ nPos = 1;
+ }
+ else
+ {
+ OUString aSoundURL;
+ aValue >>= aSoundURL;
+
+ if( !aSoundURL.isEmpty() )
+ {
+ sal_uLong i;
+ for( i = 0; i < maSoundList.size(); i++ )
+ {
+ OUString aString = maSoundList[ i ];
+ if( aString == aSoundURL )
+ {
+ nPos = static_cast<sal_Int32>(i)+2;
+ break;
+ }
+ }
+
+ if( nPos == 0 )
+ {
+ nPos = static_cast<sal_Int32>(maSoundList.size())+2;
+ maSoundList.push_back( aSoundURL );
+ INetURLObject aURL( aSoundURL );
+ mxLBSound->insert_text(nPos, aURL.GetBase());
+ }
+ }
+ }
+
+ if( nPos != -1)
+ mxLBSound->set_active(nPos);
+ }
+
+ updateControlStates();
+
+}
+
+void CustomAnimationEffectTabPage::updateControlStates()
+{
+ auto nPos = mxLBAfterEffect->get_active();
+ mxCLBDimColor->set_sensitive( nPos == 1 );
+ mxFTDimColor->set_sensitive( nPos == 1 );
+
+ if( mbHasText )
+ {
+ nPos = mxLBTextAnim->get_active();
+ mxMFTextDelay->set_sensitive( nPos != 0 );
+ mxFTTextDelay->set_sensitive( nPos != 0 );
+ }
+
+ if (comphelper::LibreOfficeKit::isActive())
+ {
+ mxFTSound->hide();
+ mxLBSound->hide();
+ mxPBSoundPreview->hide();
+ }
+ else
+ {
+ nPos = mxLBSound->get_active();
+ mxPBSoundPreview->set_sensitive( nPos >= 2 );
+ }
+
+}
+
+IMPL_LINK(CustomAnimationEffectTabPage, implClickHdl, weld::Button&, rBtn, void)
+{
+ implHdl(&rBtn);
+}
+
+IMPL_LINK(CustomAnimationEffectTabPage, implSelectHdl, weld::ComboBox&, rListBox, void)
+{
+ implHdl(&rListBox);
+}
+
+void CustomAnimationEffectTabPage::implHdl(const weld::Widget* pControl)
+{
+ if (pControl == mxLBTextAnim.get())
+ {
+ if (mxMFTextDelay->get_value(FieldUnit::NONE) == 0)
+ mxMFTextDelay->set_value(100, FieldUnit::NONE);
+ }
+ else if (pControl == mxLBSound.get())
+ {
+ auto nPos = mxLBSound->get_active();
+ if (nPos == (mxLBSound->get_count() - 1))
+ {
+ openSoundFileDialog();
+ }
+ }
+ else if (pControl == mxPBSoundPreview.get())
+ {
+ onSoundPreview();
+ }
+
+ updateControlStates();
+}
+
+void CustomAnimationEffectTabPage::update( STLPropertySet* pSet )
+{
+ if (mxLBSubControl)
+ {
+ Any aNewValue(mxLBSubControl->getValue());
+ Any aOldValue;
+ if( mpSet->getPropertyState( nHandleProperty1Value ) != STLPropertyState::Ambiguous)
+ aOldValue = mpSet->getPropertyValue( nHandleProperty1Value );
+
+ if( aOldValue != aNewValue )
+ pSet->setPropertyValue( nHandleProperty1Value, aNewValue );
+ }
+
+ if (mxCBSmoothStart->get_visible())
+ {
+ // set selected value for accelerate if different than in original set
+
+ double fTemp = mxCBSmoothStart->get_active() ? 0.5 : 0.0;
+
+ double fOldTemp = 0.0;
+ if(mpSet->getPropertyState( nHandleAccelerate ) != STLPropertyState::Ambiguous)
+ mpSet->getPropertyValue( nHandleAccelerate ) >>= fOldTemp;
+ else
+ fOldTemp = -2.0;
+
+ if( fOldTemp != fTemp )
+ pSet->setPropertyValue( nHandleAccelerate, Any( fTemp ) );
+
+ // set selected value for decelerate if different than in original set
+ fTemp = mxCBSmoothEnd->get_active() ? 0.5 : 0.0;
+
+ if(mpSet->getPropertyState( nHandleDecelerate ) != STLPropertyState::Ambiguous)
+ mpSet->getPropertyValue( nHandleDecelerate ) >>= fOldTemp;
+ else
+ fOldTemp = -2.0;
+
+ if( fOldTemp != fTemp )
+ pSet->setPropertyValue( nHandleDecelerate, Any( fTemp ) );
+ }
+
+ auto nPos = mxLBAfterEffect->get_active();
+ if (nPos != -1)
+ {
+ bool bAfterEffect = nPos != 0;
+
+ bool bOldAfterEffect = false;
+
+ if(mpSet->getPropertyState( nHandleHasAfterEffect ) != STLPropertyState::Ambiguous)
+ mpSet->getPropertyValue( nHandleHasAfterEffect ) >>= bOldAfterEffect;
+ else
+ bOldAfterEffect = !bAfterEffect;
+
+ if( bOldAfterEffect != bAfterEffect )
+ pSet->setPropertyValue( nHandleHasAfterEffect, Any( bAfterEffect ) );
+
+ Any aDimColor;
+ if( nPos == 1 )
+ {
+ Color aSelectedColor = mxCLBDimColor->GetSelectEntryColor();
+ aDimColor <<= aSelectedColor.GetRGBColor();
+ }
+
+ if( (mpSet->getPropertyState( nHandleDimColor ) == STLPropertyState::Ambiguous) ||
+ (mpSet->getPropertyValue( nHandleDimColor ) != aDimColor) )
+ pSet->setPropertyValue( nHandleDimColor, aDimColor );
+
+ bool bAfterEffectOnNextEffect = nPos != 2;
+ bool bOldAfterEffectOnNextEffect = !bAfterEffectOnNextEffect;
+
+ if( mpSet->getPropertyState( nHandleAfterEffectOnNextEffect ) != STLPropertyState::Ambiguous)
+ mpSet->getPropertyValue( nHandleAfterEffectOnNextEffect ) >>= bOldAfterEffectOnNextEffect;
+
+ if( bAfterEffectOnNextEffect != bOldAfterEffectOnNextEffect )
+ pSet->setPropertyValue( nHandleAfterEffectOnNextEffect, Any( bAfterEffectOnNextEffect ) );
+ }
+
+ nPos = mxLBTextAnim->get_active();
+ if (nPos != -1)
+ {
+ sal_Int16 nIterateType;
+
+ switch( nPos )
+ {
+ case 1: nIterateType = TextAnimationType::BY_WORD; break;
+ case 2: nIterateType = TextAnimationType::BY_LETTER; break;
+ default:
+ nIterateType = TextAnimationType::BY_PARAGRAPH;
+ }
+
+ sal_Int16 nOldIterateType = nIterateType-1;
+
+ if(mpSet->getPropertyState( nHandleIterateType ) != STLPropertyState::Ambiguous)
+ mpSet->getPropertyValue( nHandleIterateType ) >>= nOldIterateType;
+
+ if( nIterateType != nOldIterateType )
+ pSet->setPropertyValue( nHandleIterateType, Any( nIterateType ) );
+ }
+
+ {
+ double fIterateInterval = static_cast<double>(mxMFTextDelay->get_value(FieldUnit::NONE)) / 10;
+ double fOldIterateInterval = -1.0;
+
+ if( mpSet->getPropertyState( nHandleIterateInterval ) != STLPropertyState::Ambiguous )
+ mpSet->getPropertyValue( nHandleIterateInterval ) >>= fOldIterateInterval;
+
+ if( fIterateInterval != fOldIterateInterval )
+ pSet->setPropertyValue( nHandleIterateInterval, Any( fIterateInterval ) );
+ }
+
+ nPos = mxLBSound->get_active();
+ if (nPos == -1)
+ return;
+
+ Any aNewSoundURL, aOldSoundURL( Any( sal_Int32(0) ) );
+
+ if( nPos == 0 )
+ {
+ // 0 means no sound, so leave any empty
+ }
+ else if( nPos == 1 )
+ {
+ // this means stop sound
+ aNewSoundURL <<= true;
+ }
+ else
+ {
+ OUString aSoundURL( maSoundList[ nPos-2 ] );
+ aNewSoundURL <<= aSoundURL;
+ }
+
+ if( mpSet->getPropertyState( nHandleSoundURL ) != STLPropertyState::Ambiguous )
+ aOldSoundURL = mpSet->getPropertyValue( nHandleSoundURL );
+
+ if( aNewSoundURL != aOldSoundURL )
+ pSet->setPropertyValue( nHandleSoundURL, aNewSoundURL );
+}
+
+void CustomAnimationEffectTabPage::fillSoundListBox()
+{
+ GalleryExplorer::FillObjList( GALLERY_THEME_SOUNDS, maSoundList );
+ GalleryExplorer::FillObjList( GALLERY_THEME_USERSOUNDS, maSoundList );
+
+ mxLBSound->append_text( SdResId(STR_CUSTOMANIMATION_NO_SOUND) );
+ mxLBSound->append_text( SdResId(STR_CUSTOMANIMATION_STOP_PREVIOUS_SOUND) );
+ for(const OUString & rString : maSoundList)
+ {
+ INetURLObject aURL( rString );
+ mxLBSound->append_text( aURL.GetBase() );
+ }
+ mxLBSound->append_text( SdResId(STR_CUSTOMANIMATION_BROWSE_SOUND) );
+}
+
+void CustomAnimationEffectTabPage::clearSoundListBox()
+{
+ maSoundList.clear();
+ mxLBSound->clear();
+}
+
+sal_Int32 CustomAnimationEffectTabPage::getSoundObject( std::u16string_view rStr )
+{
+ size_t i;
+ const size_t nCount = maSoundList.size();
+ for( i = 0; i < nCount; i++ )
+ {
+ if( maSoundList[ i ].equalsIgnoreAsciiCase(rStr) )
+ return i+2;
+ }
+
+ return -1;
+}
+
+void CustomAnimationEffectTabPage::openSoundFileDialog()
+{
+ SdOpenSoundFileDialog aFileDialog(GetFrameWeld());
+
+ bool bValidSoundFile = false;
+ bool bQuitLoop = false;
+ ::tools::Long nPos = 0;
+
+ while( !bQuitLoop && (aFileDialog.Execute() == ERRCODE_NONE) )
+ {
+ OUString aFile = aFileDialog.GetPath();
+ nPos = getSoundObject( aFile );
+
+ if( nPos < 0 ) // not in Soundliste
+ {
+ // try to insert in Gallery
+ if( GalleryExplorer::InsertURL( GALLERY_THEME_USERSOUNDS, aFile ) )
+ {
+ clearSoundListBox();
+ fillSoundListBox();
+
+ nPos = getSoundObject( aFile );
+ DBG_ASSERT( nPos >= 0, "sd::CustomAnimationEffectTabPage::openSoundFileDialog(), Recently inserted sound not in list!" );
+
+ bValidSoundFile=true;
+ bQuitLoop=true;
+ }
+ else
+ {
+ OUString aStrWarning(SdResId(STR_WARNING_NOSOUNDFILE));
+ aStrWarning = aStrWarning.replaceFirst("%", aFile);
+ std::unique_ptr<weld::MessageDialog> xWarn(Application::CreateMessageDialog(nullptr,
+ VclMessageType::Warning, VclButtonsType::NONE,
+ aStrWarning));
+ xWarn->add_button(GetStandardText(StandardButtonType::Retry), RET_RETRY);
+ xWarn->add_button(GetStandardText(StandardButtonType::Cancel), RET_CANCEL);
+ bQuitLoop = xWarn->run() != RET_RETRY;
+
+ bValidSoundFile=false;
+ }
+ }
+ else
+ {
+ bValidSoundFile=true;
+ bQuitLoop=true;
+ }
+ }
+
+ if( !bValidSoundFile )
+ nPos = 0;
+
+ mxLBSound->set_active(nPos);
+}
+
+void CustomAnimationEffectTabPage::onSoundPreview()
+{
+#if HAVE_FEATURE_AVMEDIA
+ const auto nPos = mxLBSound->get_active();
+
+ if( nPos >= 2 ) try
+ {
+ const OUString aSoundURL( maSoundList[ nPos-2 ] );
+ mxPlayer.set( avmedia::MediaWindow::createPlayer( aSoundURL, "" ), uno::UNO_SET_THROW );
+ mxPlayer->start();
+ }
+ catch( uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION( "sd", "CustomAnimationEffectTabPage::onSoundPreview()" );
+ }
+#endif
+}
+
+class CustomAnimationDurationTabPage
+{
+public:
+ CustomAnimationDurationTabPage(weld::Container* pParent, const STLPropertySet* pSet);
+
+ void update( STLPropertySet* pSet );
+
+ DECL_LINK(implControlHdl, weld::ComboBox&, void);
+ DECL_LINK(DurationModifiedHdl, weld::MetricSpinButton&, void);
+
+private:
+ const STLPropertySet* mpSet;
+
+ std::unique_ptr<weld::Builder> mxBuilder;
+ std::unique_ptr<weld::Container> mxContainer;
+ std::unique_ptr<weld::Label> mxFTStart;
+ std::unique_ptr<weld::ComboBox> mxLBStart;
+ std::unique_ptr<weld::Label> mxFTStartDelay;
+ std::unique_ptr<weld::MetricSpinButton> mxMFStartDelay;
+ std::unique_ptr<weld::Label> mxFTDuration;
+ std::unique_ptr<weld::MetricSpinButton> mxCBXDuration;
+ std::unique_ptr<weld::Label> mxFTRepeat;
+ std::unique_ptr<weld::ComboBox> mxCBRepeat;
+ std::unique_ptr<weld::CheckButton> mxCBXRewind;
+ std::unique_ptr<weld::RadioButton> mxRBClickSequence;
+ std::unique_ptr<weld::RadioButton> mxRBInteractive;
+ std::unique_ptr<weld::ComboBox> mxLBTrigger;
+};
+
+CustomAnimationDurationTabPage::CustomAnimationDurationTabPage(weld::Container* pParent, const STLPropertySet* pSet)
+ : mpSet(pSet)
+ , mxBuilder(Application::CreateBuilder(pParent, "modules/simpress/ui/customanimationtimingtab.ui"))
+ , mxContainer(mxBuilder->weld_container("TimingTab"))
+ , mxFTStart(mxBuilder->weld_label("start_label"))
+ , mxLBStart(mxBuilder->weld_combo_box("start_list"))
+ , mxFTStartDelay(mxBuilder->weld_label("delay_label"))
+ , mxMFStartDelay(mxBuilder->weld_metric_spin_button("delay_value", FieldUnit::SECOND))
+ , mxFTDuration(mxBuilder->weld_label("duration_label"))
+ , mxCBXDuration(mxBuilder->weld_metric_spin_button("anim_duration", FieldUnit::SECOND))
+ , mxFTRepeat(mxBuilder->weld_label("repeat_label"))
+ , mxCBRepeat(mxBuilder->weld_combo_box("repeat_list"))
+ , mxCBXRewind(mxBuilder->weld_check_button("rewind"))
+ , mxRBClickSequence(mxBuilder->weld_radio_button("rb_click_sequence"))
+ , mxRBInteractive(mxBuilder->weld_radio_button("rb_interactive"))
+ , mxLBTrigger(mxBuilder->weld_combo_box("trigger_list"))
+{
+ mxLBTrigger->set_size_request(mxLBTrigger->get_approximate_digit_width() * 20, -1);
+
+ fillRepeatComboBox(*mxCBRepeat);
+
+ mxLBTrigger->connect_changed(LINK(this, CustomAnimationDurationTabPage, implControlHdl));
+ mxCBXDuration->connect_value_changed(LINK( this, CustomAnimationDurationTabPage, DurationModifiedHdl));
+
+ if( pSet->getPropertyState( nHandleStart ) != STLPropertyState::Ambiguous )
+ {
+ sal_Int16 nStart = 0;
+ pSet->getPropertyValue( nHandleStart ) >>= nStart;
+ sal_Int32 nPos = 0;
+ switch( nStart )
+ {
+ case EffectNodeType::WITH_PREVIOUS: nPos = 1; break;
+ case EffectNodeType::AFTER_PREVIOUS: nPos = 2; break;
+ }
+ mxLBStart->set_active(nPos);
+ }
+
+ if( pSet->getPropertyState( nHandleBegin ) != STLPropertyState::Ambiguous )
+ {
+ double fBegin = 0.0;
+ pSet->getPropertyValue( nHandleBegin ) >>= fBegin;
+ mxMFStartDelay->set_value(static_cast<::tools::Long>(fBegin*10), FieldUnit::NONE);
+ }
+
+ if( pSet->getPropertyState( nHandleDuration ) != STLPropertyState::Ambiguous )
+ {
+ double fDuration = 0.0;
+ pSet->getPropertyValue( nHandleDuration ) >>= fDuration;
+
+ if( fDuration == 0.001 )
+ {
+ mxFTDuration->set_sensitive(false);
+ mxCBXDuration->set_sensitive(false);
+ mxFTRepeat->set_sensitive(false);
+ mxCBRepeat->set_sensitive(false);
+ mxCBXRewind->set_sensitive(false);
+ }
+ else
+ {
+ mxCBXDuration->set_value(fDuration * 100.0, FieldUnit::NONE);
+ }
+ }
+
+ if( pSet->getPropertyState( nHandleRepeat ) != STLPropertyState::Ambiguous )
+ {
+ Any aRepeatCount( pSet->getPropertyValue( nHandleRepeat ) );
+ if( (aRepeatCount.getValueType() == ::cppu::UnoType<double>::get()) || !aRepeatCount.hasValue() )
+ {
+ double fRepeat = 0.0;
+ if( aRepeatCount.hasValue() )
+ aRepeatCount >>= fRepeat;
+
+ auto nPos = -1;
+
+ if( fRepeat == 0 )
+ nPos = 0;
+ else if( fRepeat == 2.0 )
+ nPos = 1;
+ else if( fRepeat == 3.0 )
+ nPos = 2;
+ else if( fRepeat == 4.0 )
+ nPos = 3;
+ else if( fRepeat == 5.0 )
+ nPos = 4;
+ else if( fRepeat == 10.0 )
+ nPos = 5;
+
+ if (nPos != -1)
+ mxCBRepeat->set_active(nPos);
+ else
+ mxCBRepeat->set_entry_text(OUString::number(fRepeat));
+ }
+ else if( aRepeatCount.getValueType() == ::cppu::UnoType<Timing>::get() )
+ {
+ Any aEnd;
+ if( pSet->getPropertyState( nHandleEnd ) != STLPropertyState::Ambiguous )
+ aEnd = pSet->getPropertyValue( nHandleEnd );
+
+ mxCBRepeat->set_active(aEnd.hasValue() ? 6 : 7);
+ }
+ }
+
+ if( pSet->getPropertyState( nHandleRewind ) != STLPropertyState::Ambiguous )
+ {
+ sal_Int16 nFill = 0;
+ if( pSet->getPropertyValue( nHandleRewind ) >>= nFill )
+ {
+ mxCBXRewind->set_active(nFill == AnimationFill::REMOVE);
+ }
+ else
+ {
+ mxCBXRewind->set_state(TRISTATE_INDET);
+ }
+ }
+
+ Reference< XShape > xTrigger;
+
+ if( pSet->getPropertyState( nHandleTrigger ) != STLPropertyState::Ambiguous )
+ {
+ pSet->getPropertyValue( nHandleTrigger ) >>= xTrigger;
+
+ mxRBInteractive->set_active(xTrigger.is());
+ mxRBClickSequence->set_active(!xTrigger.is());
+ }
+
+ Reference< XDrawPage > xCurrentPage;
+ pSet->getPropertyValue( nHandleCurrentPage ) >>= xCurrentPage;
+ if( !xCurrentPage.is() )
+ return;
+
+ static const OUStringLiteral aStrIsEmptyPresObj( u"IsEmptyPresentationObject" );
+
+ sal_Int32 nShape, nCount = xCurrentPage->getCount();
+ for( nShape = 0; nShape < nCount; nShape++ )
+ {
+ Reference< XShape > xShape( xCurrentPage->getByIndex( nShape ), UNO_QUERY );
+
+ if( !xShape.is() )
+ continue;
+
+ Reference< XPropertySet > xSet( xShape, UNO_QUERY );
+ if( xSet.is() && xSet->getPropertySetInfo()->hasPropertyByName( aStrIsEmptyPresObj ) )
+ {
+ bool bIsEmpty = false;
+ xSet->getPropertyValue( aStrIsEmptyPresObj ) >>= bIsEmpty;
+ if( bIsEmpty )
+ continue;
+ }
+
+ OUString aDescription( getShapeDescription( xShape, true ) );
+ mxLBTrigger->append(OUString::number(nShape), aDescription);
+ auto nPos = mxLBTrigger->get_count() - 1;
+ if (xShape == xTrigger)
+ mxLBTrigger->set_active(nPos);
+ }
+}
+
+IMPL_LINK_NOARG(CustomAnimationDurationTabPage, implControlHdl, weld::ComboBox&, void)
+{
+ mxRBInteractive->set_active(true);
+ assert(!mxRBClickSequence->get_active());
+}
+
+IMPL_LINK_NOARG(CustomAnimationDurationTabPage, 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);
+ else
+ mxCBXDuration->set_value(duration_value, FieldUnit::NONE);
+ }
+}
+
+void CustomAnimationDurationTabPage::update( STLPropertySet* pSet )
+{
+ auto nPos = mxLBStart->get_active();
+ if (nPos != -1)
+ {
+ sal_Int16 nStart;
+ sal_Int16 nOldStart = -1;
+
+ switch( nPos )
+ {
+ case 1: nStart = EffectNodeType::WITH_PREVIOUS; break;
+ case 2: nStart = EffectNodeType::AFTER_PREVIOUS; break;
+ default:
+ nStart = EffectNodeType::ON_CLICK; break;
+ }
+
+ if(mpSet->getPropertyState( nHandleStart ) != STLPropertyState::Ambiguous)
+ mpSet->getPropertyValue( nHandleStart ) >>= nOldStart;
+
+ if( nStart != nOldStart )
+ pSet->setPropertyValue( nHandleStart, Any( nStart ) );
+ }
+
+ {
+ double fBegin = static_cast<double>(mxMFStartDelay->get_value(FieldUnit::NONE)) / 10.0;
+ double fOldBegin = -1.0;
+
+ if( mpSet->getPropertyState( nHandleBegin ) != STLPropertyState::Ambiguous )
+ mpSet->getPropertyValue( nHandleBegin ) >>= fOldBegin;
+
+ if( fBegin != fOldBegin )
+ pSet->setPropertyValue( nHandleBegin, Any( fBegin ) );
+ }
+
+ nPos = mxCBRepeat->get_active();
+ if (nPos != -1 || !mxCBRepeat->get_active_text().isEmpty())
+ {
+ Any aRepeatCount;
+ Any aEnd;
+
+ switch( nPos )
+ {
+ case 0:
+ break;
+
+ case 6:
+ {
+ Event aEvent;
+ aEvent.Trigger = EventTrigger::ON_NEXT;
+ aEvent.Repeat = 0;
+ aEnd <<= aEvent;
+ }
+ [[fallthrough]];
+ case 7:
+ aRepeatCount <<= Timing_INDEFINITE;
+ break;
+ default:
+ {
+ OUString aText(mxCBRepeat->get_text(nPos));
+ if( !aText.isEmpty() )
+ aRepeatCount <<= aText.toDouble();
+ }
+ }
+
+ Any aOldRepeatCount( aRepeatCount );
+ if( mpSet->getPropertyState( nHandleRepeat ) != STLPropertyState::Ambiguous )
+ aOldRepeatCount = mpSet->getPropertyValue( nHandleRepeat );
+
+ if( aRepeatCount != aOldRepeatCount )
+ pSet->setPropertyValue( nHandleRepeat, aRepeatCount );
+
+ Any aOldEnd( aEnd );
+ if( mpSet->getPropertyState( nHandleEnd ) != STLPropertyState::Ambiguous )
+ aOldEnd = mpSet->getPropertyValue( nHandleEnd );
+
+ if( aEnd != aOldEnd )
+ pSet->setPropertyValue( nHandleEnd, aEnd );
+ }
+
+ double fDuration = -1.0;
+
+ if (!mxCBXDuration->get_text().isEmpty())
+ {
+ double duration_value = static_cast<double>(mxCBXDuration->get_value(FieldUnit::NONE));
+
+ if(duration_value > 0)
+ fDuration = duration_value/100.0;
+ }
+
+ if( fDuration != -1.0 )
+ {
+ double fOldDuration = -1;
+
+ if( mpSet->getPropertyState( nHandleDuration ) != STLPropertyState::Ambiguous )
+ mpSet->getPropertyValue( nHandleDuration ) >>= fOldDuration;
+
+ if( fDuration != fOldDuration )
+ pSet->setPropertyValue( nHandleDuration, Any( fDuration ) );
+ }
+
+ if (mxCBXRewind->get_state() != TRISTATE_INDET)
+ {
+ sal_Int16 nFill = mxCBXRewind->get_active() ? AnimationFill::REMOVE : AnimationFill::HOLD;
+
+ bool bSet = true;
+
+ if( mpSet->getPropertyState( nHandleRewind ) != STLPropertyState::Ambiguous )
+ {
+ sal_Int16 nOldFill = 0;
+ mpSet->getPropertyValue( nHandleRewind ) >>= nOldFill;
+ bSet = nFill != nOldFill;
+ }
+
+ if( bSet )
+ pSet->setPropertyValue( nHandleRewind, Any( nFill ) );
+ }
+
+ Reference< XShape > xTrigger;
+
+ if (mxRBInteractive->get_active())
+ {
+ nPos = mxLBTrigger->get_active();
+ if (nPos != -1)
+ {
+ sal_Int32 nShape = mxLBTrigger->get_id(nPos).toInt32();
+
+ Reference< XDrawPage > xCurrentPage;
+ mpSet->getPropertyValue( nHandleCurrentPage ) >>= xCurrentPage;
+
+ if( xCurrentPage.is() && (nShape >= 0) && (nShape < xCurrentPage->getCount()) )
+ xCurrentPage->getByIndex( nShape ) >>= xTrigger;
+ }
+ }
+
+ if (xTrigger.is() || mxRBClickSequence->get_active())
+ {
+ Any aNewValue( xTrigger );
+ Any aOldValue;
+
+ if( mpSet->getPropertyState( nHandleTrigger ) != STLPropertyState::Ambiguous )
+ aOldValue = mpSet->getPropertyValue( nHandleTrigger );
+
+ if( aNewValue != aOldValue )
+ pSet->setPropertyValue( nHandleTrigger, aNewValue );
+ }
+}
+
+class CustomAnimationTextAnimTabPage
+{
+public:
+ CustomAnimationTextAnimTabPage(weld::Container* pParent, const STLPropertySet* pSet);
+
+ void update( STLPropertySet* pSet );
+
+ void updateControlStates();
+ DECL_LINK(implSelectHdl, weld::ComboBox&, void);
+
+private:
+ const STLPropertySet* mpSet;
+ bool mbHasVisibleShapes;
+
+ std::unique_ptr<weld::Builder> mxBuilder;
+ std::unique_ptr<weld::Container> mxContainer;
+ std::unique_ptr<weld::Label> mxFTGroupText;
+ std::unique_ptr<weld::ComboBox> mxLBGroupText;
+ std::unique_ptr<weld::CheckButton> mxCBXGroupAuto;
+ std::unique_ptr<weld::MetricSpinButton> mxMFGroupAuto;
+ std::unique_ptr<weld::CheckButton> mxCBXAnimateForm;
+ std::unique_ptr<weld::CheckButton> mxCBXReverse;
+};
+
+CustomAnimationTextAnimTabPage::CustomAnimationTextAnimTabPage(weld::Container* pParent, const STLPropertySet* pSet)
+ : mpSet(pSet)
+ , mbHasVisibleShapes(true)
+ , mxBuilder(Application::CreateBuilder(pParent, "modules/simpress/ui/customanimationtexttab.ui"))
+ , mxContainer(mxBuilder->weld_container("TextAnimationTab"))
+ , mxFTGroupText(mxBuilder->weld_label("group_text_label"))
+ , mxLBGroupText(mxBuilder->weld_combo_box("group_text_list"))
+ , mxCBXGroupAuto(mxBuilder->weld_check_button("auto_after"))
+ , mxMFGroupAuto(mxBuilder->weld_metric_spin_button("auto_after_value",FieldUnit::SECOND))
+ , mxCBXAnimateForm(mxBuilder->weld_check_button("animate_shape"))
+ , mxCBXReverse(mxBuilder->weld_check_button("reverse_order"))
+{
+ mxLBGroupText->connect_changed(LINK(this, CustomAnimationTextAnimTabPage, implSelectHdl));
+
+ if( pSet->getPropertyState( nHandleTextGrouping ) != STLPropertyState::Ambiguous )
+ {
+ sal_Int32 nTextGrouping = 0;
+ if( pSet->getPropertyValue( nHandleTextGrouping ) >>= nTextGrouping )
+ mxLBGroupText->set_active(nTextGrouping + 1);
+ }
+
+ if( pSet->getPropertyState( nHandleHasVisibleShape ) != STLPropertyState::Ambiguous )
+ pSet->getPropertyValue( nHandleHasVisibleShape ) >>= mbHasVisibleShapes;
+
+ if( pSet->getPropertyState( nHandleTextGroupingAuto ) != STLPropertyState::Ambiguous )
+ {
+ double fTextGroupingAuto = 0.0;
+ if( pSet->getPropertyValue( nHandleTextGroupingAuto ) >>= fTextGroupingAuto )
+ {
+ mxCBXGroupAuto->set_active(fTextGroupingAuto >= 0.0);
+ if( fTextGroupingAuto >= 0.0 )
+ mxMFGroupAuto->set_value(static_cast<::tools::Long>(fTextGroupingAuto*10), FieldUnit::NONE);
+ }
+ }
+ else
+ {
+ mxCBXGroupAuto->set_state( TRISTATE_INDET );
+ }
+
+ mxCBXAnimateForm->set_state( TRISTATE_INDET );
+ if( pSet->getPropertyState( nHandleAnimateForm ) != STLPropertyState::Ambiguous )
+ {
+ bool bAnimateForm = false;
+ if( pSet->getPropertyValue( nHandleAnimateForm ) >>= bAnimateForm )
+ {
+ mxCBXAnimateForm->set_active( bAnimateForm );
+ }
+ }
+ else
+ {
+ mxCBXAnimateForm->set_sensitive(false);
+ }
+
+ mxCBXReverse->set_state(TRISTATE_INDET);
+ if( pSet->getPropertyState( nHandleTextReverse ) != STLPropertyState::Ambiguous )
+ {
+ bool bTextReverse = false;
+ if( pSet->getPropertyValue( nHandleTextReverse ) >>= bTextReverse )
+ {
+ mxCBXReverse->set_active( bTextReverse );
+ }
+ }
+
+ if( pSet->getPropertyState( nHandleMaxParaDepth ) == STLPropertyState::Direct )
+ {
+ sal_Int32 nMaxParaDepth = 0;
+ pSet->getPropertyValue( nHandleMaxParaDepth ) >>= nMaxParaDepth;
+ nMaxParaDepth += 1;
+
+ sal_Int32 nPos = 6;
+ while( (nPos > 2) && (nPos > nMaxParaDepth) )
+ {
+ mxLBGroupText->remove(nPos);
+ nPos--;
+ }
+ }
+
+ updateControlStates();
+}
+
+void CustomAnimationTextAnimTabPage::update( STLPropertySet* pSet )
+{
+ auto nPos = mxLBGroupText->get_active();
+ if (nPos != -1)
+ {
+ sal_Int32 nTextGrouping = nPos - 1;
+ sal_Int32 nOldGrouping = -2;
+
+ if(mpSet->getPropertyState( nHandleTextGrouping ) != STLPropertyState::Ambiguous)
+ mpSet->getPropertyValue( nHandleTextGrouping ) >>= nOldGrouping;
+
+ if( nTextGrouping != nOldGrouping )
+ pSet->setPropertyValue( nHandleTextGrouping, Any( nTextGrouping ) );
+ }
+
+ if (nPos != 0)
+ {
+ bool bTextReverse = mxCBXReverse->get_active();
+ bool bOldTextReverse = !bTextReverse;
+
+ if(mpSet->getPropertyState( nHandleTextReverse ) != STLPropertyState::Ambiguous)
+ mpSet->getPropertyValue( nHandleTextReverse ) >>= bOldTextReverse;
+
+ if( bTextReverse != bOldTextReverse )
+ pSet->setPropertyValue( nHandleTextReverse, Any( bTextReverse ) );
+
+ if( nPos > 1 )
+ {
+ double fTextGroupingAuto = mxCBXGroupAuto->get_active() ? mxMFGroupAuto->get_value(FieldUnit::NONE) / 10.0 : -1.0;
+ double fOldTextGroupingAuto = -2.0;
+
+ if(mpSet->getPropertyState( nHandleTextGroupingAuto ) != STLPropertyState::Ambiguous)
+ mpSet->getPropertyValue( nHandleTextGroupingAuto ) >>= fOldTextGroupingAuto;
+
+ if( fTextGroupingAuto != fOldTextGroupingAuto )
+ pSet->setPropertyValue( nHandleTextGroupingAuto, Any( fTextGroupingAuto ) );
+ }
+ }
+ //#i120049# impress crashes when modifying the "Random effects" animation
+ //effect's trigger condition to "Start effect on click of".
+ //If this control is disabled, we should ignore its value
+ if (mxCBXAnimateForm->get_sensitive())
+ {
+ bool bAnimateForm = mxCBXAnimateForm->get_active();
+ bool bOldAnimateForm = !bAnimateForm;
+
+ if(mpSet->getPropertyState( nHandleAnimateForm ) != STLPropertyState::Ambiguous)
+ mpSet->getPropertyValue( nHandleAnimateForm ) >>= bOldAnimateForm;
+
+ if( bAnimateForm != bOldAnimateForm )
+ pSet->setPropertyValue( nHandleAnimateForm, Any( bAnimateForm ) );
+ }
+}
+
+void CustomAnimationTextAnimTabPage::updateControlStates()
+{
+ auto nPos = mxLBGroupText->get_active();
+
+ mxCBXGroupAuto->set_sensitive( nPos > 1 );
+ mxMFGroupAuto->set_sensitive( nPos > 1 );
+ mxCBXReverse->set_sensitive( nPos > 0 );
+
+ if( !mbHasVisibleShapes && nPos > 0 )
+ {
+ mxCBXAnimateForm->set_active(false);
+ mxCBXAnimateForm->set_sensitive(false);
+ }
+ else
+ {
+ mxCBXAnimateForm->set_sensitive(true);
+ }
+}
+
+IMPL_LINK_NOARG(CustomAnimationTextAnimTabPage, implSelectHdl, weld::ComboBox&, void)
+{
+ updateControlStates();
+}
+
+CustomAnimationDialog::CustomAnimationDialog(weld::Window* pParent, std::unique_ptr<STLPropertySet> pSet, const OString& rPage)
+ : GenericDialogController(pParent, "modules/simpress/ui/customanimationproperties.ui", "CustomAnimationProperties")
+ , mxSet(std::move(pSet))
+ , mxTabControl(m_xBuilder->weld_notebook("tabcontrol"))
+ , mxDurationTabPage(new CustomAnimationDurationTabPage(mxTabControl->get_page("timing"), mxSet.get()))
+ , mxEffectTabPage(new CustomAnimationEffectTabPage(mxTabControl->get_page("effect"), m_xDialog.get(), mxSet.get()))
+{
+ bool bHasText = false;
+ if( mxSet->getPropertyState( nHandleHasText ) != STLPropertyState::Ambiguous )
+ mxSet->getPropertyValue( nHandleHasText ) >>= bHasText;
+
+ if( bHasText )
+ {
+ mxTextAnimTabPage.reset(new CustomAnimationTextAnimTabPage(mxTabControl->get_page("textanim"), mxSet.get()));
+ }
+ else
+ {
+ mxTabControl->remove_page("textanim");
+ }
+
+ if (!rPage.isEmpty())
+ mxTabControl->set_current_page(rPage);
+}
+
+CustomAnimationDialog::~CustomAnimationDialog()
+{
+}
+
+STLPropertySet* CustomAnimationDialog::getResultSet()
+{
+ mxResultSet = createDefaultSet();
+
+ mxEffectTabPage->update( mxResultSet.get() );
+ mxDurationTabPage->update( mxResultSet.get() );
+ if (mxTextAnimTabPage)
+ mxTextAnimTabPage->update( mxResultSet.get() );
+
+ return mxResultSet.get();
+}
+
+std::unique_ptr<STLPropertySet> CustomAnimationDialog::createDefaultSet()
+{
+ Any aEmpty;
+
+ std::unique_ptr<STLPropertySet> pSet(new STLPropertySet());
+ pSet->setPropertyDefaultValue( nHandleMaxParaDepth, Any( sal_Int32(-1) ) );
+
+ pSet->setPropertyDefaultValue( nHandleHasAfterEffect, Any( false ) );
+ pSet->setPropertyDefaultValue( nHandleAfterEffectOnNextEffect, Any( false ) );
+ pSet->setPropertyDefaultValue( nHandleDimColor, aEmpty );
+ pSet->setPropertyDefaultValue( nHandleIterateType, Any( sal_Int16(0) ) );
+ pSet->setPropertyDefaultValue( nHandleIterateInterval, Any( 0.0 ) );
+
+ pSet->setPropertyDefaultValue( nHandleStart, Any( sal_Int16(EffectNodeType::ON_CLICK) ) );
+ pSet->setPropertyDefaultValue( nHandleBegin, Any( 0.0 ) );
+ pSet->setPropertyDefaultValue( nHandleDuration, Any( 2.0 ) );
+ pSet->setPropertyDefaultValue( nHandleRepeat, aEmpty );
+ pSet->setPropertyDefaultValue( nHandleRewind, Any( AnimationFill::HOLD ) );
+
+ pSet->setPropertyDefaultValue( nHandleEnd, aEmpty );
+
+ pSet->setPropertyDefaultValue( nHandlePresetId, aEmpty );
+ pSet->setPropertyDefaultValue( nHandleProperty1Type, Any( nPropertyTypeNone ) );
+ pSet->setPropertyDefaultValue( nHandleProperty1Value, aEmpty );
+ pSet->setPropertyDefaultValue( nHandleProperty2Type, Any( nPropertyTypeNone ) );
+ pSet->setPropertyDefaultValue( nHandleProperty2Value, aEmpty );
+ pSet->setPropertyDefaultValue( nHandleAccelerate, aEmpty );
+ pSet->setPropertyDefaultValue( nHandleDecelerate, aEmpty );
+ pSet->setPropertyDefaultValue( nHandleAutoReverse, aEmpty );
+ pSet->setPropertyDefaultValue( nHandleTrigger, aEmpty );
+
+ pSet->setPropertyDefaultValue( nHandleHasText, Any( false ) );
+ pSet->setPropertyDefaultValue( nHandleHasVisibleShape, Any( false ) );
+ pSet->setPropertyDefaultValue( nHandleTextGrouping, Any( sal_Int32(-1) ) );
+ pSet->setPropertyDefaultValue( nHandleAnimateForm, Any( true ) );
+ pSet->setPropertyDefaultValue( nHandleTextGroupingAuto, Any( -1.0 ) );
+ pSet->setPropertyDefaultValue( nHandleTextReverse, Any( false ) );
+
+ pSet->setPropertyDefaultValue( nHandleCurrentPage, aEmpty );
+
+ pSet->setPropertyDefaultValue( nHandleSoundURL, aEmpty );
+ pSet->setPropertyDefaultValue( nHandleSoundVolume, Any( 1.0) );
+ pSet->setPropertyDefaultValue( nHandleSoundEndAfterSlide, Any( sal_Int32(0) ) );
+
+ pSet->setPropertyDefaultValue( nHandleCommand, Any( sal_Int16(0) ) );
+ return pSet;
+}
+
+std::unique_ptr<SdPropertySubControl> SdPropertySubControl::create(sal_Int32 nType, weld::Label* pLabel, weld::Container* pParent, weld::Window* pTopLevel, const Any& rValue, const OUString& rPresetId, const Link<LinkParamNone*,void>& rModifyHdl)
+{
+ std::unique_ptr<SdPropertySubControl> pSubControl;
+ switch( nType )
+ {
+ case nPropertyTypeDirection:
+ case nPropertyTypeSpokes:
+ case nPropertyTypeZoom:
+ pSubControl.reset( new SdPresetPropertyBox( pLabel, pParent, rValue, rPresetId, rModifyHdl ) );
+ break;
+
+ case nPropertyTypeColor:
+ case nPropertyTypeFillColor:
+ case nPropertyTypeFirstColor:
+ case nPropertyTypeCharColor:
+ case nPropertyTypeLineColor:
+ pSubControl.reset( new SdColorPropertyBox( pLabel, pParent, pTopLevel, rValue, rModifyHdl ) );
+ break;
+
+ case nPropertyTypeFont:
+ pSubControl.reset( new SdFontPropertyBox( pLabel, pParent, rValue, rModifyHdl ) );
+ break;
+
+ case nPropertyTypeCharHeight:
+ pSubControl.reset( new SdCharHeightPropertyBox( pLabel, pParent, rValue, rModifyHdl ) );
+ break;
+
+ case nPropertyTypeRotate:
+ pSubControl.reset( new SdRotationPropertyBox( pLabel, pParent, rValue, rModifyHdl ) );
+ break;
+
+ case nPropertyTypeTransparency:
+ pSubControl.reset( new SdTransparencyPropertyBox( pLabel, pParent, rValue, rModifyHdl ) );
+ break;
+
+ case nPropertyTypeScale:
+ pSubControl.reset( new SdScalePropertyBox( pLabel, pParent, rValue, rModifyHdl ) );
+ break;
+
+ case nPropertyTypeCharDecoration:
+ pSubControl.reset( new SdFontStylePropertyBox( pLabel, pParent, rValue, rModifyHdl ) );
+ break;
+ }
+
+ return pSubControl;
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/animations/CustomAnimationDialog.hxx b/sd/source/ui/animations/CustomAnimationDialog.hxx
new file mode 100644
index 000000000..b8a8abcff
--- /dev/null
+++ b/sd/source/ui/animations/CustomAnimationDialog.hxx
@@ -0,0 +1,141 @@
+/* -*- 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 .
+ */
+
+#pragma once
+
+#include <vcl/weld.hxx>
+
+namespace sd {
+
+// property handles
+const sal_Int32 nHandleSound = 0;
+const sal_Int32 nHandleHasAfterEffect = 1;
+const sal_Int32 nHandleIterateType = 2;
+const sal_Int32 nHandleIterateInterval = 3;
+const sal_Int32 nHandleStart = 4;
+const sal_Int32 nHandleBegin = 5;
+const sal_Int32 nHandleDuration = 6;
+const sal_Int32 nHandleRepeat = 7;
+const sal_Int32 nHandleRewind = 8;
+const sal_Int32 nHandleEnd = 9;
+const sal_Int32 nHandleAfterEffectOnNextEffect = 10;
+const sal_Int32 nHandleDimColor = 11;
+const sal_Int32 nHandleMaxParaDepth = 12;
+const sal_Int32 nHandlePresetId = 13;
+const sal_Int32 nHandleProperty1Type = 14;
+const sal_Int32 nHandleProperty1Value = 15;
+const sal_Int32 nHandleProperty2Type = 16;
+const sal_Int32 nHandleProperty2Value = 17;
+
+const sal_Int32 nHandleAccelerate = 18;
+const sal_Int32 nHandleDecelerate = 19;
+const sal_Int32 nHandleAutoReverse = 20;
+const sal_Int32 nHandleTrigger = 21;
+
+const sal_Int32 nHandleHasText = 22;
+const sal_Int32 nHandleTextGrouping = 23;
+const sal_Int32 nHandleAnimateForm = 24;
+const sal_Int32 nHandleTextGroupingAuto = 25;
+const sal_Int32 nHandleTextReverse = 26;
+
+const sal_Int32 nHandleCurrentPage = 27;
+const sal_Int32 nHandleSoundURL = 28;
+const sal_Int32 nHandleSoundVolume = 29;
+const sal_Int32 nHandleSoundEndAfterSlide = 30;
+
+const sal_Int32 nHandleCommand = 31;
+
+const sal_Int32 nHandleHasVisibleShape = 32;
+
+const sal_Int32 nPropertyTypeNone = 0;
+const sal_Int32 nPropertyTypeDirection = 1;
+const sal_Int32 nPropertyTypeSpokes = 2;
+const sal_Int32 nPropertyTypeFirstColor = 3;
+const sal_Int32 nPropertyTypeSecondColor = 4;
+const sal_Int32 nPropertyTypeZoom = 5;
+const sal_Int32 nPropertyTypeFillColor = 6;
+const sal_Int32 nPropertyTypeColorStyle = 7;
+const sal_Int32 nPropertyTypeFont = 8;
+const sal_Int32 nPropertyTypeCharHeight = 9;
+const sal_Int32 nPropertyTypeCharColor = 10;
+const sal_Int32 nPropertyTypeCharHeightStyle = 11;
+const sal_Int32 nPropertyTypeCharDecoration = 12;
+const sal_Int32 nPropertyTypeLineColor = 13;
+const sal_Int32 nPropertyTypeRotate = 14;
+const sal_Int32 nPropertyTypeColor = 15;
+const sal_Int32 nPropertyTypeAccelerate = 16;
+const sal_Int32 nPropertyTypeDecelerate = 17;
+const sal_Int32 nPropertyTypeAutoReverse = 18;
+const sal_Int32 nPropertyTypeTransparency = 19;
+const sal_Int32 nPropertyTypeFontStyle = 20;
+const sal_Int32 nPropertyTypeScale = 21;
+
+class SdPropertySubControl
+{
+public:
+ explicit SdPropertySubControl(weld::Container* pParent);
+ virtual ~SdPropertySubControl();
+
+ virtual css::uno::Any getValue() = 0;
+ virtual void setValue( const css::uno::Any& rValue, const OUString& rPresetId ) = 0;
+
+ static std::unique_ptr<SdPropertySubControl>
+ create( sal_Int32 nType,
+ weld::Label* pLabel,
+ weld::Container* pParent,
+ weld::Window* pTopLevel,
+ const css::uno::Any& rValue,
+ const OUString& rPresetId,
+ const Link<LinkParamNone*,void>& rModifyHdl );
+
+protected:
+ std::unique_ptr<weld::Builder> mxBuilder;
+ std::unique_ptr<weld::Container> mxContainer;
+ weld::Container* mpParent;
+};
+
+class CustomAnimationDurationTabPage;
+class CustomAnimationEffectTabPage;
+class CustomAnimationTextAnimTabPage;
+class STLPropertySet;
+
+class CustomAnimationDialog : public weld::GenericDialogController
+{
+public:
+ CustomAnimationDialog(weld::Window* pParent, std::unique_ptr<STLPropertySet> pSet, const OString& Page);
+ virtual ~CustomAnimationDialog() override;
+
+ STLPropertySet* getResultSet();
+ STLPropertySet* getPropertySet() const { return mxSet.get(); }
+
+ static std::unique_ptr<STLPropertySet> createDefaultSet();
+
+private:
+ std::unique_ptr<STLPropertySet> mxSet;
+ std::unique_ptr<STLPropertySet> mxResultSet;
+
+ std::unique_ptr<weld::Notebook> mxTabControl;
+ std::unique_ptr<CustomAnimationDurationTabPage> mxDurationTabPage;
+ std::unique_ptr<CustomAnimationEffectTabPage> mxEffectTabPage;
+ std::unique_ptr<CustomAnimationTextAnimTabPage> mxTextAnimTabPage;
+};
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/animations/CustomAnimationList.cxx b/sd/source/ui/animations/CustomAnimationList.cxx
new file mode 100644
index 000000000..cc85ed74f
--- /dev/null
+++ b/sd/source/ui/animations/CustomAnimationList.cxx
@@ -0,0 +1,1231 @@
+/* -*- 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 <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 <tools/diagnose_ex.h>
+
+#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 const OUStringLiteral aPropName1(u"Name");
+ if(xInfo->hasPropertyByName(aPropName1))
+ xSet->getPropertyValue(aPropName1) >>= aDescription;
+
+ bAppendIndex = aDescription.isEmpty();
+
+ static const OUStringLiteral aPropName2(u"UINameSingular");
+ 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(const OUString& aDescription,
+ const 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(const OUString& aDescription, const CustomAnimationEffectPtr& pEffect)
+ : msDescription(aDescription)
+ , mpEffect(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());
+ if (aSize.Height() < nItemMinHeight)
+ aSize.setHeight(nItemMinHeight);
+ return aSize;
+}
+
+void CustomAnimationListEntryItem::PaintTrigger(vcl::RenderContext& rRenderContext, const ::tools::Rectangle& rRect)
+{
+ Size aSize(rRect.GetSize());
+
+ ::tools::Rectangle aOutRect(rRect);
+
+ // fill the background
+ Color aColor(rRenderContext.GetSettings().GetStyleSettings().GetDialogColor());
+
+ rRenderContext.Push();
+ rRenderContext.SetFillColor(aColor);
+ rRenderContext.SetLineColor();
+ rRenderContext.DrawRect(aOutRect);
+
+ // Erase the four corner pixels to make the rectangle appear rounded.
+ rRenderContext.SetLineColor(rRenderContext.GetSettings().GetStyleSettings().GetWindowColor());
+ rRenderContext.DrawPixel(aOutRect.TopLeft());
+ rRenderContext.DrawPixel(Point(aOutRect.Right(), aOutRect.Top()));
+ rRenderContext.DrawPixel(Point(aOutRect.Left(), aOutRect.Bottom()));
+ rRenderContext.DrawPixel(Point(aOutRect.Right(), aOutRect.Bottom()));
+
+ // draw the category title
+
+ int nVertBorder = ((aSize.Height() - rRenderContext.GetTextHeight()) >> 1);
+ int nHorzBorder = rRenderContext.LogicToPixel(Size(3, 3), MapMode(MapUnit::MapAppFont)).Width();
+
+ aOutRect.AdjustLeft(nHorzBorder );
+ aOutRect.AdjustRight( -nHorzBorder );
+ aOutRect.AdjustTop( nVertBorder );
+ aOutRect.AdjustBottom( -nVertBorder );
+
+ rRenderContext.DrawText(aOutRect, rRenderContext.GetEllipsisString(msDescription, aOutRect.GetWidth()));
+ rRenderContext.Pop();
+}
+
+void CustomAnimationListEntryItem::PaintEffect(vcl::RenderContext& rRenderContext, const ::tools::Rectangle& rRect, bool bSelected)
+{
+ rRenderContext.Push(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();
+
+ sal_Int16 nNodeType = mpEffect->getNodeType();
+ if (nNodeType == EffectNodeType::ON_CLICK )
+ {
+ rRenderContext.DrawImage(aPos, Image(StockImage::Yes, BMP_CUSTOMANIMATION_ON_CLICK));
+ }
+ else if (nNodeType == EffectNodeType::AFTER_PREVIOUS)
+ {
+ rRenderContext.DrawImage(aPos, Image(StockImage::Yes, BMP_CUSTOMANIMATION_AFTER_PREVIOUS));
+ }
+ else if (nNodeType == EffectNodeType::WITH_PREVIOUS)
+ {
+ //FIXME With previous image not defined in CustomAnimation.src
+ }
+
+ aPos.AdjustX(nIconWidth);
+
+ //TODO, full width of widget ?
+ rRenderContext.DrawText(aPos, rRenderContext.GetEllipsisString(msDescription, rRect.GetWidth()));
+
+ aPos.AdjustY(nIconWidth);
+
+ 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);
+ Point aImagePos(aPos);
+ aImagePos.AdjustY((nItemHeight/2 - aImage.GetSizePixel().Height()) >> 1 );
+ rRenderContext.DrawImage(aImagePos, aImage);
+ }
+
+ aPos.AdjustX(nIconWidth );
+ aPos.AdjustY((nItemHeight/2 - rRenderContext.GetTextHeight()) >> 1 );
+
+ 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(0, 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("remove");
+ return true;
+ case KEY_INSERT:
+ mpController->onContextMenu("create");
+ 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.getWidth() / 2, aRect.getHeight() / 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 = 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 = 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(), "modules/simpress/ui/effectmenu.ui"));
+ std::unique_ptr<weld::Menu> xMenu = xBuilder->weld_menu("menu");
+
+ 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("onclick", nNodeType == EffectNodeType::ON_CLICK);
+ xMenu->set_active("withprev", nNodeType == EffectNodeType::WITH_PREVIOUS);
+ xMenu->set_active("afterprev", nNodeType == EffectNodeType::AFTER_PREVIOUS);
+ xMenu->set_sensitive("options", nEntries == 1);
+ xMenu->set_sensitive("timing", nEntries == 1);
+
+ OString sCommand = xMenu->popup_at_rect(mxTreeView.get(), ::tools::Rectangle(rCEvt.GetMousePosPixel(), Size(1,1)));
+ if (!sCommand.isEmpty())
+ ExecuteContextMenuAction(sCommand);
+
+ return true;
+}
+
+void CustomAnimationList::ExecuteContextMenuAction(const OString& rIdent)
+{
+ mpController->onContextMenu(rIdent);
+}
+
+void CustomAnimationList::notify_change()
+{
+ update();
+ mpController->onSelect();
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
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: */
diff --git a/sd/source/ui/animations/STLPropertySet.cxx b/sd/source/ui/animations/STLPropertySet.cxx
new file mode 100644
index 000000000..592d7639c
--- /dev/null
+++ b/sd/source/ui/animations/STLPropertySet.cxx
@@ -0,0 +1,113 @@
+/* -*- 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 "STLPropertySet.hxx"
+#include <sal/log.hxx>
+
+using com::sun::star::uno::Any;
+
+namespace sd
+{
+
+STLPropertySet::STLPropertySet()
+{
+}
+
+STLPropertySet::~STLPropertySet()
+{
+}
+
+void STLPropertySet::setPropertyDefaultValue( sal_Int32 nHandle, const Any& rValue )
+{
+ STLPropertyMapEntry aEntry( rValue );
+ maPropertyMap[ nHandle ] = aEntry;
+}
+
+void STLPropertySet::setPropertyValue( sal_Int32 nHandle, const Any& rValue )
+{
+ PropertyMapIter aIter;
+ if( findProperty( nHandle, aIter ) )
+ {
+ (*aIter).second.mnState = STLPropertyState::Direct;
+ (*aIter).second.maValue = rValue;
+ }
+ else
+ {
+ SAL_WARN("sd", "sd::STLPropertySet::setPropertyValue(), unknown property!");
+ }
+}
+
+Any STLPropertySet::getPropertyValue( sal_Int32 nHandle ) const
+{
+ PropertyMapConstIter aIter;
+ if( findProperty( nHandle, aIter ) )
+ {
+ return (*aIter).second.maValue;
+ }
+ else
+ {
+ SAL_WARN("sd", "sd::STLPropertySet::getPropertyValue(), unknown property!");
+
+ Any aAny;
+ return aAny;
+ }
+}
+
+STLPropertyState STLPropertySet::getPropertyState( sal_Int32 nHandle ) const
+{
+ PropertyMapConstIter aIter;
+ if( findProperty( nHandle, aIter ) )
+ {
+ return (*aIter).second.mnState;
+ }
+ else
+ {
+ SAL_WARN("sd", "sd::STLPropertySet::getPropertyState(), unknown property!");
+ return STLPropertyState::Ambiguous;
+ }
+}
+
+void STLPropertySet::setPropertyState( sal_Int32 nHandle, STLPropertyState nState )
+{
+ PropertyMapIter aIter;
+ if( findProperty( nHandle, aIter ) )
+ {
+ (*aIter).second.mnState = nState;
+ }
+ else
+ {
+ SAL_WARN("sd","sd::STLPropertySet::setPropertyState(), unknown property!");
+ }
+}
+
+bool STLPropertySet::findProperty( sal_Int32 nHandle, PropertyMapIter& rIter )
+{
+ rIter = maPropertyMap.find(nHandle);
+ return( rIter != maPropertyMap.end() );
+}
+
+bool STLPropertySet::findProperty( sal_Int32 nHandle, PropertyMapConstIter& rIter ) const
+{
+ rIter = maPropertyMap.find(nHandle);
+ return( rIter != maPropertyMap.end() );
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/animations/STLPropertySet.hxx b/sd/source/ui/animations/STLPropertySet.hxx
new file mode 100644
index 000000000..3096e7c78
--- /dev/null
+++ b/sd/source/ui/animations/STLPropertySet.hxx
@@ -0,0 +1,73 @@
+/* -*- 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 .
+ */
+
+#pragma once
+
+#include <map>
+#include <com/sun/star/uno/Any.hxx>
+
+namespace sd
+{
+
+enum class STLPropertyState {
+ Default = 0,
+ Direct = 1,
+ Ambiguous = 3
+};
+
+struct STLPropertyMapEntry
+{
+ css::uno::Any maValue;
+ STLPropertyState mnState;
+
+ STLPropertyMapEntry()
+ : mnState( STLPropertyState::Ambiguous ) {}
+ explicit STLPropertyMapEntry(css::uno::Any aValue)
+ : maValue( aValue ), mnState( STLPropertyState::Default ) {}
+
+};
+
+typedef std::map<sal_Int32, STLPropertyMapEntry > PropertyMap;
+typedef PropertyMap::iterator PropertyMapIter;
+typedef PropertyMap::const_iterator PropertyMapConstIter;
+
+class STLPropertySet
+{
+public:
+ STLPropertySet();
+ ~STLPropertySet();
+
+ void setPropertyDefaultValue( sal_Int32 nHandle, const css::uno::Any& rValue );
+ void setPropertyValue( sal_Int32 nHandle, const css::uno::Any& rValue );
+ css::uno::Any getPropertyValue( sal_Int32 nHandle ) const;
+
+ STLPropertyState getPropertyState( sal_Int32 nHandle ) const;
+ void setPropertyState( sal_Int32 nHandle, STLPropertyState nState );
+
+private:
+ bool findProperty( sal_Int32 nHandle, PropertyMapIter& rIter );
+ bool findProperty( sal_Int32 nHandle, PropertyMapConstIter& rIter ) const;
+
+private:
+ PropertyMap maPropertyMap;
+};
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/animations/SlideTransitionPane.cxx b/sd/source/ui/animations/SlideTransitionPane.cxx
new file mode 100644
index 000000000..846f21c34
--- /dev/null
+++ b/sd/source/ui/animations/SlideTransitionPane.cxx
@@ -0,0 +1,1155 @@
+/* -*- 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/drawing/XDrawView.hpp>
+#include <SlideTransitionPane.hxx>
+
+#include <TransitionPreset.hxx>
+#include <sdresid.hxx>
+#include <ViewShellBase.hxx>
+#include <DrawDocShell.hxx>
+#include <SlideSorterViewShell.hxx>
+#include <drawdoc.hxx>
+#include <sdmod.hxx>
+#include <sdpage.hxx>
+#include <filedlg.hxx>
+#include <strings.hrc>
+#include <EventMultiplexer.hxx>
+
+#include <comphelper/lok.hxx>
+#include <sal/log.hxx>
+#include <tools/debug.hxx>
+#include <svx/gallery.hxx>
+#include <vcl/stdtext.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/weld.hxx>
+#include <tools/urlobj.hxx>
+#include <slideshow.hxx>
+#include <sdundogr.hxx>
+#include <undoanim.hxx>
+#include <optsitem.hxx>
+
+#include <o3tl/safeint.hxx>
+
+#include <algorithm>
+
+using namespace ::com::sun::star;
+
+using ::com::sun::star::uno::Reference;
+
+
+namespace sd::impl
+{
+struct TransitionEffect
+{
+ TransitionEffect() :
+ mnType( 0 ),
+ mnSubType( 0 ),
+ mbDirection( true ),
+ mnFadeColor( 0 )
+ {
+ init();
+ }
+ explicit TransitionEffect( const ::sd::TransitionPreset & rPreset ) :
+ mnType( rPreset.getTransition()),
+ mnSubType( rPreset.getSubtype()),
+ mbDirection( rPreset.getDirection()),
+ mnFadeColor( rPreset.getFadeColor())
+ {
+ init();
+ }
+ explicit TransitionEffect( const SdPage & rPage ) :
+ mnType( rPage.getTransitionType() ),
+ mnSubType( rPage.getTransitionSubtype() ),
+ mbDirection( rPage.getTransitionDirection() ),
+ mnFadeColor( rPage.getTransitionFadeColor() )
+ {
+ init();
+
+ mfDuration = rPage.getTransitionDuration();
+ mfTime = rPage.GetTime();
+ mePresChange = rPage.GetPresChange();
+ mbSoundOn = rPage.IsSoundOn();
+ maSound = rPage.GetSoundFile();
+ mbLoopSound = rPage.IsLoopSound();
+ mbStopSound = rPage.IsStopSound();
+ }
+
+ void init()
+ {
+ mfDuration = 2.0;
+ mfTime = 0.0;
+ mePresChange = PresChange::Manual;
+ mbSoundOn = false;
+ mbLoopSound = false;
+ mbStopSound = false;
+
+ mbEffectAmbiguous = false;
+ mbDurationAmbiguous = false;
+ mbTimeAmbiguous = false;
+ mbPresChangeAmbiguous = false;
+ mbSoundAmbiguous = false;
+ mbLoopSoundAmbiguous = false;
+ }
+
+ void setAllAmbiguous()
+ {
+ mbEffectAmbiguous = true;
+ mbDurationAmbiguous = true;
+ mbTimeAmbiguous = true;
+ mbPresChangeAmbiguous = true;
+ mbSoundAmbiguous = true;
+ mbLoopSoundAmbiguous = true;
+ }
+
+ bool operator == ( const ::sd::TransitionPreset & rPreset ) const
+ {
+ return
+ (mnType == rPreset.getTransition()) &&
+ (mnSubType == rPreset.getSubtype()) &&
+ (mbDirection == rPreset.getDirection()) &&
+ (mnFadeColor == rPreset.getFadeColor());
+ }
+
+ void applyTo( SdPage & rOutPage ) const
+ {
+ if( ! mbEffectAmbiguous )
+ {
+ rOutPage.setTransitionType( mnType );
+ rOutPage.setTransitionSubtype( mnSubType );
+ rOutPage.setTransitionDirection( mbDirection );
+ rOutPage.setTransitionFadeColor( mnFadeColor );
+ }
+
+ if( ! mbDurationAmbiguous )
+ rOutPage.setTransitionDuration( mfDuration );
+ if( ! mbTimeAmbiguous )
+ rOutPage.SetTime( mfTime );
+ if( ! mbPresChangeAmbiguous )
+ rOutPage.SetPresChange( mePresChange );
+ if( ! mbSoundAmbiguous )
+ {
+ if( mbStopSound )
+ {
+ rOutPage.SetStopSound( true );
+ rOutPage.SetSound( false );
+ }
+ else
+ {
+ rOutPage.SetStopSound( false );
+ rOutPage.SetSound( mbSoundOn );
+ rOutPage.SetSoundFile( maSound );
+ }
+ }
+ if( ! mbLoopSoundAmbiguous )
+ rOutPage.SetLoopSound( mbLoopSound );
+ }
+
+ void compareWith( const SdPage & rPage )
+ {
+ TransitionEffect aOtherEffect( rPage );
+ mbEffectAmbiguous = mbEffectAmbiguous || aOtherEffect.mbEffectAmbiguous
+ || (mnType != aOtherEffect.mnType)
+ || (mnSubType != aOtherEffect.mnSubType)
+ || (mbDirection != aOtherEffect.mbDirection)
+ || (mnFadeColor != aOtherEffect.mnFadeColor);
+
+ mbDurationAmbiguous = mbDurationAmbiguous || aOtherEffect.mbDurationAmbiguous || mfDuration != aOtherEffect.mfDuration;
+ mbTimeAmbiguous = mbTimeAmbiguous || aOtherEffect.mbTimeAmbiguous || mfTime != aOtherEffect.mfTime;
+ mbPresChangeAmbiguous = mbPresChangeAmbiguous || aOtherEffect.mbPresChangeAmbiguous || mePresChange != aOtherEffect.mePresChange;
+ mbSoundAmbiguous = mbSoundAmbiguous || aOtherEffect.mbSoundAmbiguous || mbSoundOn != aOtherEffect.mbSoundOn;
+#if 0
+ // Weird leftover isolated expression with no effect, introduced in 2007 in
+ // CWS impress122. Ifdeffed out to avoid compiler warning, kept here in case
+ // somebody who understands this code notices and understands what the
+ // "right" thing to do might be.
+ (!mbStopSound && !aOtherEffect.mbStopSound && maSound != aOtherEffect.maSound) || (mbStopSound != aOtherEffect.mbStopSound);
+#endif
+ mbLoopSoundAmbiguous = mbLoopSoundAmbiguous || aOtherEffect.mbLoopSoundAmbiguous || mbLoopSound != aOtherEffect.mbLoopSound;
+ }
+
+ // effect
+ sal_Int16 mnType;
+ sal_Int16 mnSubType;
+ bool mbDirection;
+ sal_Int32 mnFadeColor;
+
+ // other settings
+ double mfDuration;
+ double mfTime;
+ PresChange mePresChange;
+ bool mbSoundOn;
+ OUString maSound;
+ bool mbLoopSound;
+ bool mbStopSound;
+
+ bool mbEffectAmbiguous;
+ bool mbDurationAmbiguous;
+ bool mbTimeAmbiguous;
+ bool mbPresChangeAmbiguous;
+ bool mbSoundAmbiguous;
+ bool mbLoopSoundAmbiguous;
+};
+
+} // namespace sd::impl
+
+// Local Helper Functions
+namespace
+{
+
+void lcl_ApplyToPages(
+ const ::sd::slidesorter::SharedPageSelection& rpPages,
+ const ::sd::impl::TransitionEffect & rEffect )
+{
+ for( const auto& rpPage : *rpPages )
+ {
+ rEffect.applyTo( *rpPage );
+ }
+}
+
+void lcl_CreateUndoForPages(
+ const ::sd::slidesorter::SharedPageSelection& rpPages,
+ ::sd::ViewShellBase const & rBase )
+{
+ ::sd::DrawDocShell* pDocSh = rBase.GetDocShell();
+ if (!pDocSh)
+ return;
+ SfxUndoManager* pManager = pDocSh->GetUndoManager();
+ if (!pManager)
+ return;
+ SdDrawDocument* pDoc = pDocSh->GetDoc();
+ if (!pDoc)
+ return;
+
+ OUString aComment( SdResId(STR_UNDO_SLIDE_PARAMS) );
+ pManager->EnterListAction(aComment, aComment, 0, rBase.GetViewShellId());
+ std::unique_ptr<SdUndoGroup> pUndoGroup(new SdUndoGroup( pDoc ));
+ pUndoGroup->SetComment( aComment );
+
+ for( const auto& rpPage : *rpPages )
+ {
+ pUndoGroup->AddAction( new sd::UndoTransition( pDoc, rpPage ) );
+ }
+
+ pManager->AddUndoAction( std::move(pUndoGroup) );
+ pManager->LeaveListAction();
+}
+
+struct lcl_EqualsSoundFileName
+{
+ explicit lcl_EqualsSoundFileName( const OUString & rStr ) :
+ maStr( rStr )
+ {}
+
+ bool operator() ( const OUString & rStr ) const
+ {
+ // note: formerly this was a case insensitive search for all
+ // platforms. It seems more sensible to do this platform-dependent
+ INetURLObject aURL(rStr);
+#if defined(_WIN32)
+ return maStr.equalsIgnoreAsciiCase( aURL.GetBase() );
+#else
+ return maStr == aURL.GetBase();
+#endif
+ }
+
+private:
+ OUString maStr;
+};
+
+// returns -1 if no object was found
+bool lcl_findSoundInList( const ::std::vector< OUString > & rSoundList,
+ std::u16string_view rFileName,
+ ::std::vector< OUString >::size_type & rOutPosition )
+{
+ INetURLObject aURL(rFileName);
+ ::std::vector< OUString >::const_iterator aIt =
+ ::std::find_if( rSoundList.begin(), rSoundList.end(),
+ lcl_EqualsSoundFileName( aURL.GetBase()));
+ if( aIt != rSoundList.end())
+ {
+ rOutPosition = ::std::distance( rSoundList.begin(), aIt );
+ return true;
+ }
+
+ return false;
+}
+
+OUString lcl_getSoundFileURL(
+ const ::std::vector< OUString > & rSoundList,
+ const weld::ComboBox& rListBox )
+{
+ sal_Int32 nPos = rListBox.get_active();
+ // the first three entries are no actual sounds
+ if( nPos >= 3 )
+ {
+ DBG_ASSERT( static_cast<sal_uInt32>(rListBox.get_count() - 3) == rSoundList.size(),
+ "Sound list-box is not synchronized to sound list" );
+ nPos -= 3;
+ if( rSoundList.size() > o3tl::make_unsigned(nPos) )
+ return rSoundList[ nPos ];
+ }
+
+ return OUString();
+}
+
+struct lcl_AppendSoundToListBox
+{
+ explicit lcl_AppendSoundToListBox(weld::ComboBox& rListBox)
+ : mrListBox( rListBox )
+ {}
+
+ void operator() ( std::u16string_view rString ) const
+ {
+ INetURLObject aURL( rString );
+ mrListBox.append_text( aURL.GetBase() );
+ }
+
+private:
+ weld::ComboBox& mrListBox;
+};
+
+void lcl_FillSoundListBox(
+ const ::std::vector< OUString > & rSoundList,
+ weld::ComboBox& rOutListBox )
+{
+ sal_Int32 nCount = rOutListBox.get_count();
+
+ // keep first three entries
+ for( sal_Int32 i=nCount - 1; i>=3; --i )
+ rOutListBox.remove( i );
+
+ ::std::for_each( rSoundList.begin(), rSoundList.end(),
+ lcl_AppendSoundToListBox( rOutListBox ));
+}
+
+/// Returns an offset into the list of transition presets
+size_t getPresetOffset( const sd::impl::TransitionEffect &rEffect )
+{
+ const sd::TransitionPresetList& rPresetList =
+ sd::TransitionPreset::getTransitionPresetList();
+
+ size_t nIdx = 0;
+ for( const auto& aIt: rPresetList )
+ {
+ if( rEffect.operator==( *aIt ))
+ break;
+ nIdx++;
+ }
+ return nIdx;
+}
+
+} // anonymous namespace
+
+namespace sd
+{
+
+class TransitionPane : public ValueSet
+{
+public:
+ explicit TransitionPane(std::unique_ptr<weld::ScrolledWindow> pScrolledWindow)
+ : ValueSet(std::move(pScrolledWindow))
+ {
+ }
+
+ void Recalculate()
+ {
+ GetScrollBar()->set_vpolicy(VclPolicyType::AUTOMATIC);
+ RecalculateItemSizes();
+ }
+
+ virtual void SetDrawingArea(weld::DrawingArea* pDrawingArea) override
+ {
+ Size aSize = pDrawingArea->get_ref_device().LogicToPixel(Size(70, 88), MapMode(MapUnit::MapAppFont));
+ pDrawingArea->set_size_request(aSize.Width(), aSize.Height());
+ ValueSet::SetDrawingArea(pDrawingArea);
+ SetOutputSizePixel(aSize);
+
+ SetStyle(GetStyle() | WB_ITEMBORDER | WB_FLATVALUESET | WB_VSCROLL);
+ EnableFullItemMode( false );
+ SetColCount(3);
+ }
+};
+
+// SlideTransitionPane
+SlideTransitionPane::SlideTransitionPane(
+ weld::Widget* pParent,
+ ViewShellBase & rBase) :
+ PanelLayout( pParent, "SlideTransitionsPanel", "modules/simpress/ui/slidetransitionspanel.ui" ),
+ mrBase( rBase ),
+ mpDrawDoc( rBase.GetDocShell() ? rBase.GetDocShell()->GetDoc() : nullptr ),
+ mbHasSelection( false ),
+ mbUpdatingControls( false ),
+ mbIsMainViewChangePending( false ),
+ maLateInitTimer("sd SlideTransitionPane maLateInitTimer")
+{
+ Initialize(mpDrawDoc);
+}
+
+css::ui::LayoutSize SlideTransitionPane::GetHeightForWidth(const sal_Int32 /*nWidth*/)
+{
+ sal_Int32 nMinimumHeight = get_preferred_size().Height();
+ return css::ui::LayoutSize(nMinimumHeight, -1, nMinimumHeight);
+}
+
+constexpr sal_uInt16 nNoneId = std::numeric_limits<sal_uInt16>::max();
+
+void SlideTransitionPane::Initialize(SdDrawDocument* pDoc)
+{
+ mxFT_VARIANT = m_xBuilder->weld_label("variant_label");
+ mxLB_VARIANT = m_xBuilder->weld_combo_box("variant_list");
+ mxFT_duration = m_xBuilder->weld_label("duration_label");
+ mxCBX_duration = m_xBuilder->weld_metric_spin_button("transition_duration", FieldUnit::SECOND);
+ mxFT_SOUND = m_xBuilder->weld_label("sound_label");
+ mxLB_SOUND = m_xBuilder->weld_combo_box("sound_list");
+ mxCB_LOOP_SOUND = m_xBuilder->weld_check_button("loop_sound");
+ mxRB_ADVANCE_ON_MOUSE = m_xBuilder->weld_radio_button("rb_mouse_click");
+ mxRB_ADVANCE_AUTO = m_xBuilder->weld_radio_button("rb_auto_after");
+ mxMF_ADVANCE_AUTO_AFTER = m_xBuilder->weld_metric_spin_button("auto_after_value", FieldUnit::SECOND);
+ mxPB_APPLY_TO_ALL = m_xBuilder->weld_button("apply_to_all");
+ mxPB_PLAY = m_xBuilder->weld_button("play");
+ mxCB_AUTO_PREVIEW = m_xBuilder->weld_check_button("auto_preview");
+
+ auto nMax = mxMF_ADVANCE_AUTO_AFTER->get_max(FieldUnit::SECOND);
+ mxMF_ADVANCE_AUTO_AFTER->set_max(99, FieldUnit::SECOND);
+ int nWidthChars = mxMF_ADVANCE_AUTO_AFTER->get_width_chars();
+ mxMF_ADVANCE_AUTO_AFTER->set_max(nMax, FieldUnit::SECOND);
+ mxMF_ADVANCE_AUTO_AFTER->set_width_chars(nWidthChars);
+ mxCBX_duration->set_width_chars(nWidthChars);
+
+ mxVS_TRANSITION_ICONS.reset(new TransitionPane(m_xBuilder->weld_scrolled_window("transitions_iconswin", true)));
+ mxVS_TRANSITION_ICONSWin.reset(new weld::CustomWeld(*m_xBuilder, "transitions_icons", *mxVS_TRANSITION_ICONS));
+
+ if( pDoc )
+ mxModel.set( pDoc->getUnoModel(), uno::UNO_QUERY );
+ // TODO: get correct view
+ if( mxModel.is())
+ mxView.set( mxModel->getCurrentController(), uno::UNO_QUERY );
+
+ // dummy list box of slide transitions for startup.
+ mxVS_TRANSITION_ICONS->InsertItem(
+ nNoneId, Image( StockImage::Yes, "sd/cmd/transition-none.png" ),
+ SdResId( STR_SLIDETRANSITION_NONE ),
+ VALUESET_APPEND, /* show legend */ true );
+ mxVS_TRANSITION_ICONS->Recalculate();
+
+ // set defaults
+ mxCB_AUTO_PREVIEW->set_active(true); // automatic preview on
+
+ // update control states before adding handlers
+ updateControls();
+
+ // set handlers
+ mxPB_APPLY_TO_ALL->connect_clicked( LINK( this, SlideTransitionPane, ApplyToAllButtonClicked ));
+ mxPB_PLAY->connect_clicked( LINK( this, SlideTransitionPane, PlayButtonClicked ));
+
+ mxVS_TRANSITION_ICONS->SetSelectHdl( LINK( this, SlideTransitionPane, TransitionSelected ));
+
+ mxLB_VARIANT->connect_changed( LINK( this, SlideTransitionPane, VariantListBoxSelected ));
+ mxCBX_duration->connect_value_changed(LINK( this, SlideTransitionPane, DurationModifiedHdl));
+ mxCBX_duration->connect_focus_out(LINK( this, SlideTransitionPane, DurationLoseFocusHdl));
+ mxLB_SOUND->connect_changed( LINK( this, SlideTransitionPane, SoundListBoxSelected ));
+ mxCB_LOOP_SOUND->connect_toggled( LINK( this, SlideTransitionPane, LoopSoundBoxChecked ));
+
+ mxRB_ADVANCE_ON_MOUSE->connect_toggled( LINK( this, SlideTransitionPane, AdvanceSlideRadioButtonToggled ));
+ mxRB_ADVANCE_AUTO->connect_toggled( LINK( this, SlideTransitionPane, AdvanceSlideRadioButtonToggled ));
+ mxMF_ADVANCE_AUTO_AFTER->connect_value_changed( LINK( this, SlideTransitionPane, AdvanceTimeModified ));
+ mxCB_AUTO_PREVIEW->connect_toggled( LINK( this, SlideTransitionPane, AutoPreviewClicked ));
+ addListener();
+
+ maLateInitTimer.SetTimeout(200);
+ maLateInitTimer.SetInvokeHandler(LINK(this, SlideTransitionPane, LateInitCallback));
+ maLateInitTimer.Start();
+}
+
+SlideTransitionPane::~SlideTransitionPane()
+{
+ maLateInitTimer.Stop();
+ removeListener();
+ mxVS_TRANSITION_ICONSWin.reset();
+ mxVS_TRANSITION_ICONS.reset();
+ mxFT_VARIANT.reset();
+ mxLB_VARIANT.reset();
+ mxFT_duration.reset();
+ mxCBX_duration.reset();
+ mxFT_SOUND.reset();
+ mxLB_SOUND.reset();
+ mxCB_LOOP_SOUND.reset();
+ mxRB_ADVANCE_ON_MOUSE.reset();
+ mxRB_ADVANCE_AUTO.reset();
+ mxMF_ADVANCE_AUTO_AFTER.reset();
+ mxPB_APPLY_TO_ALL.reset();
+ mxPB_PLAY.reset();
+ mxCB_AUTO_PREVIEW.reset();
+}
+
+void SlideTransitionPane::onSelectionChanged()
+{
+ updateControls();
+}
+
+void SlideTransitionPane::onChangeCurrentPage()
+{
+ updateControls();
+}
+
+::sd::slidesorter::SharedPageSelection SlideTransitionPane::getSelectedPages() const
+{
+ ::sd::slidesorter::SlideSorterViewShell * pSlideSorterViewShell
+ = ::sd::slidesorter::SlideSorterViewShell::GetSlideSorter(mrBase);
+ std::shared_ptr<sd::slidesorter::SlideSorterViewShell::PageSelection> pSelection;
+
+ if( pSlideSorterViewShell )
+ {
+ pSelection = pSlideSorterViewShell->GetPageSelection();
+ }
+ else
+ {
+ pSelection = std::make_shared<sd::slidesorter::SlideSorterViewShell::PageSelection>();
+ if( mxView.is() )
+ {
+ SdPage* pPage = SdPage::getImplementation( mxView->getCurrentPage() );
+ if( pPage )
+ pSelection->push_back(pPage);
+ }
+ }
+
+ return pSelection;
+}
+
+void SlideTransitionPane::updateControls()
+{
+ ::sd::slidesorter::SharedPageSelection pSelectedPages(getSelectedPages());
+ if( pSelectedPages->empty())
+ {
+ mbHasSelection = false;
+ return;
+ }
+ mbHasSelection = true;
+
+ DBG_ASSERT( ! mbUpdatingControls, "Multiple Control Updates" );
+ mbUpdatingControls = true;
+
+ // get model data for first page
+ SdPage * pFirstPage = pSelectedPages->front();
+ DBG_ASSERT( pFirstPage, "Invalid Page" );
+
+ impl::TransitionEffect aEffect( *pFirstPage );
+
+ // merge with other pages
+
+ // start with second page (note aIt != aEndIt, because ! aSelectedPages.empty())
+ for( const auto& rpPage : *pSelectedPages )
+ {
+ if( rpPage )
+ aEffect.compareWith( *rpPage );
+ }
+
+ // detect current slide effect
+ if( aEffect.mbEffectAmbiguous )
+ {
+ SAL_WARN( "sd.transitions", "Unusual, ambiguous transition effect" );
+ mxVS_TRANSITION_ICONS->SelectItem(nNoneId);
+ }
+ else
+ {
+ // ToDo: That 0 is "no transition" is documented nowhere except in the
+ // CTOR of sdpage
+ if( aEffect.mnType == 0 )
+ mxVS_TRANSITION_ICONS->SelectItem(nNoneId);
+ else
+ updateVariants( getPresetOffset( aEffect ) );
+ }
+
+ if( aEffect.mbDurationAmbiguous )
+ {
+ mxCBX_duration->set_text("");
+//TODO mxCBX_duration->SetNoSelection();
+ }
+ else
+ {
+ mxCBX_duration->set_value( (aEffect.mfDuration)*100.0, FieldUnit::SECOND );
+ }
+
+ if( aEffect.mbSoundAmbiguous )
+ {
+ mxLB_SOUND->set_active(-1);
+ maCurrentSoundFile.clear();
+ }
+ else
+ {
+ maCurrentSoundFile.clear();
+ if( aEffect.mbStopSound )
+ {
+ mxLB_SOUND->set_active( 1 );
+ }
+ else if( aEffect.mbSoundOn && !aEffect.maSound.isEmpty() )
+ {
+ std::vector<OUString>::size_type nPos = 0;
+ if( lcl_findSoundInList( maSoundList, aEffect.maSound, nPos ))
+ {
+ mxLB_SOUND->set_active( nPos + 3 );
+ maCurrentSoundFile = aEffect.maSound;
+ }
+ }
+ else
+ {
+ mxLB_SOUND->set_active( 0 );
+ }
+ }
+
+ if( aEffect.mbLoopSoundAmbiguous )
+ {
+ mxCB_LOOP_SOUND->set_state(TRISTATE_INDET);
+ }
+ else
+ {
+ mxCB_LOOP_SOUND->set_active(aEffect.mbLoopSound);
+ }
+
+ if( aEffect.mbPresChangeAmbiguous )
+ {
+ mxRB_ADVANCE_ON_MOUSE->set_active( false );
+ mxRB_ADVANCE_AUTO->set_active( false );
+ }
+ else
+ {
+ mxRB_ADVANCE_ON_MOUSE->set_active( aEffect.mePresChange == PresChange::Manual );
+ mxRB_ADVANCE_AUTO->set_active( aEffect.mePresChange == PresChange::Auto );
+ mxMF_ADVANCE_AUTO_AFTER->set_value(aEffect.mfTime * 100.0, FieldUnit::SECOND);
+ }
+
+ if (comphelper::LibreOfficeKit::isActive())
+ {
+ mxPB_PLAY->hide();
+ mxCB_AUTO_PREVIEW->set_active(false);
+ mxCB_AUTO_PREVIEW->hide();
+ mxFT_SOUND->hide();
+ mxLB_SOUND->hide();
+ mxCB_LOOP_SOUND->hide();
+ }
+ else
+ {
+ SdOptions* pOptions = SD_MOD()->GetSdOptions(DocumentType::Impress);
+ mxCB_AUTO_PREVIEW->set_active( pOptions->IsPreviewTransitions() );
+ }
+
+ mbUpdatingControls = false;
+
+ updateControlState();
+}
+
+void SlideTransitionPane::updateControlState()
+{
+ mxVS_TRANSITION_ICONSWin->set_sensitive( mbHasSelection );
+ mxLB_VARIANT->set_sensitive( mbHasSelection && mxLB_VARIANT->get_count() > 0 );
+ mxCBX_duration->set_sensitive( mbHasSelection );
+ mxLB_SOUND->set_sensitive( mbHasSelection );
+ mxCB_LOOP_SOUND->set_sensitive( mbHasSelection && (mxLB_SOUND->get_active() > 2));
+ mxRB_ADVANCE_ON_MOUSE->set_sensitive( mbHasSelection );
+ mxRB_ADVANCE_AUTO->set_sensitive( mbHasSelection );
+ mxMF_ADVANCE_AUTO_AFTER->set_sensitive( mbHasSelection && mxRB_ADVANCE_AUTO->get_active());
+
+ mxPB_APPLY_TO_ALL->set_sensitive( mbHasSelection );
+ mxPB_PLAY->set_sensitive( mbHasSelection );
+ mxCB_AUTO_PREVIEW->set_sensitive( mbHasSelection );
+}
+
+void SlideTransitionPane::updateSoundList()
+{
+ maSoundList.clear();
+
+ GalleryExplorer::FillObjList( GALLERY_THEME_SOUNDS, maSoundList );
+ GalleryExplorer::FillObjList( GALLERY_THEME_USERSOUNDS, maSoundList );
+
+ lcl_FillSoundListBox( maSoundList, *mxLB_SOUND );
+}
+
+void SlideTransitionPane::openSoundFileDialog()
+{
+ if( ! mxLB_SOUND->get_sensitive())
+ return;
+
+ SdOpenSoundFileDialog aFileDialog(GetFrameWeld());
+
+ DBG_ASSERT( mxLB_SOUND->get_active() == 2,
+ "Dialog should only open when \"Other sound\" is selected" );
+
+ bool bValidSoundFile( false );
+ bool bQuitLoop( false );
+
+ while( ! bQuitLoop &&
+ aFileDialog.Execute() == ERRCODE_NONE )
+ {
+ OUString aFile = aFileDialog.GetPath();
+ std::vector<OUString>::size_type nPos = 0;
+ bValidSoundFile = lcl_findSoundInList( maSoundList, aFile, nPos );
+
+ if( bValidSoundFile )
+ {
+ bQuitLoop = true;
+ }
+ else // not in sound list
+ {
+ // try to insert into gallery
+ if( GalleryExplorer::InsertURL( GALLERY_THEME_USERSOUNDS, aFile ) )
+ {
+ updateSoundList();
+ bValidSoundFile = lcl_findSoundInList( maSoundList, aFile, nPos );
+ DBG_ASSERT( bValidSoundFile, "Adding sound to gallery failed" );
+
+ bQuitLoop = true;
+ }
+ else
+ {
+ OUString aStrWarning(SdResId(STR_WARNING_NOSOUNDFILE));
+ aStrWarning = aStrWarning.replaceFirst("%", aFile);
+ std::unique_ptr<weld::MessageDialog> xWarn(Application::CreateMessageDialog(nullptr,
+ VclMessageType::Warning, VclButtonsType::NONE,
+ aStrWarning));
+ xWarn->add_button(GetStandardText(StandardButtonType::Retry), RET_RETRY);
+ xWarn->add_button(GetStandardText(StandardButtonType::Cancel), RET_CANCEL);
+ bQuitLoop = (xWarn->run() != RET_RETRY);
+
+ bValidSoundFile = false;
+ }
+ }
+
+ if( bValidSoundFile )
+ // skip first three entries in list
+ mxLB_SOUND->set_active( nPos + 3 );
+ }
+
+ if( bValidSoundFile )
+ return;
+
+ if( !maCurrentSoundFile.isEmpty() )
+ {
+ std::vector<OUString>::size_type nPos = 0;
+ if( lcl_findSoundInList( maSoundList, maCurrentSoundFile, nPos ))
+ mxLB_SOUND->set_active( nPos + 3 );
+ else
+ mxLB_SOUND->set_active( 0 ); // NONE
+ }
+ else
+ mxLB_SOUND->set_active( 0 ); // NONE
+}
+
+impl::TransitionEffect SlideTransitionPane::getTransitionEffectFromControls() const
+{
+ impl::TransitionEffect aResult;
+ aResult.setAllAmbiguous();
+
+ bool bNoneSelected = mxVS_TRANSITION_ICONS->IsNoSelection() || mxVS_TRANSITION_ICONS->GetSelectedItemId() == nNoneId;
+
+ // check first (aResult might be overwritten)
+ if( mxVS_TRANSITION_ICONSWin->get_sensitive() &&
+ !bNoneSelected &&
+ mxVS_TRANSITION_ICONS->GetSelectedItemId() > 0 )
+ {
+ const sd::TransitionPresetList& rPresetList = sd::TransitionPreset::getTransitionPresetList();
+ auto aSelected = rPresetList.begin();
+ std::advance( aSelected, mxVS_TRANSITION_ICONS->GetSelectedItemId() - 1);
+
+ if (mxLB_VARIANT->get_active() == -1)
+ {
+ // Transition with just one effect.
+ aResult = impl::TransitionEffect( **aSelected );
+ aResult.setAllAmbiguous();
+ }
+ else
+ {
+ int nVariant = 0;
+ bool bFound = false;
+ for( const auto& aIter: rPresetList )
+ {
+ if( aIter->getSetId() == (*aSelected)->getSetId() )
+ {
+ if( mxLB_VARIANT->get_active() == nVariant)
+ {
+ aResult = impl::TransitionEffect( *aIter );
+ aResult.setAllAmbiguous();
+ bFound = true;
+ break;
+ }
+ else
+ {
+ nVariant++;
+ }
+ }
+ }
+ if( !bFound )
+ {
+ aResult.mnType = 0;
+ }
+ }
+ aResult.mbEffectAmbiguous = false;
+ }
+ else if (bNoneSelected)
+ {
+ aResult.mbEffectAmbiguous = false;
+ }
+
+ //duration
+
+ if( mxCBX_duration->get_sensitive() && (!(mxCBX_duration->get_text()).isEmpty()) )
+ {
+ aResult.mfDuration = static_cast<double>(mxCBX_duration->get_value(FieldUnit::SECOND))/100.0;
+ aResult.mbDurationAmbiguous = false;
+ }
+
+ // slide-advance mode
+ if( mxRB_ADVANCE_ON_MOUSE->get_sensitive() && mxRB_ADVANCE_AUTO->get_sensitive() &&
+ (mxRB_ADVANCE_ON_MOUSE->get_active() || mxRB_ADVANCE_AUTO->get_active()))
+ {
+ if( mxRB_ADVANCE_ON_MOUSE->get_active())
+ aResult.mePresChange = PresChange::Manual;
+ else
+ {
+ aResult.mePresChange = PresChange::Auto;
+ if( mxMF_ADVANCE_AUTO_AFTER->get_sensitive())
+ {
+ aResult.mfTime = static_cast<double>(mxMF_ADVANCE_AUTO_AFTER->get_value(FieldUnit::SECOND) ) / 100.0 ;
+ aResult.mbTimeAmbiguous = false;
+ }
+ }
+
+ aResult.mbPresChangeAmbiguous = false;
+ }
+
+ // sound
+ if( mxLB_SOUND->get_sensitive())
+ {
+ maCurrentSoundFile.clear();
+ sal_Int32 nPos = mxLB_SOUND->get_active();
+ if (nPos != -1)
+ {
+ aResult.mbStopSound = nPos == 1;
+ aResult.mbSoundOn = nPos > 1;
+ if( aResult.mbStopSound )
+ {
+ aResult.maSound.clear();
+ aResult.mbSoundAmbiguous = false;
+ }
+ else
+ {
+ aResult.maSound = lcl_getSoundFileURL(maSoundList, *mxLB_SOUND);
+ aResult.mbSoundAmbiguous = false;
+ maCurrentSoundFile = aResult.maSound;
+ }
+ }
+ }
+
+ // sound loop
+ if( mxCB_LOOP_SOUND->get_sensitive() )
+ {
+ aResult.mbLoopSound = mxCB_LOOP_SOUND->get_active();
+ aResult.mbLoopSoundAmbiguous = false;
+ }
+
+ return aResult;
+}
+
+void SlideTransitionPane::applyToSelectedPages(bool bPreview = true)
+{
+ if( mbUpdatingControls )
+ return;
+
+ vcl::Window *pFocusWindow = Application::GetFocusWindow();
+
+ ::sd::slidesorter::SharedPageSelection pSelectedPages( getSelectedPages());
+ impl::TransitionEffect aEffect = getTransitionEffectFromControls();
+ if( ! pSelectedPages->empty())
+ {
+ lcl_CreateUndoForPages( pSelectedPages, mrBase );
+ lcl_ApplyToPages( pSelectedPages, aEffect );
+ mrBase.GetDocShell()->SetModified();
+ }
+ if( mxCB_AUTO_PREVIEW->get_sensitive() &&
+ mxCB_AUTO_PREVIEW->get_active() && bPreview)
+ {
+ if (aEffect.mnType) // mnType = 0 denotes no transition
+ playCurrentEffect();
+ else if( mxView.is() )
+ SlideShow::Stop( mrBase );
+ }
+
+ if (pFocusWindow)
+ pFocusWindow->GrabFocus();
+}
+
+void SlideTransitionPane::playCurrentEffect()
+{
+ if( mxView.is() )
+ {
+
+ Reference< css::animations::XAnimationNode > xNode;
+ SlideShow::StartPreview( mrBase, mxView->getCurrentPage(), xNode );
+ }
+}
+
+void SlideTransitionPane::addListener()
+{
+ Link<tools::EventMultiplexerEvent&,void> aLink( LINK(this,SlideTransitionPane,EventMultiplexerListener) );
+ mrBase.GetEventMultiplexer()->AddEventListener( aLink );
+}
+
+void SlideTransitionPane::removeListener()
+{
+ Link<tools::EventMultiplexerEvent&,void> aLink( LINK(this,SlideTransitionPane,EventMultiplexerListener) );
+ mrBase.GetEventMultiplexer()->RemoveEventListener( aLink );
+}
+
+IMPL_LINK(SlideTransitionPane,EventMultiplexerListener,
+ tools::EventMultiplexerEvent&, rEvent, void)
+{
+ switch (rEvent.meEventId)
+ {
+ case EventMultiplexerEventId::EditViewSelection:
+ onSelectionChanged();
+ break;
+
+ case EventMultiplexerEventId::CurrentPageChanged:
+ case EventMultiplexerEventId::SlideSortedSelection:
+ onChangeCurrentPage();
+ break;
+
+ case EventMultiplexerEventId::MainViewRemoved:
+ mxView.clear();
+ onSelectionChanged();
+ onChangeCurrentPage();
+ break;
+
+ case EventMultiplexerEventId::MainViewAdded:
+ mbIsMainViewChangePending = true;
+ break;
+
+ case EventMultiplexerEventId::ConfigurationUpdated:
+ if (mbIsMainViewChangePending)
+ {
+ mbIsMainViewChangePending = false;
+
+ // 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)
+ {
+ mxView.set(mrBase.GetController(), css::uno::UNO_QUERY);
+ onSelectionChanged();
+ onChangeCurrentPage();
+ }
+ }
+ break;
+
+ default:
+ if (rEvent.meEventId != EventMultiplexerEventId::Disposing)
+ {
+ onSelectionChanged();
+ onChangeCurrentPage();
+ }
+ break;
+ }
+}
+
+IMPL_LINK_NOARG(SlideTransitionPane, ApplyToAllButtonClicked, weld::Button&, void)
+{
+ DBG_ASSERT( mpDrawDoc, "Invalid Draw Document!" );
+ if( !mpDrawDoc )
+ return;
+
+ ::sd::slidesorter::SharedPageSelection pPages =
+ std::make_shared<::sd::slidesorter::SlideSorterViewShell::PageSelection>();
+
+ sal_uInt16 nPageCount = mpDrawDoc->GetSdPageCount( PageKind::Standard );
+ pPages->reserve( nPageCount );
+ for( sal_uInt16 i=0; i<nPageCount; ++i )
+ {
+ SdPage * pPage = mpDrawDoc->GetSdPage( i, PageKind::Standard );
+ if( pPage )
+ pPages->push_back( pPage );
+ }
+
+ if( ! pPages->empty())
+ {
+ lcl_CreateUndoForPages( pPages, mrBase );
+ lcl_ApplyToPages( pPages, getTransitionEffectFromControls() );
+ }
+}
+
+IMPL_LINK_NOARG(SlideTransitionPane, PlayButtonClicked, weld::Button&, void)
+{
+ playCurrentEffect();
+}
+
+IMPL_LINK_NOARG(SlideTransitionPane, TransitionSelected, ValueSet*, void)
+{
+ updateVariants( mxVS_TRANSITION_ICONS->GetSelectedItemId() - 1 );
+ applyToSelectedPages();
+}
+
+/// we use an integer offset into the list of transition presets
+void SlideTransitionPane::updateVariants( size_t nPresetOffset )
+{
+ const sd::TransitionPresetList& rPresetList = sd::TransitionPreset::getTransitionPresetList();
+ mxLB_VARIANT->clear();
+ mxVS_TRANSITION_ICONS->SelectItem(nNoneId);
+
+ if( nPresetOffset >= rPresetList.size() )
+ {
+ mxLB_VARIANT->set_sensitive( false );
+ }
+ else
+ {
+ auto pFound = rPresetList.begin();
+ std::advance( pFound, nPresetOffset );
+
+ // Fill in the variant listbox
+ size_t nFirstItem = 0, nItem = 1;
+ for( const auto& aIt: rPresetList )
+ {
+ if( aIt->getSetId() == (*pFound)->getSetId() )
+ {
+ if (!nFirstItem)
+ nFirstItem = nItem;
+ if( !aIt->getVariantLabel().isEmpty() )
+ {
+ mxLB_VARIANT->append_text( aIt->getVariantLabel() );
+ if( *pFound == aIt )
+ mxLB_VARIANT->set_active( mxLB_VARIANT->get_count()-1 );
+ }
+ }
+ nItem++;
+ }
+
+ if( mxLB_VARIANT->get_count() == 0 )
+ mxLB_VARIANT->set_sensitive( false );
+ else
+ mxLB_VARIANT->set_sensitive(true);
+
+ // item has the id of the first transition from this set.
+ mxVS_TRANSITION_ICONS->SelectItem( nFirstItem );
+ }
+}
+
+IMPL_LINK_NOARG(SlideTransitionPane, AdvanceSlideRadioButtonToggled, weld::Toggleable&, void)
+{
+ updateControlState();
+ applyToSelectedPages(false);
+}
+
+IMPL_LINK_NOARG(SlideTransitionPane, AdvanceTimeModified, weld::MetricSpinButton&, void)
+{
+ applyToSelectedPages(false);
+}
+
+IMPL_LINK_NOARG(SlideTransitionPane, VariantListBoxSelected, weld::ComboBox&, void)
+{
+ applyToSelectedPages();
+}
+
+IMPL_LINK_NOARG(SlideTransitionPane, DurationModifiedHdl, weld::MetricSpinButton&, void)
+{
+ double duration_value = static_cast<double>(mxCBX_duration->get_value(FieldUnit::SECOND));
+ if (duration_value <= 0.0)
+ mxCBX_duration->set_value(0, FieldUnit::SECOND);
+ else
+ mxCBX_duration->set_value(duration_value, FieldUnit::SECOND);
+
+ applyToSelectedPages();
+}
+
+IMPL_LINK_NOARG(SlideTransitionPane, DurationLoseFocusHdl, weld::Widget&, void)
+{
+ applyToSelectedPages();
+}
+
+IMPL_LINK_NOARG(SlideTransitionPane, SoundListBoxSelected, weld::ComboBox&, void)
+{
+ sal_Int32 nPos = mxLB_SOUND->get_active();
+ if( nPos == 2 )
+ {
+ // other sound...
+ openSoundFileDialog();
+ }
+ updateControlState();
+ applyToSelectedPages();
+}
+
+IMPL_LINK_NOARG(SlideTransitionPane, LoopSoundBoxChecked, weld::Toggleable&, void)
+{
+ applyToSelectedPages();
+}
+
+IMPL_LINK_NOARG(SlideTransitionPane, AutoPreviewClicked, weld::Toggleable&, void)
+{
+ SdOptions* pOptions = SD_MOD()->GetSdOptions(DocumentType::Impress);
+ pOptions->SetPreviewTransitions( mxCB_AUTO_PREVIEW->get_active() );
+}
+
+IMPL_LINK_NOARG(SlideTransitionPane, LateInitCallback, Timer *, void)
+{
+ const TransitionPresetList& rPresetList = TransitionPreset::getTransitionPresetList();
+
+ size_t nPresetOffset = 0;
+ for( const TransitionPresetPtr& pPreset: rPresetList )
+ {
+ const OUString sLabel( pPreset->getSetLabel() );
+ if( !sLabel.isEmpty() )
+ {
+ if( m_aNumVariants.find( pPreset->getSetId() ) == m_aNumVariants.end() )
+ {
+ OUString sImageName("sd/cmd/transition-" + pPreset->getSetId() + ".png");
+ BitmapEx aIcon( sImageName );
+ if ( aIcon.IsEmpty() ) // need a fallback
+ sImageName = "sd/cmd/transition-none.png";
+
+ mxVS_TRANSITION_ICONS->InsertItem(
+ nPresetOffset + 1, Image(StockImage::Yes, sImageName), sLabel,
+ VALUESET_APPEND, /* show legend */ true );
+
+ m_aNumVariants[ pPreset->getSetId() ] = 1;
+ }
+ else
+ {
+ m_aNumVariants[ pPreset->getSetId() ]++;
+ }
+ }
+ nPresetOffset++;
+ }
+ mxVS_TRANSITION_ICONS->Recalculate();
+
+ SAL_INFO( "sd.transitions", "Item transition offsets in ValueSet:");
+ for( size_t i = 0; i < mxVS_TRANSITION_ICONS->GetItemCount(); ++i )
+ SAL_INFO( "sd.transitions", i << ":" << mxVS_TRANSITION_ICONS->GetItemId( i ) );
+
+ nPresetOffset = 0;
+ SAL_INFO( "sd.transitions", "Transition presets by offsets:");
+ for( const auto& aIter: rPresetList )
+ {
+ SAL_INFO( "sd.transitions", nPresetOffset++ << " " <<
+ aIter->getPresetId() << ": " << aIter->getSetId() );
+ }
+
+ updateSoundList();
+ updateControls();
+}
+
+} // namespace sd
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/animations/motionpathtag.cxx b/sd/source/ui/animations/motionpathtag.cxx
new file mode 100644
index 000000000..ced685395
--- /dev/null
+++ b/sd/source/ui/animations/motionpathtag.cxx
@@ -0,0 +1,1200 @@
+/* -*- 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/util/XChangesNotifier.hpp>
+
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+
+#include <sfx2/viewfrm.hxx>
+#include <sfx2/dispatch.hxx>
+
+#include <svx/svdpagv.hxx>
+#include <svx/sdrpagewindow.hxx>
+#include <svx/sdrpaintwindow.hxx>
+#include <svx/svdopath.hxx>
+#include <svx/xfillit0.hxx>
+#include <svx/xlineit0.hxx>
+#include <svx/xlndsit.hxx>
+#include <svx/xlnclit.hxx>
+#include <svx/xlnstit.hxx>
+#include <svx/xlnedit.hxx>
+#include <svx/xlnstwit.hxx>
+#include <svx/xlnedwit.hxx>
+#include <svx/xlnstcit.hxx>
+#include <svx/xlnedcit.hxx>
+#include <svx/xlntrit.hxx>
+#include <svx/svxids.hrc>
+#include <svx/polypolygoneditor.hxx>
+#include <svx/svddrgmt.hxx>
+
+#include <CustomAnimationPane.hxx>
+#include <View.hxx>
+#include "motionpathtag.hxx"
+#include <ViewShell.hxx>
+#include <Window.hxx>
+
+#include <svx/sdr/contact/viewcontact.hxx>
+#include <svx/sdr/overlay/overlayprimitive2dsequenceobject.hxx>
+
+using sdr::PolyPolygonEditor;
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::util;
+using namespace ::com::sun::star::drawing;
+
+namespace sd
+{
+
+const sal_uInt32 SMART_TAG_HDL_NUM = SAL_MAX_UINT32;
+const int DRGPIX = 2; // Drag MinMove in Pixel
+
+namespace {
+
+class PathDragMove : public SdrDragMove
+{
+private:
+ basegfx::B2DPolyPolygon maPathPolyPolygon;
+
+protected:
+ virtual void createSdrDragEntries() override;
+
+public:
+ PathDragMove(SdrDragView& rNewView,
+ const rtl::Reference <MotionPathTag >& xTag,
+ const basegfx::B2DPolyPolygon& rPathPolyPolygon)
+ : SdrDragMove(rNewView),
+ maPathPolyPolygon(rPathPolyPolygon),
+ mxTag( xTag )
+ {}
+
+ PathDragMove(SdrDragView& rNewView,
+ const rtl::Reference <MotionPathTag >& xTag)
+ : SdrDragMove(rNewView),
+ mxTag( xTag )
+ {}
+
+ virtual bool BeginSdrDrag() override;
+ virtual bool EndSdrDrag(bool bCopy) override;
+
+ rtl::Reference <MotionPathTag > mxTag;
+};
+
+}
+
+void PathDragMove::createSdrDragEntries()
+{
+ // call parent
+ SdrDragMove::createSdrDragEntries();
+
+ if(maPathPolyPolygon.count())
+ {
+ addSdrDragEntry(std::unique_ptr<SdrDragEntry>(new SdrDragEntryPolyPolygon(maPathPolyPolygon)));
+ }
+}
+
+bool PathDragMove::BeginSdrDrag()
+{
+ if( mxTag.is() )
+ {
+ SdrPathObj* pPathObj = mxTag->getPathObj();
+ if( pPathObj )
+ {
+ DragStat().SetActionRect(pPathObj->GetCurrentBoundRect());
+ }
+ }
+ Show();
+ return true;
+}
+
+bool PathDragMove::EndSdrDrag(bool /*bCopy*/)
+{
+ Hide();
+ if( mxTag.is() )
+ mxTag->MovePath( DragStat().GetDX(), DragStat().GetDY() );
+ return true;
+}
+
+namespace {
+
+class PathDragResize : public SdrDragResize
+{
+private:
+ basegfx::B2DPolyPolygon maPathPolyPolygon;
+
+protected:
+ virtual void createSdrDragEntries() override;
+
+public:
+ PathDragResize(SdrDragView& rNewView,
+ const rtl::Reference <MotionPathTag >& xTag,
+ const basegfx::B2DPolyPolygon& rPathPolyPolygon)
+ : SdrDragResize(rNewView),
+ maPathPolyPolygon(rPathPolyPolygon),
+ mxTag( xTag )
+ {}
+
+ PathDragResize(SdrDragView& rNewView,
+ const rtl::Reference <MotionPathTag >& xTag)
+ : SdrDragResize(rNewView),
+ mxTag( xTag )
+ {}
+
+ virtual bool EndSdrDrag(bool bCopy) override;
+ rtl::Reference <MotionPathTag > mxTag;
+};
+
+}
+
+void PathDragResize::createSdrDragEntries()
+{
+ // call parent
+ SdrDragResize::createSdrDragEntries();
+
+ if(maPathPolyPolygon.count())
+ {
+ addSdrDragEntry(std::unique_ptr<SdrDragEntry>(new SdrDragEntryPolyPolygon(maPathPolyPolygon)));
+ }
+}
+
+bool PathDragResize::EndSdrDrag(bool /*bCopy*/)
+{
+ Hide();
+ if( mxTag.is() )
+ {
+ SdrPathObj* pPathObj = mxTag->getPathObj();
+ if( pPathObj )
+ {
+ const Point aRef( DragStat().GetRef1() );
+ basegfx::B2DHomMatrix aTrans(basegfx::utils::createTranslateB2DHomMatrix(-aRef.X(), -aRef.Y()));
+ aTrans.scale(double(aXFact), double(aYFact));
+ aTrans.translate(aRef.X(), aRef.Y());
+ basegfx::B2DPolyPolygon aDragPoly(pPathObj->GetPathPoly());
+ aDragPoly.transform(aTrans);
+ pPathObj->SetPathPoly( aDragPoly );
+ }
+ }
+ return true;
+}
+
+namespace {
+
+class PathDragObjOwn : public SdrDragObjOwn
+{
+private:
+ basegfx::B2DPolyPolygon maPathPolyPolygon;
+
+protected:
+ virtual void createSdrDragEntries() override;
+
+public:
+ PathDragObjOwn(SdrDragView& rNewView,
+ const basegfx::B2DPolyPolygon& rPathPolyPolygon)
+ : SdrDragObjOwn(rNewView),
+ maPathPolyPolygon(rPathPolyPolygon)
+ {}
+
+ explicit PathDragObjOwn(SdrDragView& rNewView)
+ : SdrDragObjOwn(rNewView)
+ {}
+
+ virtual bool EndSdrDrag(bool bCopy) override;
+};
+
+}
+
+void PathDragObjOwn::createSdrDragEntries()
+{
+ // call parent
+ SdrDragObjOwn::createSdrDragEntries();
+
+ if(maPathPolyPolygon.count())
+ {
+ addSdrDragEntry(std::unique_ptr<SdrDragEntry>(new SdrDragEntryPolyPolygon(maPathPolyPolygon)));
+ }
+}
+
+bool PathDragObjOwn::EndSdrDrag(bool /*bCopy*/)
+{
+ Hide();
+
+ SdrObject* pObj = GetDragObj();
+
+ if(pObj && pObj->applySpecialDrag(DragStat()))
+ {
+ pObj->SetChanged();
+ pObj->BroadcastObjectChange();
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+namespace {
+
+class SdPathHdl : public SmartHdl
+{
+public:
+ SdPathHdl( const SmartTagReference& xTag, SdrPathObj* mpPathObj );
+
+ virtual void CreateB2dIAObject() override;
+ virtual bool IsFocusHdl() const override;
+
+private:
+ SdrPathObj* mpPathObj;
+};
+
+}
+
+SdPathHdl::SdPathHdl( const SmartTagReference& xTag, SdrPathObj* pPathObj )
+: SmartHdl( xTag, pPathObj->GetCurrentBoundRect().TopLeft(), SdrHdlKind::SmartTag )
+, mpPathObj( pPathObj )
+{
+}
+
+void SdPathHdl::CreateB2dIAObject()
+{
+ // first throw away old one
+ GetRidOfIAObject();
+
+ if(!pHdlList)
+ return;
+
+ SdrMarkView* pView = pHdlList->GetView();
+
+ if(!pView || pView->areMarkHandlesHidden())
+ return;
+
+ SdrPageView* pPageView = pView->GetSdrPageView();
+
+ if(!pPageView)
+ return;
+
+ for(sal_uInt32 b(0); b < pPageView->PageWindowCount(); b++)
+ {
+ const SdrPageWindow& rPageWindow = *pPageView->GetPageWindow(b);
+
+ if(rPageWindow.GetPaintWindow().OutputToWindow())
+ {
+ const rtl::Reference< sdr::overlay::OverlayManager >& xManager = rPageWindow.GetOverlayManager();
+ if (xManager.is() && mpPathObj)
+ {
+ const sdr::contact::ViewContact& rVC = mpPathObj->GetViewContact();
+ drawinglayer::primitive2d::Primitive2DContainer aSequence;
+ rVC.getViewIndependentPrimitive2DContainer(aSequence);
+ std::unique_ptr<sdr::overlay::OverlayObject> pNew(new sdr::overlay::OverlayPrimitive2DSequenceObject(std::move(aSequence)));
+
+ // OVERLAYMANAGER
+ insertNewlyCreatedOverlayObjectForSdrHdl(
+ std::move(pNew),
+ rPageWindow.GetObjectContact(),
+ *xManager);
+ }
+ }
+ }
+}
+
+bool SdPathHdl::IsFocusHdl() const
+{
+ return false;
+}
+
+MotionPathTag::MotionPathTag( CustomAnimationPane& rPane, ::sd::View& rView, const CustomAnimationEffectPtr& pEffect )
+: SmartTag( rView )
+, mrPane( rPane )
+, mpEffect( pEffect )
+, mxOrigin( pEffect->getTargetShape() )
+, msLastPath( pEffect->getPath() )
+, mbInUpdatePath( false )
+{
+ mpPathObj = mpEffect->createSdrPathObjFromPath(rView.getSdrModelFromSdrView());
+ mxPolyPoly = mpPathObj->GetPathPoly();
+ if (mxOrigin.is())
+ maOriginPos = mxOrigin->getPosition();
+
+ XDash aDash( css::drawing::DashStyle_RECT, 1, 80, 1, 80, 80);
+ OUString aEmpty( "?" );
+ mpPathObj->SetMergedItem( XLineDashItem( aEmpty, aDash ) );
+ mpPathObj->SetMergedItem( XLineStyleItem( drawing::LineStyle_DASH ) );
+ mpPathObj->SetMergedItem( XLineColorItem(aEmpty, COL_GRAY) );
+ mpPathObj->SetMergedItem( XFillStyleItem( drawing::FillStyle_NONE ) );
+
+ ::basegfx::B2DPolygon aStartArrow;
+ aStartArrow.append(::basegfx::B2DPoint(20.0, 0.0));
+ aStartArrow.append(::basegfx::B2DPoint(0.0, 0.0));
+ aStartArrow.append(::basegfx::B2DPoint(10.0, 30.0));
+ aStartArrow.setClosed(true);
+ mpPathObj->SetMergedItem(XLineStartItem(aEmpty,::basegfx::B2DPolyPolygon(aStartArrow)));
+ mpPathObj->SetMergedItem(XLineStartWidthItem(400));
+ mpPathObj->SetMergedItem(XLineStartCenterItem(true));
+
+ updatePathAttributes();
+
+ mpPathObj->SetMergedItem(XLineTransparenceItem(50));
+
+ mpMark.reset(new SdrMark( mpPathObj, mrView.GetSdrPageView() ));
+
+ mpPathObj->AddListener( *this );
+
+ Reference< XChangesNotifier > xNotifier( mpEffect->getNode(), UNO_QUERY );
+ if( xNotifier.is() )
+ {
+ xNotifier->addChangesListener( this );
+ }
+}
+
+MotionPathTag::~MotionPathTag()
+{
+ DBG_ASSERT( mpPathObj == nullptr, "sd::MotionPathTag::~MotionPathTag(), dispose me first!" );
+ Dispose();
+}
+
+void MotionPathTag::updatePathAttributes()
+{
+ ::basegfx::B2DPolygon aCandidate;
+ if( mxPolyPoly.count() )
+ {
+ aCandidate = mxPolyPoly.getB2DPolygon(0);
+ ::basegfx::utils::checkClosed( aCandidate );
+ }
+
+ if( !aCandidate.isClosed() )
+ {
+ ::basegfx::B2DPolygon aEndArrow;
+ aEndArrow.append(::basegfx::B2DPoint(10.0, 0.0));
+ aEndArrow.append(::basegfx::B2DPoint(0.0, 30.0));
+ aEndArrow.append(::basegfx::B2DPoint(20.0, 30.0));
+ aEndArrow.setClosed(true);
+ mpPathObj->SetMergedItem(XLineEndItem("?",::basegfx::B2DPolyPolygon(aEndArrow)));
+ mpPathObj->SetMergedItem(XLineEndWidthItem(400));
+ mpPathObj->SetMergedItem(XLineEndCenterItem(true));
+ }
+ else
+ {
+ mpPathObj->SetMergedItem(XLineEndItem());
+ }
+}
+
+void MotionPathTag::Notify( SfxBroadcaster& /*rBC*/, const SfxHint& rHint )
+{
+ if( !(mpPathObj && !mbInUpdatePath && rHint.GetId() == SfxHintId::ThisIsAnSdrHint && mpEffect) )
+ return;
+
+ if( mxPolyPoly != mpPathObj->GetPathPoly() )
+ {
+ mbInUpdatePath = true;
+ mxPolyPoly = mpPathObj->GetPathPoly();
+ rtl::Reference< MotionPathTag > xTag( this );
+ mrPane.updatePathFromMotionPathTag( xTag );
+ msLastPath = mpEffect->getPath();
+ updatePathAttributes();
+ mbInUpdatePath = false;
+ }
+}
+
+void MotionPathTag::MovePath( int nDX, int nDY )
+{
+ if( mpPathObj )
+ {
+ mpPathObj->Move( Size( nDX, nDY ) );
+ mrView.updateHandles();
+ }
+}
+
+/** returns true if the MotionPathTag handled the event. */
+bool MotionPathTag::MouseButtonDown( const MouseEvent& rMEvt, SmartHdl& rHdl )
+{
+ if( !mpPathObj )
+ return false;
+
+ if( !isSelected() )
+ {
+ SmartTagReference xTag( this );
+ mrView.getSmartTags().select( xTag );
+ selectionChanged();
+ return true;
+ }
+ else
+ {
+ if( rMEvt.IsLeft() && (rMEvt.GetClicks() == 2) )
+ {
+ mrView.GetViewShell()->GetViewFrame()->GetDispatcher()->Execute(SID_BEZIER_EDIT, SfxCallMode::ASYNCHRON);
+ return true;
+ }
+ else if( rMEvt.IsLeft() )
+ {
+ OutputDevice* pOut = mrView.GetViewShell()->GetActiveWindow()->GetOutDev();
+ Point aMDPos( pOut->PixelToLogic( rMEvt.GetPosPixel() ) );
+
+ if( !mrView.IsFrameDragSingles() && mrView.IsInsObjPointMode() && (rHdl.GetObjHdlNum() == SMART_TAG_HDL_NUM) )
+ {
+ // insert a point in edit mode
+ const bool bNewObj = rMEvt.IsMod1();
+
+ mrView.BrkAction();
+
+ Point aPt(aMDPos); // - pMarkedPV->GetOffset());
+
+ if(bNewObj)
+ aPt = mrView.GetSnapPos(aPt,mrView.GetSdrPageView());
+
+ bool bClosed0(mpPathObj->IsClosedObj());
+
+ sal_uInt32 nInsPointNum = mpPathObj->NbcInsPointOld(aPt, bNewObj);
+
+ if(bClosed0 != mpPathObj->IsClosedObj())
+ {
+ // Obj was closed implicit
+ // object changed
+ mpPathObj->SetChanged();
+ mpPathObj->BroadcastObjectChange();
+ }
+
+ if(0xffffffff != nInsPointNum)
+ {
+ mrView.UnmarkAllPoints();
+ mrView.updateHandles();
+
+ bool bRet = mrView.BegDragObj(aMDPos, pOut, mrView.GetHdl(nInsPointNum+1), 0, new PathDragObjOwn( mrView ) );
+
+ if (bRet)
+ {
+ const_cast< SdrDragStat* >( &mrView.GetDragStat() )->SetMinMoved();
+ mrView.MovDragObj(aMDPos);
+ }
+ }
+ return true;
+ }
+ else
+ {
+ SmartHdl* pHdl = &rHdl;
+ if (!mrView.IsPointMarked(*pHdl) || rMEvt.IsShift())
+ {
+ if (!rMEvt.IsShift())
+ {
+ mrView.UnmarkAllPoints();
+ pHdl = dynamic_cast< SmartHdl* >( mrView.PickHandle(aMDPos) );
+ }
+ else
+ {
+ if (mrView.IsPointMarked(*pHdl) )
+ {
+ mrView.UnmarkPoint(*pHdl);
+ pHdl = nullptr;
+ }
+ else
+ {
+ pHdl = dynamic_cast< SmartHdl* >( mrView.PickHandle(aMDPos) );
+ }
+ }
+
+ if (pHdl)
+ mrView.MarkPoint(*pHdl);
+ }
+
+ if( pHdl && !rMEvt.IsRight() )
+ {
+ mrView.BrkAction();
+ const sal_uInt16 nDrgLog = static_cast<sal_uInt16>(pOut->PixelToLogic(Size(DRGPIX,0)).Width());
+
+ rtl::Reference< MotionPathTag > xTag( this );
+ SdrDragMethod* pDragMethod;
+
+ // #i95646# add DragPoly as geometry to each local SdrDragMethod to be able
+ // to create the needed local SdrDragEntry for it in createSdrDragEntries()
+ const basegfx::B2DPolyPolygon aDragPoly(mpPathObj->GetPathPoly());
+
+ if( (pHdl->GetKind() == SdrHdlKind::Move) || (pHdl->GetKind() == SdrHdlKind::SmartTag) )
+ {
+ pDragMethod = new PathDragMove( mrView, xTag, aDragPoly );
+ pHdl->SetPos( aMDPos );
+ }
+ else if( pHdl->GetKind() == SdrHdlKind::Poly )
+ {
+ pDragMethod = new PathDragObjOwn( mrView, aDragPoly );
+ }
+ else
+ {
+ pDragMethod = new PathDragResize( mrView, xTag, aDragPoly );
+ }
+
+ mrView.BegDragObj(aMDPos, nullptr, pHdl, nDrgLog, pDragMethod );
+ }
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+/** returns true if the SmartTag consumes this event. */
+bool MotionPathTag::KeyInput( const KeyEvent& rKEvt )
+{
+ if( !mpPathObj )
+ return false;
+
+ sal_uInt16 nCode = rKEvt.GetKeyCode().GetCode();
+ switch( nCode )
+ {
+ case KEY_DELETE:
+ return OnDelete();
+
+ case KEY_DOWN:
+ case KEY_UP:
+ case KEY_LEFT:
+ case KEY_RIGHT:
+ return OnMove( rKEvt );
+
+ case KEY_ESCAPE:
+ {
+ SmartTagReference xThis( this );
+ mrView.getSmartTags().deselect();
+ return true;
+ }
+
+ case KEY_TAB:
+ return OnTabHandles( rKEvt );
+
+ case KEY_SPACE:
+ return OnMarkHandle( rKEvt );
+
+ default:
+ break;
+ }
+ return false;
+}
+
+bool MotionPathTag::OnDelete()
+{
+ mrPane.remove( mpEffect );
+ return true;
+}
+
+bool MotionPathTag::OnTabHandles( const KeyEvent& rKEvt )
+{
+ if(rKEvt.GetKeyCode().IsMod1() || rKEvt.GetKeyCode().IsMod2())
+ {
+ const SdrHdlList& rHdlList = mrView.GetHdlList();
+ bool bForward(!rKEvt.GetKeyCode().IsShift());
+
+ const_cast<SdrHdlList&>(rHdlList).TravelFocusHdl(bForward);
+
+ // guarantee visibility of focused handle
+ SdrHdl* pHdl = rHdlList.GetFocusHdl();
+
+ if(pHdl)
+ {
+ Window* pWindow = mrView.GetViewShell()->GetActiveWindow();
+ if( pWindow )
+ {
+ Point aHdlPosition(pHdl->GetPos());
+ ::tools::Rectangle aVisRect(aHdlPosition - Point(100, 100), Size(200, 200));
+ mrView.MakeVisible(aVisRect, *pWindow);
+ }
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+bool MotionPathTag::OnMarkHandle( const KeyEvent& rKEvt )
+{
+ const SdrHdlList& rHdlList = mrView.GetHdlList();
+ SdrHdl* pHdl = rHdlList.GetFocusHdl();
+
+ if(pHdl && pHdl->GetKind() == SdrHdlKind::Poly )
+ {
+ // rescue ID of point with focus
+ sal_uInt32 nPol(pHdl->GetPolyNum());
+ sal_uInt32 nPnt(pHdl->GetPointNum());
+
+ if(mrView.IsPointMarked(*pHdl))
+ {
+ if(rKEvt.GetKeyCode().IsShift())
+ {
+ mrView.UnmarkPoint(*pHdl);
+ }
+ }
+ else
+ {
+ if(!rKEvt.GetKeyCode().IsShift())
+ {
+ mrView.UnmarkAllPoints();
+ }
+ mrView.MarkPoint(*pHdl);
+ }
+
+ if(nullptr == rHdlList.GetFocusHdl())
+ {
+ // restore point with focus
+ SdrHdl* pNewOne = nullptr;
+
+ for(size_t a = 0; !pNewOne && a < rHdlList.GetHdlCount(); ++a)
+ {
+ SdrHdl* pAct = rHdlList.GetHdl(a);
+
+ if(pAct && pAct->GetKind() == SdrHdlKind::Poly && pAct->GetPolyNum() == nPol && pAct->GetPointNum() == nPnt)
+ pNewOne = pAct;
+ }
+
+ if(pNewOne)
+ const_cast<SdrHdlList&>(rHdlList).SetFocusHdl(pNewOne);
+ }
+ }
+
+ return true;
+}
+
+bool MotionPathTag::OnMove( const KeyEvent& rKEvt )
+{
+ ::tools::Long nX = 0;
+ ::tools::Long nY = 0;
+
+ switch( rKEvt.GetKeyCode().GetCode() )
+ {
+ case KEY_UP: nY = -1; break;
+ case KEY_DOWN: nY = 1; break;
+ case KEY_LEFT: nX = -1; break;
+ case KEY_RIGHT: nX = 1; break;
+ default: break;
+ }
+
+ if(rKEvt.GetKeyCode().IsMod2())
+ {
+ OutputDevice* pOut = mrView.GetViewShell()->GetActiveWindow()->GetOutDev();
+ Size aLogicSizeOnePixel = pOut ? pOut->PixelToLogic(Size(1,1)) : Size(100, 100);
+ nX *= aLogicSizeOnePixel.Width();
+ nY *= aLogicSizeOnePixel.Height();
+ }
+ else
+ {
+ // old, fixed move distance
+ nX *= 100;
+ nY *= 100;
+ }
+
+ if( nX || nY )
+ {
+ // in point edit mode move the handle with the focus
+ const SdrHdlList& rHdlList = mrView.GetHdlList();
+ SdrHdl* pHdl = rHdlList.GetFocusHdl();
+
+ if(pHdl)
+ {
+ // now move the Handle (nX, nY)
+ Point aStartPoint(pHdl->GetPos());
+ Point aEndPoint(pHdl->GetPos() + Point(nX, nY));
+
+ // start dragging
+ rtl::Reference< MotionPathTag > xTag( this );
+ SdrDragMethod* pDragMethod = nullptr;
+ if( (pHdl->GetKind() == SdrHdlKind::Move) || (pHdl->GetKind() == SdrHdlKind::SmartTag) )
+ {
+ pDragMethod = new PathDragMove( mrView, xTag );
+ }
+ else if( pHdl->GetKind() == SdrHdlKind::Poly )
+ {
+ pDragMethod = new PathDragObjOwn( mrView );
+ }
+ else if( pHdl->GetKind() != SdrHdlKind::BezierWeight )
+ {
+ pDragMethod = new PathDragResize( mrView, xTag );
+ }
+ mrView.BegDragObj(aStartPoint, nullptr, pHdl, 0, pDragMethod);
+
+ if(mrView.IsDragObj())
+ {
+ bool bWasNoSnap = mrView.GetDragStat().IsNoSnap();
+ bool bWasSnapEnabled = mrView.IsSnapEnabled();
+
+ // switch snapping off
+ if(!bWasNoSnap)
+ const_cast<SdrDragStat&>(mrView.GetDragStat()).SetNoSnap();
+ if(bWasSnapEnabled)
+ mrView.SetSnapEnabled(false);
+
+ mrView.MovAction(aEndPoint);
+ mrView.EndDragObj();
+
+ // restore snap
+ if(!bWasNoSnap)
+ const_cast<SdrDragStat&>(mrView.GetDragStat()).SetNoSnap(bWasNoSnap);
+ if(bWasSnapEnabled)
+ mrView.SetSnapEnabled(bWasSnapEnabled);
+ }
+ }
+ else
+ {
+ // move the path
+ MovePath( nX, nY );
+ }
+ }
+
+ return true;
+}
+
+sal_Int32 MotionPathTag::GetMarkablePointCount() const
+{
+ if( mpPathObj && isSelected() )
+ {
+ return mpPathObj->GetPointCount();
+ }
+ else
+ {
+ return 0;
+ }
+}
+
+sal_Int32 MotionPathTag::GetMarkedPointCount() const
+{
+ if( mpMark )
+ {
+ const SdrUShortCont& rPts = mpMark->GetMarkedPoints();
+ return rPts.size();
+ }
+ else
+ {
+ return 0;
+ }
+}
+
+bool MotionPathTag::MarkPoint(SdrHdl& rHdl, bool bUnmark )
+{
+ bool bRet=false;
+ if( mpPathObj && mrView.IsPointMarkable( rHdl ) && (rHdl.GetKind() != SdrHdlKind::SmartTag) )
+ {
+ SmartHdl* pSmartHdl = dynamic_cast< SmartHdl* >( &rHdl );
+ if( pSmartHdl && pSmartHdl->getTag().get() == this )
+ {
+ if (mrView.MarkPointHelper(&rHdl,mpMark.get(),bUnmark))
+ {
+ mrView.MarkListHasChanged();
+ bRet=true;
+ }
+ }
+ }
+ return bRet;
+}
+
+bool MotionPathTag::MarkPoints(const ::tools::Rectangle* pRect, bool bUnmark )
+{
+ bool bChgd=false;
+
+ if( mpPathObj && isSelected() )
+ {
+ size_t nHdlNum = mrView.GetHdlList().GetHdlCount();
+ if ( nHdlNum <= 1 )
+ return false;
+
+ while( --nHdlNum > 0 )
+ {
+ SmartHdl* pHdl = dynamic_cast< SmartHdl* >( mrView.GetHdl( nHdlNum ) );
+
+ if( pHdl && (pHdl->getTag().get() == this) && mrView.IsPointMarkable(*pHdl) && pHdl->IsSelected() == bUnmark)
+ {
+ Point aPos(pHdl->GetPos());
+ if( pRect==nullptr || pRect->Contains(aPos))
+ {
+ if( mrView.MarkPointHelper(pHdl,mpMark.get(),bUnmark) )
+ bChgd=true;
+ }
+ }
+ }
+
+ if(bChgd)
+ mrView.MarkListHasChanged();
+ }
+
+ return bChgd;
+}
+
+bool MotionPathTag::getContext( SdrViewContext& rContext )
+{
+ if( mpPathObj && isSelected() && !mrView.IsFrameDragSingles() )
+ {
+ rContext = SdrViewContext::PointEdit;
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+void MotionPathTag::CheckPossibilities()
+{
+ if( !(mpPathObj && isSelected()) )
+ return;
+
+ mrView.SetMoveAllowed( true );
+ mrView.SetMoveProtected( false );
+ mrView.SetResizeFreeAllowed( true );
+ mrView.SetResizePropAllowed( true );
+ mrView.SetResizeProtected( false );
+
+ if( !mrView.IsFrameDragSingles() )
+ {
+ bool b1stSmooth(true);
+ bool b1stSegm(true);
+ bool bCurve(false);
+ bool bSmoothFuz(false);
+ bool bSegmFuz(false);
+ basegfx::B2VectorContinuity eSmooth = basegfx::B2VectorContinuity::NONE;
+
+ mrView.CheckPolyPossibilitiesHelper( mpMark.get(), b1stSmooth, b1stSegm, bCurve, bSmoothFuz, bSegmFuz, eSmooth );
+ }
+}
+
+void MotionPathTag::addCustomHandles( SdrHdlList& rHandlerList )
+{
+ if( !mpPathObj )
+ return;
+
+ css::awt::Point aPos;
+ if (mxOrigin.is())
+ aPos = mxOrigin->getPosition();
+ if( (aPos.X != maOriginPos.X) || (aPos.Y != maOriginPos.Y) )
+ {
+ const basegfx::B2DHomMatrix aTransform(basegfx::utils::createTranslateB2DHomMatrix(
+ aPos.X - maOriginPos.X, aPos.Y - maOriginPos.Y));
+ mxPolyPoly.transform( aTransform );
+ mpPathObj->SetPathPoly( mxPolyPoly );
+ maOriginPos = aPos;
+ }
+
+ SmartTagReference xThis( this );
+ std::unique_ptr<SdPathHdl> pHdl(new SdPathHdl( xThis, mpPathObj ));
+ pHdl->SetObjHdlNum( SMART_TAG_HDL_NUM );
+ pHdl->SetPageView( mrView.GetSdrPageView() );
+ pHdl->SetObj(mpPathObj);
+ rHandlerList.AddHdl( std::move(pHdl) );
+
+ if( !isSelected() )
+ return;
+
+ mrView.GetSdrPageView()->SetHasMarkedObj(true);
+
+ if( !mrView.IsFrameDragSingles() )
+ {
+ SdrHdlList aTemp( rHandlerList.GetView() );
+ mpPathObj->AddToHdlList( aTemp );
+ const SdrUShortCont& rMrkPnts = mpMark->GetMarkedPoints();
+
+ for( size_t nHandle = 0; nHandle < aTemp.GetHdlCount(); ++nHandle )
+ {
+ SdrHdl* pTempHdl = aTemp.GetHdl( nHandle );
+
+ SmartHdl* pSmartHdl = new SmartHdl( xThis, mpPathObj, pTempHdl->GetPos(), pTempHdl->GetKind() );
+ pSmartHdl->SetObjHdlNum( static_cast<sal_uInt32>(nHandle) );
+ pSmartHdl->SetPolyNum( pTempHdl->GetPolyNum() );
+ pSmartHdl->SetPointNum( pTempHdl->GetPointNum() );
+ pSmartHdl->SetPlusHdl( pTempHdl->IsPlusHdl() );
+ pSmartHdl->SetSourceHdlNum( pTempHdl->GetSourceHdlNum() );
+ pSmartHdl->SetPageView( mrView.GetSdrPageView() );
+
+ rHandlerList.AddHdl( std::unique_ptr<SmartHdl>(pSmartHdl) );
+
+ const bool bSelected = rMrkPnts.find( sal_uInt16(nHandle) ) != rMrkPnts.end();
+ pSmartHdl->SetSelected(bSelected);
+
+ if( mrView.IsPlusHandlesAlwaysVisible() || bSelected )
+ {
+ SdrHdlList plusList(nullptr);
+ mpPathObj->AddToPlusHdlList(plusList, *pSmartHdl);
+ sal_uInt32 nPlusHdlCnt=plusList.GetHdlCount();
+ for (sal_uInt32 nPlusNum=0; nPlusNum<nPlusHdlCnt; nPlusNum++)
+ {
+ SdrHdl* pPlusHdl = plusList.GetHdl(nPlusNum);
+ pPlusHdl->SetObj(mpPathObj);
+ pPlusHdl->SetPageView(mrView.GetSdrPageView());
+ pPlusHdl->SetPlusHdl(true);
+ }
+ plusList.MoveTo(rHandlerList);
+ }
+ }
+ }
+ else
+ {
+ ::tools::Rectangle aRect(mpPathObj->GetCurrentBoundRect());
+
+ if(!aRect.IsEmpty())
+ {
+ size_t nCount = rHandlerList.GetHdlCount();
+
+ bool bWdt0=aRect.Left()==aRect.Right();
+ bool bHgt0=aRect.Top()==aRect.Bottom();
+ if (bWdt0 && bHgt0)
+ {
+ rHandlerList.AddHdl(std::make_unique<SmartHdl>( xThis, mpPathObj, aRect.TopLeft(),SdrHdlKind::UpperLeft));
+ }
+ else if (bWdt0 || bHgt0)
+ {
+ rHandlerList.AddHdl(std::make_unique<SmartHdl>( xThis, mpPathObj, aRect.TopLeft() ,SdrHdlKind::UpperLeft));
+ rHandlerList.AddHdl(std::make_unique<SmartHdl>( xThis, mpPathObj, aRect.BottomRight(),SdrHdlKind::LowerRight));
+ }
+ else // !bWdt0 && !bHgt0
+ {
+ rHandlerList.AddHdl(std::make_unique<SmartHdl>( xThis, mpPathObj, aRect.TopLeft() ,SdrHdlKind::UpperLeft));
+ rHandlerList.AddHdl(std::make_unique<SmartHdl>( xThis, mpPathObj, aRect.TopCenter() ,SdrHdlKind::Upper));
+ rHandlerList.AddHdl(std::make_unique<SmartHdl>( xThis, mpPathObj, aRect.TopRight() ,SdrHdlKind::UpperRight));
+ rHandlerList.AddHdl(std::make_unique<SmartHdl>( xThis, mpPathObj, aRect.LeftCenter() ,SdrHdlKind::Left ));
+ rHandlerList.AddHdl(std::make_unique<SmartHdl>( xThis, mpPathObj, aRect.RightCenter() ,SdrHdlKind::Right));
+ rHandlerList.AddHdl(std::make_unique<SmartHdl>( xThis, mpPathObj, aRect.BottomLeft() ,SdrHdlKind::LowerLeft));
+ rHandlerList.AddHdl(std::make_unique<SmartHdl>( xThis, mpPathObj, aRect.BottomCenter(),SdrHdlKind::Lower));
+ rHandlerList.AddHdl(std::make_unique<SmartHdl>( xThis, mpPathObj, aRect.BottomRight() ,SdrHdlKind::LowerRight));
+ }
+
+ while( nCount < rHandlerList.GetHdlCount() )
+ {
+ rHandlerList.GetHdl(nCount++)->SetPageView( mrView.GetSdrPageView() );
+ }
+ }
+ }
+}
+
+void MotionPathTag::disposing()
+{
+ Reference< XChangesNotifier > xNotifier( mpEffect->getNode(), UNO_QUERY );
+ if( xNotifier.is() )
+ {
+ xNotifier->removeChangesListener( this );
+ }
+
+ if( mpPathObj )
+ {
+ SdrObject* pTemp(mpPathObj);
+ mpPathObj = nullptr;
+ mrView.updateHandles();
+
+ // always use SdrObject::Free(...) for SdrObjects (!)
+ SdrObject::Free(pTemp);
+ }
+
+ mpMark.reset();
+
+ SmartTag::disposing();
+}
+
+void MotionPathTag::deselect()
+{
+ SmartTag::deselect();
+
+ if( mpMark )
+ {
+ SdrUShortCont& rPts = mpMark->GetMarkedPoints();
+ rPts.clear();
+ }
+
+ selectionChanged();
+}
+
+void MotionPathTag::selectionChanged()
+{
+ if( mrView.GetViewShell() && mrView.GetViewShell()->GetViewFrame() )
+ {
+ SfxBindings& rBindings = mrView.GetViewShell()->GetViewFrame()->GetBindings();
+ rBindings.InvalidateAll(true);
+ }
+}
+
+// IPolyPolygonEditorController
+
+void MotionPathTag::DeleteMarkedPoints()
+{
+ if( !(mpPathObj && IsDeleteMarkedPointsPossible()) )
+ return;
+
+ mrView.BrkAction();
+
+ SdrUShortCont& rPts = mpMark->GetMarkedPoints();
+ PolyPolygonEditor aEditor( mpPathObj->GetPathPoly());
+ if (aEditor.DeletePoints(rPts))
+ {
+ if( aEditor.GetPolyPolygon().count() )
+ {
+ mpPathObj->SetPathPoly( aEditor.GetPolyPolygon() );
+ }
+
+ mrView.UnmarkAllPoints();
+ mrView.MarkListHasChanged();
+ mrView.updateHandles();
+ }
+}
+
+bool MotionPathTag::IsDeleteMarkedPointsPossible() const
+{
+ return mpPathObj && isSelected() && (GetMarkedPointCount() != 0);
+}
+
+void MotionPathTag::RipUpAtMarkedPoints()
+{
+ // not supported for motion path
+}
+
+bool MotionPathTag::IsRipUpAtMarkedPointsPossible() const
+{
+ // not supported for motion path
+ return false;
+}
+
+bool MotionPathTag::IsSetMarkedSegmentsKindPossible() const
+{
+ if( mpPathObj )
+ return mrView.IsSetMarkedSegmentsKindPossible();
+ else
+ return false;
+}
+
+SdrPathSegmentKind MotionPathTag::GetMarkedSegmentsKind() const
+{
+ if( mpPathObj )
+ return mrView.GetMarkedSegmentsKind();
+ else
+ return SdrPathSegmentKind::Line;
+}
+
+void MotionPathTag::SetMarkedSegmentsKind(SdrPathSegmentKind eKind)
+{
+ if(mpPathObj && isSelected() && (GetMarkedPointCount() != 0))
+ {
+ SdrUShortCont& rPts = mpMark->GetMarkedPoints();
+ PolyPolygonEditor aEditor( mpPathObj->GetPathPoly() );
+ if (aEditor.SetSegmentsKind(eKind, rPts))
+ {
+ mpPathObj->SetPathPoly(aEditor.GetPolyPolygon());
+ mrView.MarkListHasChanged();
+ mrView.updateHandles();
+ }
+ }
+}
+
+bool MotionPathTag::IsSetMarkedPointsSmoothPossible() const
+{
+ if( mpPathObj )
+ return mrView.IsSetMarkedPointsSmoothPossible();
+ else
+ return false;
+}
+
+SdrPathSmoothKind MotionPathTag::GetMarkedPointsSmooth() const
+{
+ if( mpPathObj )
+ return mrView.GetMarkedPointsSmooth();
+ else
+ return SdrPathSmoothKind::Angular;
+}
+
+void MotionPathTag::SetMarkedPointsSmooth(SdrPathSmoothKind eKind)
+{
+ basegfx::B2VectorContinuity eFlags;
+
+ if(SdrPathSmoothKind::Angular == eKind)
+ {
+ eFlags = basegfx::B2VectorContinuity::NONE;
+ }
+ else if(SdrPathSmoothKind::Asymmetric == eKind)
+ {
+ eFlags = basegfx::B2VectorContinuity::C1;
+ }
+ else if(SdrPathSmoothKind::Symmetric == eKind)
+ {
+ eFlags = basegfx::B2VectorContinuity::C2;
+ }
+ else
+ {
+ return;
+ }
+
+ if(mpPathObj && mpMark && isSelected() && (GetMarkedPointCount() != 0))
+ {
+ SdrUShortCont& rPts = mpMark->GetMarkedPoints();
+ PolyPolygonEditor aEditor( mpPathObj->GetPathPoly());
+ if (aEditor.SetPointsSmooth(eFlags, rPts))
+ {
+ mpPathObj->SetPathPoly(aEditor.GetPolyPolygon());
+ mrView.MarkListHasChanged();
+ mrView.updateHandles();
+ }
+ }
+}
+
+bool MotionPathTag::IsOpenCloseMarkedObjectsPossible() const
+{
+ // not supported for motion path
+ return false;
+}
+
+SdrObjClosedKind MotionPathTag::GetMarkedObjectsClosedState() const
+{
+ // not supported for motion path
+ return SdrObjClosedKind::Open;
+}
+
+// XChangesListener
+void SAL_CALL MotionPathTag::changesOccurred( const ChangesEvent& /*Event*/ )
+{
+ if( mpPathObj && !mbInUpdatePath && (mpEffect->getPath() != msLastPath) )
+ {
+ mbInUpdatePath =true;
+ msLastPath = mpEffect->getPath();
+ mpEffect->updateSdrPathObjFromPath( *mpPathObj );
+ mbInUpdatePath = false;
+ updatePathAttributes();
+ mrView.updateHandles();
+ }
+}
+
+void SAL_CALL MotionPathTag::disposing( const EventObject& /*Source*/ )
+{
+ if( mpPathObj )
+ Dispose();
+}
+
+Any SAL_CALL MotionPathTag::queryInterface( const css::uno::Type& aType )
+{
+ if( aType == cppu::UnoType<XChangesListener>::get() )
+ return Any( Reference< XChangesListener >( this ) );
+ if( aType == cppu::UnoType<XEventListener>::get() )
+ return Any( Reference< XEventListener >( this ) );
+ if( aType == cppu::UnoType<XInterface>::get() )
+ return Any( Reference< XInterface >( this ) );
+
+ return Any();
+}
+
+void SAL_CALL MotionPathTag::acquire() noexcept
+{
+ SimpleReferenceComponent::acquire();
+}
+
+void SAL_CALL MotionPathTag::release( ) noexcept
+{
+ SimpleReferenceComponent::release();
+}
+
+} // end of namespace sd
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/animations/motionpathtag.hxx b/sd/source/ui/animations/motionpathtag.hxx
new file mode 100644
index 000000000..715ce4268
--- /dev/null
+++ b/sd/source/ui/animations/motionpathtag.hxx
@@ -0,0 +1,114 @@
+/* -*- 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 .
+ */
+
+#pragma once
+
+#include <com/sun/star/util/XChangesListener.hpp>
+#include <basegfx/polygon/b2dpolypolygon.hxx>
+#include <smarttag.hxx>
+#include <CustomAnimationList.hxx>
+
+namespace com::sun::star::drawing { class XShape; }
+class SdrPathObj;
+
+namespace sd {
+
+class View;
+class CustomAnimationPane;
+
+/// Base class for all functions.
+class MotionPathTag final : public SmartTag, public IPolyPolygonEditorController, public SfxListener, public css::util::XChangesListener
+{
+public:
+ MotionPathTag( CustomAnimationPane& rPane, ::sd::View& rView, const CustomAnimationEffectPtr& pEffect );
+ virtual ~MotionPathTag() override;
+
+ SdrPathObj* getPathObj() const { return mpPathObj; }
+
+ /// @return true if the SmartTag handled the event.
+ virtual bool MouseButtonDown( const MouseEvent&, SmartHdl& ) override;
+
+ /// @return true if the SmartTag consumes this event.
+ virtual bool KeyInput( const KeyEvent& rKEvt ) override;
+
+ // callbacks from sdr view
+ virtual sal_Int32 GetMarkablePointCount() const override;
+ virtual sal_Int32 GetMarkedPointCount() const override;
+ virtual bool MarkPoint(SdrHdl& rHdl, bool bUnmark) override;
+ virtual void CheckPossibilities() override;
+ virtual bool MarkPoints(const ::tools::Rectangle* pRect, bool bUnmark) override;
+
+ const CustomAnimationEffectPtr& getEffect() const { return mpEffect; }
+
+ virtual void Notify( SfxBroadcaster& rBC, const SfxHint& rHint ) override;
+
+ // IPolyPolygonEditorController
+ virtual void DeleteMarkedPoints() override;
+ virtual bool IsDeleteMarkedPointsPossible() const override;
+
+ virtual void RipUpAtMarkedPoints() override;
+ virtual bool IsRipUpAtMarkedPointsPossible() const override;
+
+ virtual bool IsSetMarkedSegmentsKindPossible() const override;
+ virtual SdrPathSegmentKind GetMarkedSegmentsKind() const override;
+ virtual void SetMarkedSegmentsKind(SdrPathSegmentKind eKind) override;
+
+ virtual bool IsSetMarkedPointsSmoothPossible() const override;
+ virtual SdrPathSmoothKind GetMarkedPointsSmooth() const override;
+ virtual void SetMarkedPointsSmooth(SdrPathSmoothKind eKind) override;
+
+ virtual bool IsOpenCloseMarkedObjectsPossible() const override;
+ virtual SdrObjClosedKind GetMarkedObjectsClosedState() const override;
+
+ void MovePath( int nDX, int nDY );
+ bool OnDelete();
+ bool OnTabHandles( const KeyEvent& rKEvt );
+ bool OnMarkHandle( const KeyEvent& rKEvt );
+ bool OnMove( const KeyEvent& rKEvt );
+
+ // XChangesListener
+ virtual void SAL_CALL changesOccurred( const css::util::ChangesEvent& Event ) override;
+ virtual void SAL_CALL disposing( const css::lang::EventObject& Source ) override;
+ virtual css::uno::Any SAL_CALL queryInterface( const css::uno::Type& aType ) override;
+ virtual void SAL_CALL acquire( ) noexcept override;
+ virtual void SAL_CALL release( ) noexcept override;
+
+private:
+ virtual void addCustomHandles( SdrHdlList& rHandlerList ) override;
+ virtual bool getContext( SdrViewContext& rContext ) override;
+ virtual void disposing() override;
+ virtual void deselect() override;
+
+ void updatePathAttributes();
+ void selectionChanged();
+
+ CustomAnimationPane& mrPane;
+ CustomAnimationEffectPtr mpEffect;
+ ::basegfx::B2DPolyPolygon mxPolyPoly;
+ css::uno::Reference< css::drawing::XShape > mxOrigin;
+ SdrPathObj* mpPathObj;
+ css::awt::Point maOriginPos;
+ std::unique_ptr<SdrMark> mpMark;
+ OUString msLastPath;
+ bool mbInUpdatePath;
+};
+
+} // end of namespace sd
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */