diff options
Diffstat (limited to '')
-rw-r--r-- | sd/source/ui/animations/CustomAnimationDialog.cxx | 2082 | ||||
-rw-r--r-- | sd/source/ui/animations/CustomAnimationDialog.hxx | 141 | ||||
-rw-r--r-- | sd/source/ui/animations/CustomAnimationList.cxx | 1232 | ||||
-rw-r--r-- | sd/source/ui/animations/CustomAnimationPane.cxx | 2578 | ||||
-rw-r--r-- | sd/source/ui/animations/STLPropertySet.cxx | 113 | ||||
-rw-r--r-- | sd/source/ui/animations/STLPropertySet.hxx | 74 | ||||
-rw-r--r-- | sd/source/ui/animations/SlideTransitionPane.cxx | 1153 | ||||
-rw-r--r-- | sd/source/ui/animations/motionpathtag.cxx | 1197 | ||||
-rw-r--r-- | sd/source/ui/animations/motionpathtag.hxx | 114 |
9 files changed, 8684 insertions, 0 deletions
diff --git a/sd/source/ui/animations/CustomAnimationDialog.cxx b/sd/source/ui/animations/CustomAnimationDialog.cxx new file mode 100644 index 0000000000..7ed2f45904 --- /dev/null +++ b/sd/source/ui/animations/CustomAnimationDialog.cxx @@ -0,0 +1,2082 @@ +/* -*- 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 <comphelper/diagnose_ex.hxx> + +#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 OUString& 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 OUString&, 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 OUString&, 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(OUString::number(i), nValue == i); +} + +IMPL_LINK_NOARG(SdTransparencyPropertyBox, implModifyHdl, weld::MetricSpinButton&, void) +{ + updateMenu(); + maModifyHdl.Call(nullptr); +} + +IMPL_LINK(SdTransparencyPropertyBox, implMenuSelectHdl, const OUString&, 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 OUString&, 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("clockwise", bDirection); + mxControl->set_item_active("counterclock", !bDirection); +} + +IMPL_LINK_NOARG(SdRotationPropertyBox, implModifyHdl, weld::MetricSpinButton&, void) +{ + updateMenu(); + maModifyHdl.Call(nullptr); +} + +IMPL_LINK(SdRotationPropertyBox, implMenuSelectHdl, const OUString&, 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 OUString&, 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 OUString&, 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 OUString&, 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 OUString&, 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::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")) + , 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::ComboBox> mxLBStart; + 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")) + , mxLBStart(mxBuilder->weld_combo_box("start_list")) + , 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 constexpr OUString aStrIsEmptyPresObj( u"IsEmptyPresentationObject"_ustr ); + + 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::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")) + , 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 OUString& 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 0000000000..34101fbc1f --- /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 OUString& 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 0000000000..39b3d488d2 --- /dev/null +++ b/sd/source/ui/animations/CustomAnimationList.cxx @@ -0,0 +1,1232 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <com/sun/star/document/XActionLockable.hpp> +#include <com/sun/star/drawing/XDrawPage.hpp> +#include <com/sun/star/drawing/XShapes.hpp> +#include <com/sun/star/presentation/ShapeAnimationSubType.hpp> +#include <com/sun/star/presentation/EffectNodeType.hpp> +#include <com/sun/star/presentation/ParagraphTarget.hpp> +#include <com/sun/star/container/XEnumerationAccess.hpp> +#include <com/sun/star/presentation/EffectPresetClass.hpp> +#include <com/sun/star/presentation/EffectCommands.hpp> +#include <com/sun/star/text/XTextRange.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <comphelper/scopeguard.hxx> +#include <CustomAnimationList.hxx> +#include <CustomAnimationPreset.hxx> +#include <utility> +#include <vcl/commandevent.hxx> +#include <vcl/event.hxx> +#include <vcl/image.hxx> +#include <vcl/settings.hxx> +#include <vcl/svapp.hxx> +#include <vcl/weldutils.hxx> +#include <tools/debug.hxx> +#include <tools/gen.hxx> +#include <comphelper/diagnose_ex.hxx> + +#include <sdresid.hxx> + +#include <strings.hrc> +#include <bitmaps.hlst> + +#include <algorithm> +#include <memory> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::presentation; + +using ::com::sun::star::uno::UNO_QUERY; +using ::com::sun::star::uno::UNO_QUERY_THROW; +using ::com::sun::star::uno::Any; +using ::com::sun::star::uno::Reference; +using ::com::sun::star::uno::Exception; +using ::com::sun::star::uno::XInterface; +using ::com::sun::star::text::XTextRange; +using ::com::sun::star::drawing::XShape; +using ::com::sun::star::drawing::XShapes; +using ::com::sun::star::drawing::XDrawPage; +using ::com::sun::star::container::XChild; +using ::com::sun::star::container::XIndexAccess; +using ::com::sun::star::container::XEnumerationAccess; +using ::com::sun::star::container::XEnumeration; +using ::com::sun::star::beans::XPropertySet; +using ::com::sun::star::beans::XPropertySetInfo; + +namespace sd { + +// go recursively through all shapes in the given XShapes collection and return true as soon as the +// given shape is found. nIndex is incremented for each shape with the same shape type as the given +// shape is found until the given shape is found. +static bool getShapeIndex( const Reference< XShapes >& xShapes, const Reference< XShape >& xShape, sal_Int32& nIndex ) +{ + const sal_Int32 nCount = xShapes->getCount(); + sal_Int32 n; + for( n = 0; n < nCount; n++ ) + { + Reference< XShape > xChild; + xShapes->getByIndex( n ) >>= xChild; + if( xChild == xShape ) + return true; + + if( xChild->getShapeType() == xShape->getShapeType() ) + nIndex++; + + Reference< XShapes > xChildContainer( xChild, UNO_QUERY ); + if( xChildContainer.is() ) + { + if( getShapeIndex( xChildContainer, xShape, nIndex ) ) + return true; + } + } + + return false; +} + +// returns the index of the shape type from the given shape +static sal_Int32 getShapeIndex( const Reference< XShape >& xShape ) +{ + Reference< XChild > xChild( xShape, UNO_QUERY ); + Reference< XShapes > xPage; + + while( xChild.is() && !xPage.is() ) + { + Reference< XInterface > x( xChild->getParent() ); + xChild.set( x, UNO_QUERY ); + Reference< XDrawPage > xTestPage( x, UNO_QUERY ); + if( xTestPage.is() ) + xPage.set( x, UNO_QUERY ); + } + + sal_Int32 nIndex = 1; + + if( xPage.is() && getShapeIndex( xPage, xShape, nIndex ) ) + return nIndex; + else + return -1; +} + +OUString getShapeDescription( const Reference< XShape >& xShape, bool bWithText ) +{ + OUString aDescription; + Reference< XPropertySet > xSet( xShape, UNO_QUERY ); + bool bAppendIndex = true; + + if(xSet.is()) try + { + Reference<XPropertySetInfo> xInfo(xSet->getPropertySetInfo()); + if (xInfo.is()) + { + static constexpr OUString aPropName1(u"Name"_ustr); + if(xInfo->hasPropertyByName(aPropName1)) + xSet->getPropertyValue(aPropName1) >>= aDescription; + + bAppendIndex = aDescription.isEmpty(); + + static constexpr OUString aPropName2(u"UINameSingular"_ustr); + if(xInfo->hasPropertyByName(aPropName2)) + xSet->getPropertyValue(aPropName2) >>= aDescription; + } + } + catch( Exception& ) + { + TOOLS_WARN_EXCEPTION( "sd", "sd::getShapeDescription()" ); + } + + if (bAppendIndex) + { + aDescription += " " + OUString::number(getShapeIndex(xShape)); + } + + if( bWithText ) + { + Reference< XTextRange > xText( xShape, UNO_QUERY ); + if( xText.is() ) + { + OUString aText( xText->getString() ); + if( !aText.isEmpty() ) + { + aDescription += ": "; + + aText = aText.replace( '\n', ' ' ); + aText = aText.replace( '\r', ' ' ); + + aDescription += aText; + } + } + } + return aDescription; +} + +static OUString getDescription( const Any& rTarget, bool bWithText ) +{ + OUString aDescription; + + if( rTarget.getValueType() == ::cppu::UnoType<ParagraphTarget>::get() ) + { + ParagraphTarget aParaTarget; + rTarget >>= aParaTarget; + + css::uno::Reference<css::document::XActionLockable> xLockable(aParaTarget.Shape, css::uno::UNO_QUERY); + if (xLockable.is()) + xLockable->addActionLock(); + comphelper::ScopeGuard aGuard([&xLockable]() + { + if (xLockable.is()) + xLockable->removeActionLock(); + }); + + Reference< XEnumerationAccess > xText( aParaTarget.Shape, UNO_QUERY_THROW ); + Reference< XEnumeration > xEnumeration( xText->createEnumeration(), css::uno::UNO_SET_THROW ); + sal_Int32 nPara = aParaTarget.Paragraph; + + while( xEnumeration->hasMoreElements() && nPara ) + { + xEnumeration->nextElement(); + nPara--; + } + + DBG_ASSERT( xEnumeration->hasMoreElements(), "sd::CustomAnimationEffect::prepareText(), paragraph out of range!" ); + + if( xEnumeration->hasMoreElements() ) + { + Reference< XTextRange > xParagraph; + xEnumeration->nextElement() >>= xParagraph; + + if( xParagraph.is() ) + aDescription = xParagraph->getString(); + } + } + else + { + Reference< XShape > xShape; + rTarget >>= xShape; + if( xShape.is() ) + aDescription = getShapeDescription( xShape, bWithText ); + } + + return aDescription; +} + +class CustomAnimationListEntryItem +{ +public: + CustomAnimationListEntryItem(OUString aDescription, + CustomAnimationEffectPtr pEffect); + const CustomAnimationEffectPtr& getEffect() const { return mpEffect; } + + Size GetSize(const vcl::RenderContext& rRenderContext); + void Paint(vcl::RenderContext& rRenderContext, const ::tools::Rectangle& rRect, bool bSelected); + void PaintEffect(vcl::RenderContext& rRenderContext, const ::tools::Rectangle& rRect, bool bSelected); + void PaintTrigger(vcl::RenderContext& rRenderContext, const ::tools::Rectangle& rRect); + +private: + OUString msDescription; + OUString msEffectName; + CustomAnimationEffectPtr mpEffect; + +public: + static const ::tools::Long nIconWidth = 19; + static const ::tools::Long nItemMinHeight = 38; +}; + +CustomAnimationListEntryItem::CustomAnimationListEntryItem(OUString aDescription, CustomAnimationEffectPtr pEffect) + : msDescription(std::move(aDescription)) + , mpEffect(std::move(pEffect)) +{ + if (!mpEffect) + return; + switch (mpEffect->getPresetClass()) + { + case EffectPresetClass::ENTRANCE: + msEffectName = SdResId(STR_CUSTOMANIMATION_ENTRANCE); break; + case EffectPresetClass::EXIT: + msEffectName = SdResId(STR_CUSTOMANIMATION_EXIT); break; + case EffectPresetClass::EMPHASIS: + msEffectName = SdResId(STR_CUSTOMANIMATION_EMPHASIS); break; + case EffectPresetClass::MOTIONPATH: + msEffectName = SdResId(STR_CUSTOMANIMATION_MOTION_PATHS); break; + default: + msEffectName = SdResId(STR_CUSTOMANIMATION_MISC); break; + } + msEffectName = msEffectName.replaceFirst( "%1" , CustomAnimationPresets::getCustomAnimationPresets().getUINameForPresetId(mpEffect->getPresetId())); +} + +IMPL_STATIC_LINK(CustomAnimationList, CustomRenderHdl, weld::TreeView::render_args, aPayload, void) +{ + vcl::RenderContext& rRenderContext = std::get<0>(aPayload); + const ::tools::Rectangle& rRect = std::get<1>(aPayload); + bool bSelected = std::get<2>(aPayload); + const OUString& rId = std::get<3>(aPayload); + + CustomAnimationListEntryItem* pItem = weld::fromId<CustomAnimationListEntryItem*>(rId); + + pItem->Paint(rRenderContext, rRect, bSelected); +} + +IMPL_STATIC_LINK(CustomAnimationList, CustomGetSizeHdl, weld::TreeView::get_size_args, aPayload, Size) +{ + vcl::RenderContext& rRenderContext = aPayload.first; + const OUString& rId = aPayload.second; + + CustomAnimationListEntryItem* pItem = weld::fromId<CustomAnimationListEntryItem*>(rId); + if (!pItem) + return Size(CustomAnimationListEntryItem::nIconWidth, CustomAnimationListEntryItem::nItemMinHeight); + return pItem->GetSize(rRenderContext); +} + +Size CustomAnimationListEntryItem::GetSize(const vcl::RenderContext& rRenderContext) +{ + auto width = rRenderContext.GetTextWidth( msDescription ) + nIconWidth; + if (width < (rRenderContext.GetTextWidth( msEffectName ) + 2*nIconWidth)) + width = rRenderContext.GetTextWidth( msEffectName ) + 2*nIconWidth; + + Size aSize(width, rRenderContext.GetTextHeight()); + 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(1, true); +} + +CustomAnimationListDropTarget::CustomAnimationListDropTarget(CustomAnimationList& rTreeView) + : DropTargetHelper(rTreeView.get_widget().get_drop_target()) + , m_rTreeView(rTreeView) +{ +} + +sal_Int8 CustomAnimationListDropTarget::AcceptDrop(const AcceptDropEvent& rEvt) +{ + sal_Int8 nAccept = m_rTreeView.AcceptDrop(rEvt); + + if (nAccept != DND_ACTION_NONE) + { + // to enable the autoscroll when we're close to the edges + weld::TreeView& rWidget = m_rTreeView.get_widget(); + rWidget.get_dest_row_at_pos(rEvt.maPosPixel, nullptr, true); + } + + return nAccept; +} + +sal_Int8 CustomAnimationListDropTarget::ExecuteDrop(const ExecuteDropEvent& rEvt) +{ + return m_rTreeView.ExecuteDrop(rEvt); +} + +// D'n'D #1: Record selected effects for drag'n'drop. +IMPL_LINK(CustomAnimationList, DragBeginHdl, bool&, rUnsetDragIcon, bool) +{ + rUnsetDragIcon = false; + + // Record which effects are selected: + // Since NextSelected(..) iterates through the selected items in the order they + // were selected, create a sorted list for simpler drag'n'drop algorithms. + mDndEffectsSelected.clear(); + mxTreeView->selected_foreach([this](weld::TreeIter& rEntry){ + mDndEffectsSelected.emplace_back(mxTreeView->make_iterator(&rEntry)); + return false; + }); + + // Note: pEntry is the effect with focus (if multiple effects are selected) + mxDndEffectDragging = mxTreeView->make_iterator(); + if (!mxTreeView->get_cursor(mxDndEffectDragging.get())) + mxDndEffectDragging.reset(); + + // Allow normal processing. + return false; +} + +// D'n'D #3: Called each time mouse moves during drag +sal_Int8 CustomAnimationList::AcceptDrop( const AcceptDropEvent& rEvt ) +{ + sal_Int8 ret = DND_ACTION_NONE; + + const bool bIsMove = DND_ACTION_MOVE == rEvt.mnAction; + if (mxDndEffectDragging && !rEvt.mbLeaving && bIsMove) + ret = DND_ACTION_MOVE; + return ret; +} + +// D'n'D #5: Tell model to update effect order. +sal_Int8 CustomAnimationList::ExecuteDrop(const ExecuteDropEvent& rEvt) +{ + std::unique_ptr<weld::TreeIter> xDndEffectInsertBefore(mxTreeView->make_iterator()); + if (!mxTreeView->get_dest_row_at_pos(rEvt.maPosPixel, xDndEffectInsertBefore.get(), true)) + xDndEffectInsertBefore.reset(); + + const bool bMovingEffect = ( mxDndEffectDragging != nullptr ); + const bool bMoveNotSelf = !xDndEffectInsertBefore || (mxDndEffectDragging && mxTreeView->iter_compare(*xDndEffectInsertBefore, *mxDndEffectDragging) != 0); + const bool bHaveSequence(mpMainSequence); + + if( bMovingEffect && bMoveNotSelf && bHaveSequence ) + { + CustomAnimationListEntryItem* pTarget = xDndEffectInsertBefore ? + weld::fromId<CustomAnimationListEntryItem*>(mxTreeView->get_id(*xDndEffectInsertBefore)) : + nullptr; + + // Build list of effects + std::vector< CustomAnimationEffectPtr > aEffects; + for( const auto &pEntry : mDndEffectsSelected ) + { + CustomAnimationListEntryItem* pCustomAnimationEffect = weld::fromId<CustomAnimationListEntryItem*>(mxTreeView->get_id(*pEntry)); + aEffects.push_back(pCustomAnimationEffect->getEffect()); + } + + // Callback to observer to have it update the model. + // If pTarget is null, pass nullptr to indicate end of list. + mpController->onDragNDropComplete( + std::move(aEffects), + pTarget ? pTarget->getEffect() : nullptr ); + + // Reset selection + mxTreeView->select(*mxDndEffectDragging); + Select(); + } + + // NOTE: Don't call default handler because all required + // move operations have been completed here to update the model. + return DND_ACTION_NONE; +} + +CustomAnimationList::~CustomAnimationList() +{ + if (mnPostExpandEvent) + { + Application::RemoveUserEvent(mnPostExpandEvent); + mnPostExpandEvent = nullptr; + } + + if (mnPostCollapseEvent) + { + Application::RemoveUserEvent(mnPostCollapseEvent); + mnPostCollapseEvent = nullptr; + } + + if( mpMainSequence ) + mpMainSequence->removeListener( this ); + + clear(); +} + +IMPL_LINK(CustomAnimationList, KeyInputHdl, const KeyEvent&, rKEvt, bool) +{ + const int nKeyCode = rKEvt.GetKeyCode().GetCode(); + switch (nKeyCode) + { + case KEY_DELETE: + mpController->onContextMenu("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.getOpenWidth() / 2, aRect.getOpenHeight() / 2); + const CommandEvent aCEvt(aPos, CommandEventId::ContextMenu); + CommandHdl(aCEvt); + return true; + } + } + } + return false; +} + +/** selects or deselects the given effect. + Selections of other effects are not changed */ +void CustomAnimationList::select( const CustomAnimationEffectPtr& pEffect ) +{ + CustomAnimationListEntryItem* pEntry = nullptr; + + std::unique_ptr<weld::TreeIter> xEntry = mxTreeView->make_iterator(); + if (mxTreeView->get_iter_first(*xEntry)) + { + do + { + CustomAnimationListEntryItem* pTestEntry = weld::fromId<CustomAnimationListEntryItem*>(mxTreeView->get_id(*xEntry)); + if (pTestEntry->getEffect() == pEffect) + { + mxTreeView->select(*xEntry); + mxTreeView->scroll_to_row(*xEntry); + pEntry = pTestEntry; + break; + } + } while (mxTreeView->iter_next(*xEntry)); + } + + if( !pEntry ) + { + append( pEffect ); + select( pEffect ); + } +} + +void CustomAnimationList::clear() +{ + mxEntries.clear(); + mxTreeView->clear(); + + mxEmptyLabelParent->show(); + mxTreeView->hide(); + + mxLastParentEntry.reset(); + mxLastTargetShape = nullptr; +} + +void CustomAnimationList::update( const MainSequencePtr& pMainSequence ) +{ + if( mpMainSequence ) + mpMainSequence->removeListener( this ); + + mpMainSequence = pMainSequence; + update(); + + if( mpMainSequence ) + mpMainSequence->addListener( this ); +} + +struct stl_append_effect_func +{ + explicit stl_append_effect_func( CustomAnimationList& rList ) : mrList( rList ) {} + void operator()(const CustomAnimationEffectPtr& pEffect); + CustomAnimationList& mrList; +}; + +void stl_append_effect_func::operator()(const CustomAnimationEffectPtr& pEffect) +{ + mrList.append( pEffect ); +} + +void CustomAnimationList::update() +{ + mbIgnorePaint = true; + + std::vector< CustomAnimationEffectPtr > aVisible; + std::vector< CustomAnimationEffectPtr > aSelected; + CustomAnimationEffectPtr aCurrent; + + CustomAnimationEffectPtr pFirstSelEffect; + CustomAnimationEffectPtr pLastSelEffect; + ::tools::Long nFirstVis = -1; + ::tools::Long nLastVis = -1; + ::tools::Long nFirstSelOld = -1; + ::tools::Long nLastSelOld = -1; + + std::unique_ptr<weld::TreeIter> xEntry = mxTreeView->make_iterator(); + + if( mpMainSequence ) + { + std::unique_ptr<weld::TreeIter> xLastSelectedEntry; + std::unique_ptr<weld::TreeIter> xLastVisibleEntry; + + // save selection, current, and expand (visible) states + mxTreeView->all_foreach([this, &aVisible, &nFirstVis, &xLastVisibleEntry, + &aSelected, &nFirstSelOld, &pFirstSelEffect, &xLastSelectedEntry](weld::TreeIter& rEntry){ + CustomAnimationListEntryItem* pEntry = weld::fromId<CustomAnimationListEntryItem*>(mxTreeView->get_id(rEntry)); + CustomAnimationEffectPtr pEffect(pEntry->getEffect()); + if (pEffect) + { + if (weld::IsEntryVisible(*mxTreeView, rEntry)) + { + aVisible.push_back(pEffect); + // save scroll position + if (nFirstVis == -1) + nFirstVis = weld::GetAbsPos(*mxTreeView, rEntry); + if (!xLastVisibleEntry) + xLastVisibleEntry = mxTreeView->make_iterator(&rEntry); + else + mxTreeView->copy_iterator(rEntry, *xLastVisibleEntry); + } + + if (mxTreeView->is_selected(rEntry)) + { + aSelected.push_back(pEffect); + if (nFirstSelOld == -1) + { + pFirstSelEffect = 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); + + OUString sCommand = xMenu->popup_at_rect(mxTreeView.get(), ::tools::Rectangle(rCEvt.GetMousePosPixel(), Size(1,1))); + if (!sCommand.isEmpty()) + ExecuteContextMenuAction(sCommand); + + return true; +} + +void CustomAnimationList::ExecuteContextMenuAction(const OUString& rIdent) +{ + mpController->onContextMenu(rIdent); +} + +void CustomAnimationList::notify_change() +{ + update(); + mpController->onSelect(); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sd/source/ui/animations/CustomAnimationPane.cxx b/sd/source/ui/animations/CustomAnimationPane.cxx new file mode 100644 index 0000000000..7eb6231387 --- /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 <comphelper/diagnose_ex.hxx> + +#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 = mrBase.GetDrawController(); + 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 OUString &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 constexpr OUString aAttributeName( u"CharHeight"_ustr ); + 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 constexpr OUString aAttributeName( u"CharHeight"_ustr ); + 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 OUString& 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 0000000000..592d7639cd --- /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 0000000000..e2e088cd5a --- /dev/null +++ b/sd/source/ui/animations/STLPropertySet.hxx @@ -0,0 +1,74 @@ +/* -*- 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> +#include <utility> + +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(std::move( 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 0000000000..e2c954623e --- /dev/null +++ b/sd/source/ui/animations/SlideTransitionPane.cxx @@ -0,0 +1,1153 @@ +/* -*- 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 <utility> +#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( OUString aStr ) : + maStr(std::move( aStr )) + {} + + 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) +{ + mxLB_VARIANT = m_xBuilder->weld_combo_box("variant_list"); + 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 = pDoc->getUnoModel(); + // 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(); + mxLB_VARIANT.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; + + weld::Window* pDialogParent(GetFrameWeld()); + SdOpenSoundFileDialog aFileDialog(pDialogParent); + + 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(pDialogParent, + 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 0000000000..d3153f892b --- /dev/null +++ b/sd/source/ui/animations/motionpathtag.cxx @@ -0,0 +1,1197 @@ +/* -*- 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> +#include <utility> + +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, + rtl::Reference <MotionPathTag > xTag, + basegfx::B2DPolyPolygon aPathPolyPolygon) + : SdrDragMove(rNewView), + maPathPolyPolygon(std::move(aPathPolyPolygon)), + mxTag(std::move( xTag )) + {} + + PathDragMove(SdrDragView& rNewView, + rtl::Reference <MotionPathTag > xTag) + : SdrDragMove(rNewView), + mxTag(std::move( 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, + rtl::Reference <MotionPathTag > xTag, + basegfx::B2DPolyPolygon aPathPolyPolygon) + : SdrDragResize(rNewView), + maPathPolyPolygon(std::move(aPathPolyPolygon)), + mxTag(std::move( xTag )) + {} + + PathDragResize(SdrDragView& rNewView, + rtl::Reference <MotionPathTag > xTag) + : SdrDragResize(rNewView), + mxTag(std::move( 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, + basegfx::B2DPolyPolygon aPathPolyPolygon) + : SdrDragObjOwn(rNewView), + maPathPolyPolygon(std::move(aPathPolyPolygon)) + {} + + 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(!m_pHdlList) + return; + + SdrMarkView* pView = m_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.get(), 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() + 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, std::move(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.get() )); + pHdl->SetObjHdlNum( SMART_TAG_HDL_NUM ); + pHdl->SetPageView( mrView.GetSdrPageView() ); + pHdl->SetObj(mpPathObj.get()); + 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.get(), 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.get()); + 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.get(), aRect.TopLeft(),SdrHdlKind::UpperLeft)); + } + else if (bWdt0 || bHgt0) + { + rHandlerList.AddHdl(std::make_unique<SmartHdl>( xThis, mpPathObj.get(), aRect.TopLeft() ,SdrHdlKind::UpperLeft)); + rHandlerList.AddHdl(std::make_unique<SmartHdl>( xThis, mpPathObj.get(), aRect.BottomRight(),SdrHdlKind::LowerRight)); + } + else // !bWdt0 && !bHgt0 + { + rHandlerList.AddHdl(std::make_unique<SmartHdl>( xThis, mpPathObj.get(), aRect.TopLeft() ,SdrHdlKind::UpperLeft)); + rHandlerList.AddHdl(std::make_unique<SmartHdl>( xThis, mpPathObj.get(), aRect.TopCenter() ,SdrHdlKind::Upper)); + rHandlerList.AddHdl(std::make_unique<SmartHdl>( xThis, mpPathObj.get(), aRect.TopRight() ,SdrHdlKind::UpperRight)); + rHandlerList.AddHdl(std::make_unique<SmartHdl>( xThis, mpPathObj.get(), aRect.LeftCenter() ,SdrHdlKind::Left )); + rHandlerList.AddHdl(std::make_unique<SmartHdl>( xThis, mpPathObj.get(), aRect.RightCenter() ,SdrHdlKind::Right)); + rHandlerList.AddHdl(std::make_unique<SmartHdl>( xThis, mpPathObj.get(), aRect.BottomLeft() ,SdrHdlKind::LowerLeft)); + rHandlerList.AddHdl(std::make_unique<SmartHdl>( xThis, mpPathObj.get(), aRect.BottomCenter(),SdrHdlKind::Lower)); + rHandlerList.AddHdl(std::make_unique<SmartHdl>( xThis, mpPathObj.get(), 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 ) + { + mpPathObj = nullptr; + mrView.updateHandles(); + } + + 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 0000000000..7e7973385c --- /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.get(); } + + /// @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; + rtl::Reference<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: */ |