/* -*- 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/animations/XAnimationNode.hpp>
 
#include <SlideTransitionPane.hxx>
#include <createslidetransitionpanel.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 <DrawController.hxx>
#include <com/sun/star/beans/XPropertySet.hpp>
#include <EventMultiplexer.hxx>
 
#include <sal/log.hxx>
#include <svtools/controldims.hxx>
#include <svx/gallery.hxx>
#include <unotools/pathoptions.hxx>
#include <vcl/svapp.hxx>
#include <vcl/weld.hxx>
#include <tools/urlobj.hxx>
#include <DrawViewShell.hxx>
#include <slideshow.hxx>
#include <drawview.hxx>
#include <sdundogr.hxx>
#include <undoanim.hxx>
#include <optsitem.hxx>
#include <sddll.hxx>
 
#include <sfx2/sidebar/Theme.hxx>
 
#include <algorithm>
 
using namespace ::com::sun::star;
 
using ::com::sun::star::uno::Reference;
 
 
namespace sd
{
namespace 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 impl
} // namespace sd
 
// Local Helper Functions
namespace
{
 
void lcl_ApplyToPages(
    const ::sd::slidesorter::SharedPageSelection& rpPages,
    const ::sd::impl::TransitionEffect & rEffect )
{
    ::std::vector< SdPage * >::const_iterator aIt( rpPages->begin());
    const ::std::vector< SdPage * >::const_iterator aEndIt( rpPages->end());
    for( ; aIt != aEndIt; ++aIt )
    {
        rEffect.applyTo( *(*aIt) );
    }
}
 
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());
    SdUndoGroup* pUndoGroup = new SdUndoGroup( pDoc );
    pUndoGroup->SetComment( aComment );
 
    ::std::vector< SdPage * >::const_iterator aIt( rpPages->begin());
    const ::std::vector< SdPage * >::const_iterator aEndIt( rpPages->end());
    for( ; aIt != aEndIt; ++aIt )
    {
        pUndoGroup->AddAction( new sd::UndoTransition( pDoc, (*aIt) ) );
    }
 
    pManager->AddUndoAction( pUndoGroup );
    pManager->LeaveListAction();
}
 
struct lcl_EqualsSoundFileName
{
    explicit lcl_EqualsSoundFileName( const OUString & rStr ) :
            maStr( rStr )
    {}
 
    bool operator() ( const OUString & rStr ) const
    {
        // note: formerly this was a case insensitive search for all
        // platforms. It seems more sensible to do this platform-dependent
        INetURLObject aURL(rStr);
#if defined(_WIN32)
        return maStr.equalsIgnoreAsciiCase( aURL.GetBase() );
#else
        return maStr == aURL.GetBase();
#endif
    }
 
private:
    OUString maStr;
};
 
// returns -1 if no object was found
bool lcl_findSoundInList( const ::std::vector< OUString > & rSoundList,
                          const OUString & 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 ListBox* rListBox )
{
    if( rListBox->GetSelectedEntryCount() > 0 )
    {
        sal_Int32 nPos = rListBox->GetSelectedEntryPos();
        // the first three entries are no actual sounds
        if( nPos >= 3 )
        {
            DBG_ASSERT( static_cast<sal_uInt32>(rListBox->GetEntryCount() - 3) == rSoundList.size(),
                        "Sound list-box is not synchronized to sound list" );
            nPos -= 3;
            if( rSoundList.size() > static_cast<size_t>(nPos) )
                return rSoundList[ nPos ];
        }
    }
 
    return OUString();
}
 
struct lcl_AppendSoundToListBox
{
    explicit lcl_AppendSoundToListBox( ListBox*  rListBox ) :
            mrListBox( rListBox )
    {}
 
    void operator() ( const OUString & rString ) const
    {
        INetURLObject aURL( rString );
        mrListBox->InsertEntry( aURL.GetBase() );
    }
 
private:
    VclPtr<ListBox>  mrListBox;
};
 
void lcl_FillSoundListBox(
    const ::std::vector< OUString > & rSoundList,
    ListBox*  rOutListBox )
{
    sal_Int32 nCount = rOutListBox->GetEntryCount();
 
    // keep first three entries
    for( sal_Int32 i=nCount - 1; i>=3; --i )
        rOutListBox->RemoveEntry( 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();
    sd::TransitionPresetPtr pFound;
 
    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( vcl::Window *pParent )
        : ValueSet( pParent, WB_TABSTOP | WB_3DLOOK | WB_BORDER |
                  WB_ITEMBORDER | WB_FLATVALUESET | WB_VSCROLL )
    {
        EnableFullItemMode( false );
        set_hexpand( true );
        set_vexpand( true );
        SetExtraSpacing( 2 );
    }
    virtual ~TransitionPane() override { disposeOnce(); }
 
    virtual Size GetOptimalSize() const override
    {
        return LogicToPixel(Size(70, 88), MapMode(MapUnit::MapAppFont));
    }
};
 
// SlideTransitionPane
SlideTransitionPane::SlideTransitionPane(
    Window * pParent,
    ViewShellBase & rBase,
    SdDrawDocument* pDoc,
    const css::uno::Reference<css::frame::XFrame>& rxFrame ) :
        PanelLayout( pParent, "SlideTransitionsPanel", "modules/simpress/ui/slidetransitionspanel.ui", rxFrame ),
 
        mrBase( rBase ),
        mpDrawDoc( pDoc ),
        mbHasSelection( false ),
        mbUpdatingControls( false ),
        mbIsMainViewChangePending( false ),
        mbHorizontalLayout( false ),
        maLateInitTimer()
{
    Initialize(pDoc);
}
 
SlideTransitionPane::SlideTransitionPane(
    Window * pParent,
    ViewShellBase & rBase,
    SdDrawDocument* pDoc,
    const css::uno::Reference<css::frame::XFrame>& rxFrame,
    bool /*bHorizontalLayout*/ ) :
        PanelLayout( pParent, "SlideTransitionsPanel", "modules/simpress/ui/slidetransitionspanelhorizontal.ui", rxFrame ),
 
        mrBase( rBase ),
        mpDrawDoc( pDoc ),
        mbHasSelection( false ),
        mbUpdatingControls( false ),
        mbIsMainViewChangePending( false ),
        mbHorizontalLayout( true ),
        maLateInitTimer()
{
    Initialize(pDoc);
}
 
void SlideTransitionPane::Initialize(SdDrawDocument* pDoc)
{
    get(mpFT_VARIANT, "variant_label");
    get(mpLB_VARIANT, "variant_list");
    get(mpFT_duration, "duration_label");
    get(mpCBX_duration, "transition_duration");
    get(mpFT_SOUND, "sound_label");
    get(mpLB_SOUND, "sound_list");
    get(mpCB_LOOP_SOUND, "loop_sound" );
    get(mpRB_ADVANCE_ON_MOUSE, "rb_mouse_click");
    get(mpRB_ADVANCE_AUTO, "rb_auto_after");
    get(mpMF_ADVANCE_AUTO_AFTER, "auto_after_value");
    auto nMax = mpMF_ADVANCE_AUTO_AFTER->GetMax();
    mpMF_ADVANCE_AUTO_AFTER->SetMax(1000);
    Size aOptimalSize(mpMF_ADVANCE_AUTO_AFTER->CalcMinimumSize());
    mpMF_ADVANCE_AUTO_AFTER->set_width_request(aOptimalSize.Width());
    mpMF_ADVANCE_AUTO_AFTER->SetMax(nMax);
    get(mpPB_APPLY_TO_ALL, "apply_to_all");
    get(mpPB_PLAY, "play");
    get(mpCB_AUTO_PREVIEW, "auto_preview");
 
    VclPtr<VclAlignment> xAlign;
    get(xAlign, "transitions_icons");
 
    mpVS_TRANSITION_ICONS = VclPtr<TransitionPane>::Create(xAlign);
    mpVS_TRANSITION_ICONS->Show();
 
    mpLB_VARIANT->SetDropDownLineCount(4);
 
    if( pDoc )
        mxModel.set( pDoc->getUnoModel(), uno::UNO_QUERY );
    // TODO: get correct view
    if( mxModel.is())
        mxView.set( mxModel->getCurrentController(), uno::UNO_QUERY );
 
    // dummy list box of slide transitions for startup.
    mpVS_TRANSITION_ICONS->InsertItem(
        0, Image( BitmapEx( "sd/cmd/transition-none.png" ) ),
        SdResId( STR_SLIDETRANSITION_NONE ),
        VALUESET_APPEND, /* show legend */ true );
    mpVS_TRANSITION_ICONS->RecalculateItemSizes();
 
    mpCBX_duration->InsertValue(100, FUNIT_CUSTOM);
    mpCBX_duration->InsertValue(200, FUNIT_CUSTOM);
    mpCBX_duration->InsertValue(300, FUNIT_CUSTOM);
    mpCBX_duration->AdaptDropDownLineCountToMaximum();
 
    // set defaults
    mpCB_AUTO_PREVIEW->Check();      // automatic preview on
 
    // update control states before adding handlers
    updateControls();
 
    // set handlers
    mpPB_APPLY_TO_ALL->SetClickHdl( LINK( this, SlideTransitionPane, ApplyToAllButtonClicked ));
    mpPB_PLAY->SetClickHdl( LINK( this, SlideTransitionPane, PlayButtonClicked ));
 
    mpVS_TRANSITION_ICONS->SetSelectHdl( LINK( this, SlideTransitionPane, TransitionSelected ));
 
    mpLB_VARIANT->SetSelectHdl( LINK( this, SlideTransitionPane, VariantListBoxSelected ));
    mpCBX_duration->SetModifyHdl(LINK( this, SlideTransitionPane, DurationModifiedHdl));
    mpCBX_duration->SetLoseFocusHdl(LINK( this, SlideTransitionPane, DurationLoseFocusHdl));
    mpLB_SOUND->SetSelectHdl( LINK( this, SlideTransitionPane, SoundListBoxSelected ));
    mpCB_LOOP_SOUND->SetClickHdl( LINK( this, SlideTransitionPane, LoopSoundBoxChecked ));
 
    mpRB_ADVANCE_ON_MOUSE->SetToggleHdl( LINK( this, SlideTransitionPane, AdvanceSlideRadioButtonToggled ));
    mpRB_ADVANCE_AUTO->SetToggleHdl( LINK( this, SlideTransitionPane, AdvanceSlideRadioButtonToggled ));
    mpMF_ADVANCE_AUTO_AFTER->SetModifyHdl( LINK( this, SlideTransitionPane, AdvanceTimeModified ));
    mpCB_AUTO_PREVIEW->SetClickHdl( LINK( this, SlideTransitionPane, AutoPreviewClicked ));
    addListener();
 
    maLateInitTimer.SetTimeout(200);
    maLateInitTimer.SetInvokeHandler(LINK(this, SlideTransitionPane, LateInitCallback));
    maLateInitTimer.Start();
 
    UpdateLook();
}
 
SlideTransitionPane::~SlideTransitionPane()
{
    disposeOnce();
}
 
void SlideTransitionPane::dispose()
{
    maLateInitTimer.Stop();
    removeListener();
    mpVS_TRANSITION_ICONS.disposeAndClear();
    mpFT_VARIANT.clear();
    mpLB_VARIANT.clear();
    mpFT_duration.clear();
    mpCBX_duration.clear();
    mpFT_SOUND.clear();
    mpLB_SOUND.clear();
    mpCB_LOOP_SOUND.clear();
    mpRB_ADVANCE_ON_MOUSE.clear();
    mpRB_ADVANCE_AUTO.clear();
    mpMF_ADVANCE_AUTO_AFTER.clear();
    mpPB_APPLY_TO_ALL.clear();
    mpPB_PLAY.clear();
    mpCB_AUTO_PREVIEW.clear();
    PanelLayout::dispose();
}
 
void SlideTransitionPane::DataChanged (const DataChangedEvent&)
{
    UpdateLook();
}
 
void SlideTransitionPane::UpdateLook()
{
    if( mbHorizontalLayout )
    {
        SetBackground(Wallpaper());
    }
    else
    {
        SetBackground(::sfx2::sidebar::Theme::GetWallpaper(::sfx2::sidebar::Theme::Paint_PanelBackground));
        mpFT_duration->SetBackground(Wallpaper());
        mpFT_SOUND->SetBackground(Wallpaper());
    }
}
 
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.reset(new 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
    ::sd::slidesorter::SlideSorterViewShell::PageSelection::const_iterator aPageIt(
        pSelectedPages->begin());
    ::sd::slidesorter::SlideSorterViewShell::PageSelection::const_iterator aPageEndIt(
        pSelectedPages->end());
 
    // start with second page (note aIt != aEndIt, because ! aSelectedPages.empty())
    for( ++aPageIt; aPageIt != aPageEndIt; ++aPageIt )
    {
        if( *aPageIt )
            aEffect.compareWith( *(*aPageIt) );
    }
 
    // detect current slide effect
    if( aEffect.mbEffectAmbiguous )
    {
        SAL_WARN( "sd.transitions", "Unusual, ambiguous transition effect" );
        mpVS_TRANSITION_ICONS->SetNoSelection();
    }
    else
    {
        // ToDo: That 0 is "no transition" is documented nowhere except in the
        // CTOR of sdpage
        if( aEffect.mnType == 0 )
            mpVS_TRANSITION_ICONS->SetNoSelection();
        else
            updateVariants( getPresetOffset( aEffect ) );
    }
 
    if( aEffect.mbDurationAmbiguous )
    {
        mpCBX_duration->SetText("");
        mpCBX_duration->SetNoSelection();
    }
    else
    {
        mpCBX_duration->SetValue( (aEffect.mfDuration)*100.0 );
    }
 
    if( aEffect.mbSoundAmbiguous )
    {
        mpLB_SOUND->SetNoSelection();
        maCurrentSoundFile.clear();
    }
    else
    {
        maCurrentSoundFile.clear();
        if( aEffect.mbStopSound )
        {
            mpLB_SOUND->SelectEntryPos( 1 );
        }
        else if( aEffect.mbSoundOn && !aEffect.maSound.isEmpty() )
        {
            std::vector<OUString>::size_type nPos = 0;
            if( lcl_findSoundInList( maSoundList, aEffect.maSound, nPos ))
            {
                mpLB_SOUND->SelectEntryPos( nPos + 3 );
                maCurrentSoundFile = aEffect.maSound;
            }
        }
        else
        {
            mpLB_SOUND->SelectEntryPos( 0 );
        }
    }
 
    if( aEffect.mbLoopSoundAmbiguous )
    {
        mpCB_LOOP_SOUND->SetState( TRISTATE_INDET );
    }
    else
    {
        mpCB_LOOP_SOUND->Check( aEffect.mbLoopSound );
    }
 
    if( aEffect.mbPresChangeAmbiguous )
    {
        mpRB_ADVANCE_ON_MOUSE->Check( false );
        mpRB_ADVANCE_AUTO->Check( false );
    }
    else
    {
        mpRB_ADVANCE_ON_MOUSE->Check( aEffect.mePresChange == PRESCHANGE_MANUAL );
        mpRB_ADVANCE_AUTO->Check( aEffect.mePresChange == PRESCHANGE_AUTO );
        mpMF_ADVANCE_AUTO_AFTER->SetValue( aEffect.mfTime * 100.0);
    }
 
    SdOptions* pOptions = SD_MOD()->GetSdOptions(DocumentType::Impress);
    mpCB_AUTO_PREVIEW->Check( pOptions->IsPreviewTransitions() );
 
    mbUpdatingControls = false;
 
    updateControlState();
}
 
void SlideTransitionPane::updateControlState()
{
    mpVS_TRANSITION_ICONS->Enable( mbHasSelection );
    mpLB_VARIANT->Enable( mbHasSelection && mpLB_VARIANT->GetEntryCount() > 0 );
    mpCBX_duration->Enable( mbHasSelection );
    mpLB_SOUND->Enable( mbHasSelection );
    mpCB_LOOP_SOUND->Enable( mbHasSelection && (mpLB_SOUND->GetSelectedEntryPos() > 2));
    mpRB_ADVANCE_ON_MOUSE->Enable( mbHasSelection );
    mpRB_ADVANCE_AUTO->Enable( mbHasSelection );
    mpMF_ADVANCE_AUTO_AFTER->Enable( mbHasSelection && mpRB_ADVANCE_AUTO->IsChecked());
 
    mpPB_APPLY_TO_ALL->Enable( mbHasSelection );
    mpPB_PLAY->Enable( mbHasSelection );
    mpCB_AUTO_PREVIEW->Enable( mbHasSelection );
}
 
void SlideTransitionPane::updateSoundList()
{
    maSoundList.clear();
 
    GalleryExplorer::FillObjList( GALLERY_THEME_SOUNDS, maSoundList );
    GalleryExplorer::FillObjList( GALLERY_THEME_USERSOUNDS, maSoundList );
 
    lcl_FillSoundListBox( maSoundList, mpLB_SOUND );
}
 
void SlideTransitionPane::openSoundFileDialog()
{
    if( ! mpLB_SOUND->IsEnabled())
        return;
 
    SdOpenSoundFileDialog aFileDialog(GetFrameWeld());
 
    DBG_ASSERT( mpLB_SOUND->GetSelectedEntryPos() == 2,
                "Dialog should only open when \"Other sound\" is selected" );
 
    bool bValidSoundFile( false );
    bool bQuitLoop( false );
 
    while( ! bQuitLoop &&
           aFileDialog.Execute() == ERRCODE_NONE )
    {
        OUString aFile = aFileDialog.GetPath();
        std::vector<OUString>::size_type nPos = 0;
        bValidSoundFile = lcl_findSoundInList( maSoundList, aFile, nPos );
 
        if( bValidSoundFile )
        {
            bQuitLoop = true;
        }
        else // not in sound list
        {
            // try to insert into gallery
            if( GalleryExplorer::InsertURL( GALLERY_THEME_USERSOUNDS, aFile ) )
            {
                updateSoundList();
                bValidSoundFile = lcl_findSoundInList( maSoundList, aFile, nPos );
                DBG_ASSERT( bValidSoundFile, "Adding sound to gallery failed" );
 
                bQuitLoop = true;
            }
            else
            {
                OUString aStrWarning(SdResId(STR_WARNING_NOSOUNDFILE));
                aStrWarning = aStrWarning.replaceFirst("%", aFile);
                std::unique_ptr<weld::MessageDialog> xWarn(Application::CreateMessageDialog(nullptr,
                                                           VclMessageType::Warning, VclButtonsType::NONE,
                                                           aStrWarning));
                xWarn->add_button(Button::GetStandardText(StandardButtonType::Retry), RET_RETRY);
                xWarn->add_button(Button::GetStandardText(StandardButtonType::Cancel), RET_CANCEL);
                bQuitLoop = (xWarn->run() != RET_RETRY);
 
                bValidSoundFile = false;
            }
        }
 
        if( bValidSoundFile )
            // skip first three entries in list
            mpLB_SOUND->SelectEntryPos( nPos + 3 );
    }
 
    if( ! bValidSoundFile )
    {
        if( !maCurrentSoundFile.isEmpty() )
        {
            std::vector<OUString>::size_type nPos = 0;
            if( lcl_findSoundInList( maSoundList, maCurrentSoundFile, nPos ))
                mpLB_SOUND->SelectEntryPos( nPos + 3 );
            else
                mpLB_SOUND->SelectEntryPos( 0 );  // NONE
        }
        else
            mpLB_SOUND->SelectEntryPos( 0 );  // NONE
    }
}
 
impl::TransitionEffect SlideTransitionPane::getTransitionEffectFromControls() const
{
    impl::TransitionEffect aResult;
    aResult.setAllAmbiguous();
 
    // check first (aResult might be overwritten)
    if(  mpVS_TRANSITION_ICONS->IsEnabled() &&
        !mpVS_TRANSITION_ICONS->IsNoSelection() &&
         mpVS_TRANSITION_ICONS->GetSelectedItemId() > 0 )
    {
        const sd::TransitionPresetList& rPresetList = sd::TransitionPreset::getTransitionPresetList();
        auto aSelected = rPresetList.begin();
        std::advance( aSelected, mpVS_TRANSITION_ICONS->GetSelectedItemId() - 1);
 
        if( mpLB_VARIANT->GetSelectedEntryPos() == LISTBOX_ENTRY_NOTFOUND )
        {
            // 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( mpLB_VARIANT->GetSelectedEntryPos() == nVariant)
                    {
                        aResult = impl::TransitionEffect( *aIter );
                        aResult.setAllAmbiguous();
                        bFound = true;
                        break;
                    }
                    else
                    {
                        nVariant++;
                    }
                }
            }
            if( !bFound )
            {
                aResult.mnType = 0;
            }
        }
        aResult.mbEffectAmbiguous = false;
    }
    else if (mpVS_TRANSITION_ICONS->IsNoSelection())
    {
        aResult.mbEffectAmbiguous = false;
    }
 
    //duration
 
    if( mpCBX_duration->IsEnabled() && (!(mpCBX_duration->GetText()).isEmpty()) )
    {
        aResult.mfDuration = static_cast<double>(mpCBX_duration->GetValue())/100.0;
        aResult.mbDurationAmbiguous = false;
    }
 
    // slide-advance mode
    if( mpRB_ADVANCE_ON_MOUSE->IsEnabled() && mpRB_ADVANCE_AUTO->IsEnabled() &&
        (mpRB_ADVANCE_ON_MOUSE->IsChecked() || mpRB_ADVANCE_AUTO->IsChecked()))
    {
        if( mpRB_ADVANCE_ON_MOUSE->IsChecked())
            aResult.mePresChange = PRESCHANGE_MANUAL;
        else
        {
            aResult.mePresChange = PRESCHANGE_AUTO;
            if( mpMF_ADVANCE_AUTO_AFTER->IsEnabled())
            {
                aResult.mfTime = static_cast<double>(mpMF_ADVANCE_AUTO_AFTER->GetValue() ) / 100.0 ;
                aResult.mbTimeAmbiguous = false;
            }
        }
 
        aResult.mbPresChangeAmbiguous = false;
    }
 
    // sound
    if( mpLB_SOUND->IsEnabled())
    {
        maCurrentSoundFile.clear();
        if( mpLB_SOUND->GetSelectedEntryCount() > 0 )
        {
            sal_Int32 nPos = mpLB_SOUND->GetSelectedEntryPos();
            aResult.mbStopSound = nPos == 1;
            aResult.mbSoundOn = nPos > 1;
            if( aResult.mbStopSound )
            {
                aResult.maSound.clear();
                aResult.mbSoundAmbiguous = false;
            }
            else
            {
                aResult.maSound = lcl_getSoundFileURL( maSoundList, mpLB_SOUND );
                aResult.mbSoundAmbiguous = false;
                maCurrentSoundFile = aResult.maSound;
            }
        }
    }
 
    // sound loop
    if( mpCB_LOOP_SOUND->IsEnabled() )
    {
        aResult.mbLoopSound = mpCB_LOOP_SOUND->IsChecked();
        aResult.mbLoopSoundAmbiguous = false;
    }
 
    return aResult;
}
 
void SlideTransitionPane::applyToSelectedPages(bool bPreview = true)
{
    if( ! mbUpdatingControls )
    {
        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( mpCB_AUTO_PREVIEW->IsEnabled() &&
            mpCB_AUTO_PREVIEW->IsChecked() && 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, Button*, void)
{
    DBG_ASSERT( mpDrawDoc, "Invalid Draw Document!" );
    if( !mpDrawDoc )
        return;
 
    ::sd::slidesorter::SharedPageSelection pPages (
        new ::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, Button*, void)
{
    playCurrentEffect();
}
 
IMPL_LINK_NOARG(SlideTransitionPane, TransitionSelected, ValueSet *, void)
{
    updateVariants( mpVS_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();
    mpLB_VARIANT->Clear();
    mpVS_TRANSITION_ICONS->SetNoSelection();
 
    if( nPresetOffset >= rPresetList.size() )
    {
        mpLB_VARIANT->Enable( 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() )
                {
                    mpLB_VARIANT->InsertEntry( aIt->getVariantLabel() );
                    if( *pFound == aIt )
                        mpLB_VARIANT->SelectEntryPos( mpLB_VARIANT->GetEntryCount()-1 );
                }
            }
            nItem++;
        }
 
        if( mpLB_VARIANT->GetEntryCount() == 0 )
            mpLB_VARIANT->Enable( false );
        else
            mpLB_VARIANT->Enable();
 
        // item has the id of the first transition from this set.
        mpVS_TRANSITION_ICONS->SelectItem( nFirstItem );
    }
}
 
IMPL_LINK_NOARG(SlideTransitionPane, AdvanceSlideRadioButtonToggled, RadioButton&, void)
{
    updateControlState();
    applyToSelectedPages(false);
}
 
IMPL_LINK_NOARG(SlideTransitionPane, AdvanceTimeModified, Edit&, void)
{
    applyToSelectedPages(false);
}
 
IMPL_LINK_NOARG(SlideTransitionPane, VariantListBoxSelected, ListBox&, void)
{
    applyToSelectedPages();
}
 
IMPL_LINK_NOARG(SlideTransitionPane, DurationModifiedHdl, Edit&, void)
{
    double duration_value = static_cast<double>(mpCBX_duration->GetValue());
    if(duration_value <= 0.0)
        mpCBX_duration->SetValue(0);
    else
        mpCBX_duration->SetValue(duration_value);
}
 
IMPL_LINK_NOARG(SlideTransitionPane, DurationLoseFocusHdl, Control&, void)
{
    applyToSelectedPages();
}
 
IMPL_LINK_NOARG(SlideTransitionPane, SoundListBoxSelected, ListBox&, void)
{
    if( mpLB_SOUND->GetSelectedEntryCount() )
    {
        sal_Int32 nPos = mpLB_SOUND->GetSelectedEntryPos();
        if( nPos == 2 )
        {
            // other sound...
            openSoundFileDialog();
        }
    }
    updateControlState();
    applyToSelectedPages();
}
 
IMPL_LINK_NOARG(SlideTransitionPane, LoopSoundBoxChecked, Button*, void)
{
    applyToSelectedPages();
}
 
IMPL_LINK_NOARG(SlideTransitionPane, AutoPreviewClicked, Button*, void)
{
    SdOptions* pOptions = SD_MOD()->GetSdOptions(DocumentType::Impress);
    pOptions->SetPreviewTransitions( mpCB_AUTO_PREVIEW->IsChecked() );
}
 
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
                    aIcon = BitmapEx( "sd/cmd/transition-none.png" );
 
                mpVS_TRANSITION_ICONS->InsertItem(
                    nPresetOffset + 1, Image( aIcon ), sLabel,
                    VALUESET_APPEND, /* show legend */ true );
 
                m_aNumVariants[ pPreset->getSetId() ] = 1;
            }
            else
            {
                m_aNumVariants[ pPreset->getSetId() ]++;
            }
        }
        nPresetOffset++;
    }
    mpVS_TRANSITION_ICONS->RecalculateItemSizes();
 
    SAL_INFO( "sd.transitions", "Item transition offsets in ValueSet:");
    for( size_t i = 0; i < mpVS_TRANSITION_ICONS->GetItemCount(); ++i )
        SAL_INFO( "sd.transitions", i << ":" << mpVS_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();
}
 
vcl::Window * createSlideTransitionPanel( vcl::Window* pParent, ViewShellBase& rBase, const css::uno::Reference<css::frame::XFrame>& rxFrame )
{
    vcl::Window* pWindow = nullptr;
 
    DrawDocShell* pDocSh = rBase.GetDocShell();
    if( pDocSh )
    {
        pWindow = VclPtr<SlideTransitionPane>::Create( pParent, rBase, pDocSh->GetDoc(), rxFrame );
    }
 
    return pWindow;
}
 
} //  namespace sd
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V568 It's odd that the argument of sizeof() operator is the expression.