Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/LibreOffice/sd/source/ui/animations/   (Office von Apache Version 25.8.3.2©)  Datei vom 5.10.2025 mit Größe 43 kB image not shown  

Quelle  CustomAnimationList.cxx   Sprache: C

 
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */


#include <com/sun/star/document/XActionLockable.hpp>
#include <com/sun/star/drawing/XDrawPage.hpp>
#include <com/sun/star/drawing/XShapes.hpp>
#include <com/sun/star/presentation/ShapeAnimationSubType.hpp>
#include <com/sun/star/presentation/EffectNodeType.hpp>
#include <com/sun/star/presentation/ParagraphTarget.hpp>
#include <com/sun/star/container/XEnumerationAccess.hpp>
#include <com/sun/star/presentation/EffectPresetClass.hpp>
#include <com/sun/star/presentation/EffectCommands.hpp>
#include <com/sun/star/text/XTextRange.hpp>
#include <com/sun/star/beans/XPropertySet.hpp>
#include <comphelper/scopeguard.hxx>
#include <CustomAnimationList.hxx>
#include <CustomAnimationPreset.hxx>
#include <utility>
#include <vcl/commandevent.hxx>
#include <vcl/event.hxx>
#include <vcl/image.hxx>
#include <vcl/settings.hxx>
#include <vcl/svapp.hxx>
#include <vcl/weldutils.hxx>
#include <tools/debug.hxx>
#include <tools/gen.hxx>
#include <comphelper/diagnose_ex.hxx>

#include <sdresid.hxx>

#include <strings.hrc>
#include <bitmaps.hlst>

#include <algorithm>
#include <memory>

using namespace ::com::sun::star;
using namespace ::com::sun::star::presentation;

using ::com::sun::star::uno::UNO_QUERY;
using ::com::sun::star::uno::UNO_QUERY_THROW;
using ::com::sun::star::uno::Any;
using ::com::sun::star::uno::Reference;
using ::com::sun::star::uno::Exception;
using ::com::sun::star::uno::XInterface;
using ::com::sun::star::text::XTextRange;
using ::com::sun::star::drawing::XShape;
using ::com::sun::star::drawing::XShapes;
using ::com::sun::star::drawing::XDrawPage;
using ::com::sun::star::container::XChild;
using ::com::sun::star::container::XIndexAccess;
using ::com::sun::star::container::XEnumerationAccess;
using ::com::sun::star::container::XEnumeration;
using ::com::sun::star::beans::XPropertySet;
using ::com::sun::star::beans::XPropertySetInfo;

namespace sd {

// go recursively through all shapes in the given XShapes collection and return true as soon as the
// given shape is found. nIndex is incremented for each shape with the same shape type as the given
// shape is found until the given shape is found.
static bool getShapeIndex(  const Reference< XShapes >& xShapes, const Reference< XShape >&&nbsp;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&&nbsp;rRect, bool bSelected);
    void PaintTrigger(vcl::RenderContext& rRenderContext, const ::tools::Rectangle&&nbsp;rRect);

private:
    OUString        msDescription;
    OUString        msEffectName;
    CustomAnimationEffectPtr mpEffect;

public:
    static const ::tools::Long nIconWidth = 19;
    static const ::tools::Long nItemMinHeight = 38;
};

CustomAnimationListEntryItem::CustomAnimationListEntryItem(OUString aDescription, CustomAnimationEffectPtr pEffect)
    : msDescription(std::move(aDescription))
    , mpEffect(std::move(pEffect))
{
    if (!mpEffect)
        return;
    switch (mpEffect->getPresetClass())
    {
    case EffectPresetClass::ENTRANCE:
        msEffectName = SdResId(STR_CUSTOMANIMATION_ENTRANCE); break;
    case EffectPresetClass::EXIT:
        msEffectName = SdResId(STR_CUSTOMANIMATION_EXIT); break;
    case EffectPresetClass::EMPHASIS:
        msEffectName = SdResId(STR_CUSTOMANIMATION_EMPHASIS); break;
    case EffectPresetClass::MOTIONPATH:
        msEffectName = SdResId(STR_CUSTOMANIMATION_MOTION_PATHS); break;
    default:
        msEffectName = SdResId(STR_CUSTOMANIMATION_MISC); break;
    }
    msEffectName = msEffectName.replaceFirst( "%1" , CustomAnimationPresets::getCustomAnimationPresets().getUINameForPresetId(mpEffect->getPresetId()));
}

IMPL_STATIC_LINK(CustomAnimationList, CustomRenderHdl, weld::TreeView::render_args, aPayload, void)
{
    vcl::RenderContext& rRenderContext = std::get<0>(aPayload);
    const ::tools::Rectangle& rRect = std::get<1>(aPayload);
    bool bSelected = std::get<2>(aPayload);
    const OUString& rId = std::get<3>(aPayload);

    CustomAnimationListEntryItem* pItem = weld::fromId<CustomAnimationListEntryItem*>(rId);

    pItem->Paint(rRenderContext, rRect, bSelected);
}

IMPL_STATIC_LINK(CustomAnimationList, CustomGetSizeHdl, weld::TreeView::get_size_args, aPayload, Size)
{
    vcl::RenderContext& rRenderContext = aPayload.first;
    const OUString& rId = aPayload.second;

    CustomAnimationListEntryItem* pItem = weld::fromId<CustomAnimationListEntryItem*>(rId);
    if (!pItem)
        return Size(CustomAnimationListEntryItem::nIconWidth, CustomAnimationListEntryItem::nItemMinHeight);
    return pItem->GetSize(rRenderContext);
}

Size CustomAnimationListEntryItem::GetSize(const vcl::RenderContext& rRenderContext)
{
    auto width = rRenderContext.GetTextWidth( msDescription ) + nIconWidth;
    if (width < (rRenderContext.GetTextWidth( msEffectName ) + 2*nIconWidth))
        width = rRenderContext.GetTextWidth( msEffectName ) + 2*nIconWidth;

    Size aSize(width, rRenderContext.GetTextHeight() * 2);
    if (aSize.Height() < nItemMinHeight)
        aSize.setHeight(nItemMinHeight);
    return aSize;
}

void CustomAnimationListEntryItem::PaintTrigger(vcl::RenderContext& rRenderContext, const ::tools::Rectangle& rRect)
{
    Size aSize(rRect.GetSize());

    ::tools::Rectangle aOutRect(rRect);

    rRenderContext.Push();
    rRenderContext.SetFillColor(rRenderContext.GetSettings().GetStyleSettings().GetDialogColor());
    rRenderContext.SetLineColor();
    // fill the background with the dialog bg color
    rRenderContext.DrawRect(aOutRect);

    // Erase the four corner pixels to make the rectangle appear rounded.
    rRenderContext.SetLineColor(rRenderContext.GetSettings().GetStyleSettings().GetWindowColor());
    rRenderContext.DrawPixel(aOutRect.TopLeft());
    rRenderContext.DrawPixel(Point(aOutRect.Right(), aOutRect.Top()));
    rRenderContext.DrawPixel(Point(aOutRect.Left(), aOutRect.Bottom()));
    rRenderContext.DrawPixel(Point(aOutRect.Right(), aOutRect.Bottom()));

    // draw the category title

    int nVertBorder = ((aSize.Height() - rRenderContext.GetTextHeight()) >> 1);
    int nHorzBorder = rRenderContext.LogicToPixel(Size(3, 3), MapMode(MapUnit::MapAppFont)).Width();

    aOutRect.AdjustLeft(nHorzBorder );
    aOutRect.AdjustRight( -nHorzBorder );
    aOutRect.AdjustTop( nVertBorder );
    aOutRect.AdjustBottom( -nVertBorder );

    // Draw the text with the dialog text color
    rRenderContext.SetTextColor(rRenderContext.GetSettings().GetStyleSettings().GetDialogTextColor());
    rRenderContext.DrawText(aOutRect, rRenderContext.GetEllipsisString(msDescription, aOutRect.GetWidth()));
    rRenderContext.Pop();
}

void CustomAnimationListEntryItem::PaintEffect(vcl::RenderContext& rRenderContext, const ::tools::Rectangle& rRect, bool bSelected)
{
    rRenderContext.Push(vcl::PushFlags::TEXTCOLOR);
    const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings();
    if (bSelected)
        rRenderContext.SetTextColor(rStyleSettings.GetHighlightTextColor());
    else
        rRenderContext.SetTextColor(rStyleSettings.GetDialogTextColor());

    Point aPos(rRect.TopLeft());
    int nItemHeight = rRect.GetHeight();

    Size nImageSize = Image(StockImage::Yes, BMP_CUSTOMANIMATION_AFTER_PREVIOUS).GetSizePixel();

    sal_Int16 nNodeType = mpEffect->getNodeType();
    if (nNodeType == EffectNodeType::ON_CLICK)
    {
        Image aImage(Image(StockImage::Yes, BMP_CUSTOMANIMATION_ON_CLICK));
        nImageSize = aImage.GetSizePixel();
        aPos.AdjustY(nItemHeight / 4 - nImageSize.Height() / 2);
        rRenderContext.DrawImage(aPos, aImage);
    }
    else if (nNodeType == EffectNodeType::AFTER_PREVIOUS)
    {
        Image aImage(Image(StockImage::Yes, BMP_CUSTOMANIMATION_AFTER_PREVIOUS));
        nImageSize = aImage.GetSizePixel();
        aPos.AdjustY(nItemHeight / 4 - nImageSize.Height() / 2);
        rRenderContext.DrawImage(aPos, aImage);
    }
    else if (nNodeType == EffectNodeType::WITH_PREVIOUS)
    {
        //FIXME With previous image not defined in CustomAnimation.src
    }

    aPos = rRect.TopLeft();
    aPos.AdjustX(nImageSize.Width() + 5);

    //TODO, full width of widget ?
    rRenderContext.DrawText(aPos, rRenderContext.GetEllipsisString(msDescription, rRect.GetWidth()));

    OUString sImage;
    switch (mpEffect->getPresetClass())
    {
    case EffectPresetClass::ENTRANCE:
        sImage = BMP_CUSTOMANIMATION_ENTRANCE_EFFECT; break;
    case EffectPresetClass::EXIT:
        sImage = BMP_CUSTOMANIMATION_EXIT_EFFECT; break;
    case EffectPresetClass::EMPHASIS:
        sImage = BMP_CUSTOMANIMATION_EMPHASIS_EFFECT; break;
    case EffectPresetClass::MOTIONPATH:
        sImage = BMP_CUSTOMANIMATION_MOTION_PATH; break;
    case EffectPresetClass::OLEACTION:
        sImage = BMP_CUSTOMANIMATION_OLE; break;
    case EffectPresetClass::MEDIACALL:
        switch (mpEffect->getCommand())
        {
        case EffectCommands::TOGGLEPAUSE:
            sImage = BMP_CUSTOMANIMATION_MEDIA_PAUSE; break;
        case EffectCommands::STOP:
            sImage = BMP_CUSTOMANIMATION_MEDIA_STOP; break;
        case EffectCommands::PLAY:
        default:
            sImage = BMP_CUSTOMANIMATION_MEDIA_PLAY; break;
        }
        break;
    default:
        break;
    }

    if (!sImage.isEmpty())
    {
        Image aImage(StockImage::Yes, sImage);
        nImageSize = aImage.GetSizePixel();
        Point aImagePos(aPos);
        aImagePos.AdjustY(nItemHeight * 3 / 4 - nImageSize.Height() / 2);
        rRenderContext.DrawImage(aImagePos, aImage);
    }
    else
    {
        Image aImage(StockImage::Yes, BMP_CUSTOMANIMATION_ENTRANCE_EFFECT);
        nImageSize = aImage.GetSizePixel();
    }

    aPos.AdjustX(nImageSize.Width() + 5);
    aPos.AdjustY(nItemHeight / 2);

    rRenderContext.DrawText(aPos, rRenderContext.GetEllipsisString(msEffectName, rRect.GetWidth()));
    rRenderContext.Pop();
}

void CustomAnimationListEntryItem::Paint(vcl::RenderContext& rRenderContext, const ::tools::Rectangle& rRect, bool bSelected)
{
    if (mpEffect)
        PaintEffect(rRenderContext, rRect, bSelected);
    else
        PaintTrigger(rRenderContext, rRect);
}

CustomAnimationList::CustomAnimationList(std::unique_ptr<weld::TreeView> xTreeView,
                                         std::unique_ptr<weld::Label> xLabel,
                                         std::unique_ptr<weld::Widget> xScrolledWindow)
    : mxTreeView(std::move(xTreeView))
    , maDropTargetHelper(*this)
    , mxEmptyLabel(std::move(xLabel))
    , mxEmptyLabelParent(std::move(xScrolledWindow))
    , mbIgnorePaint(false)
    , mpController(nullptr)
    , mnLastGroupId(0)
    , mnPostExpandEvent(nullptr)
    , mnPostCollapseEvent(nullptr)
{
    mxEmptyLabel->set_stack_background();

    mxTreeView->set_selection_mode(SelectionMode::Multiple);
    mxTreeView->connect_selection_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&&nbsp;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;
        forconst auto &pEntry : mDndEffectsSelected )
        {
            CustomAnimationListEntryItem* pCustomAnimationEffect = weld::fromId<CustomAnimationListEntryItem*>(mxTreeView->get_id(*pEntry));
            aEffects.push_back(pCustomAnimationEffect->getEffect());
        }

        // Callback to observer to have it update the model.
        // If pTarget is null, pass nullptr to indicate end of list.
        mpController->onDragNDropComplete(
            std::move(aEffects),
            pTarget ? pTarget->getEffect() : nullptr );

        // Reset selection
        mxTreeView->select(*mxDndEffectDragging);
        Select();
    }

    // NOTE: Don't call default handler because all required
    //       move operations have been completed here to update the model.
    return DND_ACTION_NONE;
}

CustomAnimationList::~CustomAnimationList()
{
    if (mnPostExpandEvent)
    {
        Application::RemoveUserEvent(mnPostExpandEvent);
        mnPostExpandEvent = nullptr;
    }

    if (mnPostCollapseEvent)
    {
        Application::RemoveUserEvent(mnPostCollapseEvent);
        mnPostCollapseEvent = nullptr;
    }

    if( mpMainSequence )
        mpMainSequence->removeListener( this );

    clear();
}

IMPL_LINK(CustomAnimationList, KeyInputHdl, const KeyEvent&, rKEvt, bool)
{
    const int nKeyCode = rKEvt.GetKeyCode().GetCode();
    switch (nKeyCode)
    {
        case KEY_DELETE:
            mpController->onContextMenu(u"remove"_ustr);
            return true;
        case KEY_INSERT:
            mpController->onContextMenu(u"create"_ustr);
            return true;
        case KEY_SPACE:
        {
            std::unique_ptr<weld::TreeIter> xEntry = mxTreeView->make_iterator();
            if (mxTreeView->get_cursor(xEntry.get()))
            {
                auto aRect = mxTreeView->get_row_area(*xEntry);
                const Point aPos(aRect.getOpenWidth() / 2, aRect.getOpenHeight() / 2);
                const CommandEvent aCEvt(aPos, CommandEventId::ContextMenu);
                CommandHdl(aCEvt);
                return true;
            }
        }
    }
    return false;
}

/** selects or deselects the given effect.
    Selections of other effects are not changed */

void CustomAnimationList::select( const CustomAnimationEffectPtr& pEffect )
{
    CustomAnimationListEntryItem* pEntry = nullptr;

    std::unique_ptr<weld::TreeIter> xEntry = mxTreeView->make_iterator();
    if (mxTreeView->get_iter_first(*xEntry))
    {
        do
        {
            CustomAnimationListEntryItem* pTestEntry = weld::fromId<CustomAnimationListEntryItem*>(mxTreeView->get_id(*xEntry));
            if (pTestEntry->getEffect() == pEffect)
            {
                mxTreeView->select(*xEntry);
                mxTreeView->scroll_to_row(*xEntry);
                pEntry = pTestEntry;
                break;
            }
        } while (mxTreeView->iter_next(*xEntry));
    }

    if( !pEntry )
    {
        append( pEffect );
        select( pEffect );
    }
}

void CustomAnimationList::clear()
{
    mxEntries.clear();
    mxTreeView->clear();

    mxEmptyLabelParent->show();
    mxTreeView->hide();

    mxLastParentEntry.reset();
    mxLastTargetShape = nullptr;
}

void CustomAnimationList::update( const MainSequencePtr& pMainSequence )
{
    if( mpMainSequence )
        mpMainSequence->removeListener( this );

    mpMainSequence = pMainSequence;
    update();

    if( mpMainSequence )
        mpMainSequence->addListener( this );
}

struct stl_append_effect_func
{
    explicit stl_append_effect_func( CustomAnimationList& rList ) : mrList( rList ) {}
    void operator()(const CustomAnimationEffectPtr& pEffect);
    CustomAnimationList& mrList;
};

void stl_append_effect_func::operator()(const CustomAnimationEffectPtr& pEffect)
{
    mrList.append( pEffect );
}

void CustomAnimationList::update()
{
    mbIgnorePaint = true;

    std::vector< CustomAnimationEffectPtr > aVisible;
    std::vector< CustomAnimationEffectPtr > aSelected;
    CustomAnimationEffectPtr aCurrent;

    CustomAnimationEffectPtr pFirstSelEffect;
    CustomAnimationEffectPtr pLastSelEffect;
    ::tools::Long nFirstVis = -1;
    ::tools::Long nLastVis = -1;
    ::tools::Long nFirstSelOld = -1;
    ::tools::Long nLastSelOld = -1;

    std::unique_ptr<weld::TreeIter> xEntry = mxTreeView->make_iterator();

    if( mpMainSequence )
    {
        std::unique_ptr<weld::TreeIter> xLastSelectedEntry;
        std::unique_ptr<weld::TreeIter> xLastVisibleEntry;

        // save selection, current, and expand (visible) states
        mxTreeView->all_foreach([this, &aVisible, &nFirstVis, &xLastVisibleEntry,
                                 &aSelected, &nFirstSelOld, &pFirstSelEffect, &xLastSelectedEntry](weld::TreeIter& rEntry){
            CustomAnimationListEntryItem* pEntry = weld::fromId<CustomAnimationListEntryItem*>(mxTreeView->get_id(rEntry));
            CustomAnimationEffectPtr pEffect(pEntry->getEffect());
            if (pEffect)
            {
                if (weld::IsEntryVisible(*mxTreeView, rEntry))
                {
                    aVisible.push_back(pEffect);
                    // save scroll position
                    if (nFirstVis == -1)
                        nFirstVis = weld::GetAbsPos(*mxTreeView, rEntry);
                    if (!xLastVisibleEntry)
                        xLastVisibleEntry = mxTreeView->make_iterator(&rEntry);
                    else
                        mxTreeView->copy_iterator(rEntry, *xLastVisibleEntry);
                }

                if (mxTreeView->is_selected(rEntry))
                {
                    aSelected.push_back(pEffect);
                    if (nFirstSelOld == -1)
                    {
                        pFirstSelEffect = std::move(pEffect);
                        nFirstSelOld = weld::GetAbsPos(*mxTreeView, rEntry);
                    }
                    if (!xLastSelectedEntry)
                        xLastSelectedEntry = mxTreeView->make_iterator(&rEntry);
                    else
                        mxTreeView->copy_iterator(rEntry, *xLastSelectedEntry);
                }
            }

            return false;
        });

        if (xLastSelectedEntry)
        {
            CustomAnimationListEntryItem* pEntry = weld::fromId<CustomAnimationListEntryItem*>(mxTreeView->get_id(*xLastSelectedEntry));
            pLastSelEffect = pEntry->getEffect();
            nLastSelOld = weld::GetAbsPos(*mxTreeView, *xLastSelectedEntry);
        }

        if (xLastVisibleEntry)
            nLastVis = weld::GetAbsPos(*mxTreeView, *xLastVisibleEntry);

        if (mxTreeView->get_cursor(xEntry.get()))
        {
            CustomAnimationListEntryItem* pEntry = weld::fromId<CustomAnimationListEntryItem*>(mxTreeView->get_id(*xEntry));
            aCurrent = pEntry->getEffect();
        }
    }

    // rebuild list

    mxTreeView->freeze();

    clear();

    if (mpMainSequence)
    {
        std::for_each( mpMainSequence->getBegin(), mpMainSequence->getEnd(), stl_append_effect_func( *this ) );
        mxLastParentEntry.reset();

        auto rInteractiveSequenceVector = mpMainSequence->getInteractiveSequenceVector();

        for (InteractiveSequencePtr const& pIS : rInteractiveSequenceVector)
        {
            Reference< XShape > xShape( pIS->getTriggerShape() );
            if( xShape.is() )
            {
                OUString aDescription = SdResId(STR_CUSTOMANIMATION_TRIGGER) + ": " +
                    getShapeDescription( xShape, false );

                mxEntries.emplace_back(std::make_unique<CustomAnimationListEntryItem>(aDescription, nullptr));

                OUString sId(weld::toId(mxEntries.back().get()));
                mxTreeView->insert(nullptr, -1, &aDescription, &sId, nullptr, nullptr, false, nullptr);
                std::for_each( pIS->getBegin(), pIS->getEnd(), stl_append_effect_func( *this ) );
                mxLastParentEntry.reset();
            }
        }
    }

    mxTreeView->thaw();

    if (mxTreeView->n_children())
    {
        mxEmptyLabelParent->hide();
        mxTreeView->show();
    }

    if (mpMainSequence)
    {
        ::tools::Long nFirstSelNew = -1;
        ::tools::Long nLastSelNew = -1;

        std::vector<std::unique_ptr<weld::TreeIter>> aNewSelection;

        // restore selection state, expand state, and current-entry (under cursor)
        if (mxTreeView->get_iter_first(*xEntry))
        {
            do
            {
                CustomAnimationListEntryItem* pEntry = weld::fromId<CustomAnimationListEntryItem*>(mxTreeView->get_id(*xEntry));

                CustomAnimationEffectPtr pEffect( pEntry->getEffect() );
                if (pEffect)
                {
                    // Any effects that were visible should still be visible, so expand their parents.
                    // (a previously expanded parent may have moved leaving a child to now be the new parent to expand)
                    if( std::find( aVisible.begin(), aVisible.end(), pEffect ) != aVisible.end() )
                    {
                        if (mxTreeView->get_iter_depth(*xEntry))
                        {
                            std::unique_ptr<weld::TreeIter> xParentEntry = mxTreeView->make_iterator(xEntry.get());
                            mxTreeView->iter_parent(*xParentEntry);
                            mxTreeView->expand_row(*xParentEntry);
                        }
                    }

                    if( std::find( aSelected.begin(), aSelected.end(), pEffect ) != aSelected.end() )
                        aNewSelection.emplace_back(mxTreeView->make_iterator(xEntry.get()));

                    // Restore the cursor, as it may deselect other effects wait until
                    // after the loop to reset the selection
                    if( pEffect == aCurrent )
                        mxTreeView->set_cursor(*xEntry);

                    if (pEffect == pFirstSelEffect)
                        nFirstSelNew = weld::GetAbsPos(*mxTreeView, *xEntry);

                    if (pEffect == pLastSelEffect)
                        nLastSelNew = weld::GetAbsPos(*mxTreeView, *xEntry);
                }
            } while (mxTreeView->iter_next(*xEntry));
        }

        // tdf#147032 unselect what previous set_cursor may have caused to get selected as a side-effect
        mxTreeView->unselect_all();
        for (const auto& rEntry : aNewSelection)
            mxTreeView->select(*rEntry);

        // Scroll to a selected entry, depending on where the selection moved.
        const bool bMoved = nFirstSelNew != nFirstSelOld;
        const bool bMovedUp = nFirstSelNew < nFirstSelOld;
        const bool bMovedDown = nFirstSelNew > nFirstSelOld;

        if( bMoved && nLastSelOld < nFirstVis && nLastSelNew < nFirstVis )
        {
            // The selection is above the visible area.
            // Scroll up to show the last few selected entries.
            if( nLastSelNew - (nLastVis - nFirstVis) > nFirstSelNew)
            {
                // The entries in the selection range can't fit in view.
                // Scroll so the last selected entry is last in view.
                mxTreeView->vadjustment_set_value(nLastSelNew - (nLastVis - nFirstVis));
            }
            else
                mxTreeView->vadjustment_set_value(nFirstSelNew);
        }
        else if( bMoved && nFirstSelOld > nLastVis && nFirstSelNew > nLastVis )
        {
            // The selection is below the visible area.
            // Scroll down to the first few selected entries.
            mxTreeView->vadjustment_set_value(nFirstSelNew);
        }
        else if( bMovedUp && nFirstSelOld <= nFirstVis )
        {
            // A visible entry has moved up out of view; scroll up one.
            mxTreeView->vadjustment_set_value(nFirstVis - 1);
        }
        else if( bMovedDown && nLastSelOld >= nLastVis )
        {
            // An entry has moved down out of view; scroll down one.
            mxTreeView->vadjustment_set_value(nFirstVis + 1);
        }
        else if ( nFirstVis != -1 )
        {
            // The selection is still in view, or it hasn't moved.
            mxTreeView->vadjustment_set_value(nFirstVis);
        }
    }

    mbIgnorePaint = false;

    Select();
}

void CustomAnimationList::append( CustomAnimationEffectPtr pEffect )
{
    Any aTarget( pEffect->getTarget() );
    if( !aTarget.hasValue() )
        return;

    try
    {
        // create a ui description
        OUString aDescription = getDescription(aTarget, pEffect->getTargetSubItem() != ShapeAnimationSubType::ONLY_BACKGROUND);

        std::unique_ptr<weld::TreeIter> xParentEntry;

        Reference< XShape > xTargetShape( pEffect->getTargetShape() );
        sal_Int32 nGroupId = pEffect->getGroupId();

        // if this effect has the same target and group-id as the last root effect,
        // the last root effect is also this effects parent
        if (mxLastParentEntry && nGroupId != -1 && mxLastTargetShape == xTargetShape && mnLastGroupId == nGroupId)
            xParentEntry = mxTreeView->make_iterator(mxLastParentEntry.get());

        // create an entry for the effect
        std::unique_ptr<weld::TreeIter> xEntry = mxTreeView->make_iterator();

        mxEntries.emplace_back(std::make_unique<CustomAnimationListEntryItem>(aDescription, pEffect));

        OUString sId(weld::toId(mxEntries.back().get()));

        if (xParentEntry)
        {
            // add a subentry
            mxTreeView->insert(xParentEntry.get(), -1, &aDescription, &sId, nullptr, nullptr, false, xEntry.get());
        }
        else
        {
            // add a root entry
            mxTreeView->insert(nullptr, -1, &aDescription, &sId, nullptr, nullptr, false, xEntry.get());

            // and the new root entry becomes the possible next group header
            mxLastTargetShape = std::move(xTargetShape);
            mnLastGroupId = nGroupId;
            mxLastParentEntry = std::move(xEntry);
        }
    }
    catch (const Exception&)
    {
        TOOLS_WARN_EXCEPTION( "sd""sd::CustomAnimationList::append()" );
    }
}

static void selectShape(weld::TreeView* pTreeList, const Reference< XShape >& xShape )
{
    std::unique_ptr<weld::TreeIter> xEntry = pTreeList->make_iterator();
    if (!pTreeList->get_iter_first(*xEntry))
        return;

    bool bFirstEntry = true;

    do
    {
        CustomAnimationListEntryItem* pEntry = weld::fromId<CustomAnimationListEntryItem*>(pTreeList->get_id(*xEntry));
        CustomAnimationEffectPtr pEffect(pEntry->getEffect());
        if (pEffect)
        {
            if (pEffect->getTarget() == xShape)
            {
                pTreeList->select(*xEntry);
                if (bFirstEntry)
                {
                    pTreeList->scroll_to_row(*xEntry);
                    bFirstEntry = false;
                }
            }
        }
    } while (pTreeList->iter_next(*xEntry));
}

void CustomAnimationList::onSelectionChanged(const Any& rSelection)
{
    try
    {
        mxTreeView->unselect_all();

        if (rSelection.hasValue())
        {
            Reference< XIndexAccess > xShapes(rSelection, UNO_QUERY);
            if( xShapes.is() )
            {
                sal_Int32 nCount = xShapes->getCount();
                sal_Int32 nIndex;
                for( nIndex = 0; nIndex < nCount; nIndex++ )
                {
                    Reference< XShape > xShape( xShapes->getByIndex( nIndex ), UNO_QUERY );
                    if( xShape.is() )
                        selectShape(mxTreeView.get(), xShape);
                }
            }
            else
            {
                Reference< XShape > xShape(rSelection, UNO_QUERY);
                if( xShape.is() )
                    selectShape(mxTreeView.get(), xShape);
            }
        }

        Select();
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd""sd::CustomAnimationList::onSelectionChanged()" );
    }
}

IMPL_LINK_NOARG(CustomAnimationList, SelectHdl, weld::TreeView&, void)
{
    Select();
}

// Notify controller to refresh UI when we are notified of selection change from base class
void CustomAnimationList::Select()
{
    if( mbIgnorePaint )
        return;
    mpController->onSelect();
}

IMPL_LINK_NOARG(CustomAnimationList, PostExpandHdl, void*, void)
{
    std::unique_ptr<weld::TreeIter> xEntry = mxTreeView->make_iterator();
    if (mxTreeView->get_selected(xEntry.get()))
    {
        for (bool bChild = mxTreeView->iter_children(*xEntry); bChild; bChild = mxTreeView->iter_next_sibling(*xEntry))
        {
            if (!mxTreeView->is_selected(*xEntry))
                mxTreeView->select(*xEntry);
        }
    }

    // Notify controller that selection has changed (it should update the UI)
    mpController->onSelect();

    mnPostExpandEvent = nullptr;
}

IMPL_LINK(CustomAnimationList, ExpandHdl, const weld::TreeIter&, rParent, bool)
{
    // If expanded entry is selected, then select its children too afterwards.
    if (mxTreeView->is_selected(rParent) && !mnPostExpandEvent) {
        mnPostExpandEvent = Application::PostUserEvent(LINK(this, CustomAnimationList, PostExpandHdl));
    }

    return true;
}

IMPL_LINK_NOARG(CustomAnimationList, PostCollapseHdl, void*, void)
{
    // Deselect all entries as SvTreeListBox::Collapse selects the last
    // entry to have focus (or its parent), which is not desired
    mxTreeView->unselect_all();

    // Restore selection state for entries which are still visible
    for (const auto &pEntry : lastSelectedEntries)
    {
        if (weld::IsEntryVisible(*mxTreeView, *pEntry))
            mxTreeView->select(*pEntry);
    }

    lastSelectedEntries.clear();

    // Notify controller that selection has changed (it should update the UI)
    mpController->onSelect();

    mnPostCollapseEvent = nullptr;
}

IMPL_LINK_NOARG(CustomAnimationList, CollapseHdl, const weld::TreeIter&, bool)
{
    if (!mnPostCollapseEvent)
    {
        // weld::TreeView::collapse() discards multi-selection state
        // of list entries, so first save current selection state
        mxTreeView->selected_foreach([this](weld::TreeIter& rEntry){
            lastSelectedEntries.emplace_back(mxTreeView->make_iterator(&rEntry));
            return false;
        });

        mnPostCollapseEvent = Application::PostUserEvent(LINK(this, CustomAnimationList, PostCollapseHdl));
    }

    // Execute collapse on base class
    return true;
}

bool CustomAnimationList::isExpanded( const CustomAnimationEffectPtr& pEffect ) const
{
    bool bExpanded = true// we assume expanded by default

    std::unique_ptr<weld::TreeIter> xEntry = mxTreeView->make_iterator();
    if (mxTreeView->get_iter_first(*xEntry))
    {
        do
        {
            CustomAnimationListEntryItem* pEntry =
                weld::fromId<CustomAnimationListEntryItem*>(mxTreeView->get_id(*xEntry));
            if (pEntry->getEffect() == pEffect)
            {
                if (mxTreeView->get_iter_depth(*xEntry)) // no parent, keep expanded default of true
                {
                    std::unique_ptr<weld::TreeIter> xParentEntry = mxTreeView->make_iterator(xEntry.get());
                    if (mxTreeView->iter_parent(*xParentEntry))
                        bExpanded = mxTreeView->get_row_expanded(*xParentEntry);
                }
                break;
            }
        } while (mxTreeView->iter_next(*xEntry));
    }

    return bExpanded;
}

bool CustomAnimationList::isVisible(const CustomAnimationEffectPtr& pEffect) const
{
    std::unique_ptr<weld::TreeIter> xEntry = mxTreeView->make_iterator();
    if (mxTreeView->get_iter_first(*xEntry))
    {
        do
        {
            CustomAnimationListEntryItem* pTestEntry = weld::fromId<CustomAnimationListEntryItem*>(mxTreeView->get_id(*xEntry));
            if (pTestEntry->getEffect() == pEffect)
                return weld::IsEntryVisible(*mxTreeView, *xEntry);
        } while (mxTreeView->iter_next(*xEntry));
    }
    return true;
}

EffectSequence CustomAnimationList::getSelection() const
{
    EffectSequence aSelection;

    mxTreeView->selected_foreach([this, &aSelection](weld::TreeIter& rEntry){
        CustomAnimationListEntryItem* pEntry = weld::fromId<CustomAnimationListEntryItem*>(mxTreeView->get_id(rEntry));
        CustomAnimationEffectPtr pEffect(pEntry->getEffect());
        if (pEffect)
            aSelection.push_back(pEffect);

        // if the selected effect is not expanded and has children
        // we say that the children are automatically selected
        if (!mxTreeView->get_row_expanded(rEntry) && mxTreeView->iter_has_child(rEntry))
        {
            std::unique_ptr<weld::TreeIter> xChild = mxTreeView->make_iterator(&rEntry);
            (void)mxTreeView->iter_children(*xChild);

            do
            {
                if (!mxTreeView->is_selected(*xChild))
                {
                    CustomAnimationListEntryItem* pChild = weld::fromId<CustomAnimationListEntryItem*>(mxTreeView->get_id(*xChild));
                    const CustomAnimationEffectPtr& pChildEffect( pChild->getEffect() );
                    if( pChildEffect )
                        aSelection.push_back( pChildEffect );
                }
            } while (mxTreeView->iter_next_sibling(*xChild));
        }

        return false;
    });

    return aSelection;
}

IMPL_LINK_NOARG(CustomAnimationList, DoubleClickHdl, weld::TreeView&, bool)
{
    mpController->onDoubleClick();
    return false;
}

IMPL_LINK(CustomAnimationList, CommandHdl, const CommandEvent&, rCEvt, bool)
{
    if (rCEvt.GetCommand() != CommandEventId::ContextMenu)
        return false;

    if (rCEvt.IsMouseEvent())
    {
        ::Point aPos = rCEvt.GetMousePosPixel();
        std::unique_ptr<weld::TreeIter> xIter(mxTreeView->make_iterator());
        if (mxTreeView->get_dest_row_at_pos(aPos, xIter.get(), false) && !mxTreeView->is_selected(*xIter))
        {
            mxTreeView->unselect_all();
            mxTreeView->set_cursor(*xIter);
            mxTreeView->select(*xIter);
            SelectHdl(*mxTreeView);
        }
    }

    if (!mxTreeView->get_selected(nullptr))
        return false;

    std::unique_ptr<weld::Builder> xBuilder(Application::CreateBuilder(mxTreeView.get(), u"modules/simpress/ui/effectmenu.ui"_ustr));
    std::unique_ptr<weld::Menu> xMenu = xBuilder->weld_menu(u"menu"_ustr);

    sal_Int16 nNodeType = -1;
    sal_Int16 nEntries = 0;

    mxTreeView->selected_foreach([this, &nNodeType, &nEntries](weld::TreeIter& rEntry){
        CustomAnimationListEntryItem* pEntry = weld::fromId<CustomAnimationListEntryItem*>(mxTreeView->get_id(rEntry));
        CustomAnimationEffectPtr pEffect(pEntry->getEffect());

        nEntries++;
        if (pEffect)
        {
            if( nNodeType == -1 )
            {
                nNodeType = pEffect->getNodeType();
            }
            else
            {
                if( nNodeType != pEffect->getNodeType() )
                {
                    nNodeType = -1;
                    return true;
                }
            }
        }

        return false;
    });

    xMenu->set_active(u"onclick"_ustr, nNodeType == EffectNodeType::ON_CLICK);
    xMenu->set_active(u"withprev"_ustr, nNodeType == EffectNodeType::WITH_PREVIOUS);
    xMenu->set_active(u"afterprev"_ustr, nNodeType == EffectNodeType::AFTER_PREVIOUS);
    xMenu->set_sensitive(u"options"_ustr, nEntries == 1);
    xMenu->set_sensitive(u"timing"_ustr, nEntries == 1);

    OUString sCommand = xMenu->popup_at_rect(mxTreeView.get(), ::tools::Rectangle(rCEvt.GetMousePosPixel(), Size(1,1)));
    if (!sCommand.isEmpty())
        ExecuteContextMenuAction(sCommand);

    return true;
}

void CustomAnimationList::ExecuteContextMenuAction(const OUString& rIdent)
{
    mpController->onContextMenu(rIdent);
}

void CustomAnimationList::notify_change()
{
    update();
    mpController->onSelect();
}

}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

Messung V0.5
C=94 H=96 G=94

¤ Dauer der Verarbeitung: 0.40 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.