1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <com/sun/star/document/XActionLockable.hpp>
21 #include <com/sun/star/drawing/XDrawPage.hpp>
22 #include <com/sun/star/drawing/XShapes.hpp>
23 #include <com/sun/star/presentation/ShapeAnimationSubType.hpp>
24 #include <com/sun/star/presentation/EffectNodeType.hpp>
25 #include <com/sun/star/presentation/ParagraphTarget.hpp>
26 #include <com/sun/star/container/XEnumerationAccess.hpp>
27 #include <com/sun/star/presentation/EffectPresetClass.hpp>
28 #include <com/sun/star/presentation/EffectCommands.hpp>
29 #include <com/sun/star/text/XTextRange.hpp>
30 #include <com/sun/star/beans/XPropertySet.hpp>
31 #include <comphelper/scopeguard.hxx>
32 #include <CustomAnimationList.hxx>
33 #include <CustomAnimationPreset.hxx>
35 #include <vcl/commandevent.hxx>
36 #include <vcl/event.hxx>
37 #include <vcl/image.hxx>
38 #include <vcl/settings.hxx>
39 #include <vcl/svapp.hxx>
40 #include <vcl/weldutils.hxx>
41 #include <tools/debug.hxx>
42 #include <tools/gen.hxx>
43 #include <comphelper/diagnose_ex.hxx>
45 #include <sdresid.hxx>
47 #include <strings.hrc>
48 #include <bitmaps.hlst>
53 using namespace ::com::sun::star
;
54 using namespace ::com::sun::star::presentation
;
56 using ::com::sun::star::uno::UNO_QUERY
;
57 using ::com::sun::star::uno::UNO_QUERY_THROW
;
58 using ::com::sun::star::uno::Any
;
59 using ::com::sun::star::uno::Reference
;
60 using ::com::sun::star::uno::Exception
;
61 using ::com::sun::star::uno::XInterface
;
62 using ::com::sun::star::text::XTextRange
;
63 using ::com::sun::star::drawing::XShape
;
64 using ::com::sun::star::drawing::XShapes
;
65 using ::com::sun::star::drawing::XDrawPage
;
66 using ::com::sun::star::container::XChild
;
67 using ::com::sun::star::container::XIndexAccess
;
68 using ::com::sun::star::container::XEnumerationAccess
;
69 using ::com::sun::star::container::XEnumeration
;
70 using ::com::sun::star::beans::XPropertySet
;
71 using ::com::sun::star::beans::XPropertySetInfo
;
75 // go recursively through all shapes in the given XShapes collection and return true as soon as the
76 // given shape is found. nIndex is incremented for each shape with the same shape type as the given
77 // shape is found until the given shape is found.
78 static bool getShapeIndex( const Reference
< XShapes
>& xShapes
, const Reference
< XShape
>& xShape
, sal_Int32
& nIndex
)
80 const sal_Int32 nCount
= xShapes
->getCount();
82 for( n
= 0; n
< nCount
; n
++ )
84 Reference
< XShape
> xChild
;
85 xShapes
->getByIndex( n
) >>= xChild
;
86 if( xChild
== xShape
)
89 if( xChild
->getShapeType() == xShape
->getShapeType() )
92 Reference
< XShapes
> xChildContainer( xChild
, UNO_QUERY
);
93 if( xChildContainer
.is() )
95 if( getShapeIndex( xChildContainer
, xShape
, nIndex
) )
103 // returns the index of the shape type from the given shape
104 static sal_Int32
getShapeIndex( const Reference
< XShape
>& xShape
)
106 Reference
< XChild
> xChild( xShape
, UNO_QUERY
);
107 Reference
< XShapes
> xPage
;
109 while( xChild
.is() && !xPage
.is() )
111 Reference
< XInterface
> x( xChild
->getParent() );
112 xChild
.set( x
, UNO_QUERY
);
113 Reference
< XDrawPage
> xTestPage( x
, UNO_QUERY
);
115 xPage
.set( x
, UNO_QUERY
);
118 sal_Int32 nIndex
= 1;
120 if( xPage
.is() && getShapeIndex( xPage
, xShape
, nIndex
) )
126 OUString
getShapeDescription( const Reference
< XShape
>& xShape
, bool bWithText
)
128 OUString aDescription
;
129 Reference
< XPropertySet
> xSet( xShape
, UNO_QUERY
);
130 bool bAppendIndex
= true;
134 Reference
<XPropertySetInfo
> xInfo(xSet
->getPropertySetInfo());
137 static constexpr OUString
aPropName1(u
"Name"_ustr
);
138 if(xInfo
->hasPropertyByName(aPropName1
))
139 xSet
->getPropertyValue(aPropName1
) >>= aDescription
;
141 bAppendIndex
= aDescription
.isEmpty();
143 static constexpr OUString
aPropName2(u
"UINameSingular"_ustr
);
144 if(xInfo
->hasPropertyByName(aPropName2
))
145 xSet
->getPropertyValue(aPropName2
) >>= aDescription
;
150 TOOLS_WARN_EXCEPTION( "sd", "sd::getShapeDescription()" );
155 aDescription
+= " " + OUString::number(getShapeIndex(xShape
));
160 Reference
< XTextRange
> xText( xShape
, UNO_QUERY
);
163 OUString
aText( xText
->getString() );
164 if( !aText
.isEmpty() )
166 aDescription
+= ": ";
168 aText
= aText
.replace( '\n', ' ' );
169 aText
= aText
.replace( '\r', ' ' );
171 aDescription
+= aText
;
178 static OUString
getDescription( const Any
& rTarget
, bool bWithText
)
180 OUString aDescription
;
182 if( rTarget
.getValueType() == ::cppu::UnoType
<ParagraphTarget
>::get() )
184 ParagraphTarget aParaTarget
;
185 rTarget
>>= aParaTarget
;
187 css::uno::Reference
<css::document::XActionLockable
> xLockable(aParaTarget
.Shape
, css::uno::UNO_QUERY
);
189 xLockable
->addActionLock();
190 comphelper::ScopeGuard
aGuard([&xLockable
]()
193 xLockable
->removeActionLock();
196 Reference
< XEnumerationAccess
> xText( aParaTarget
.Shape
, UNO_QUERY_THROW
);
197 Reference
< XEnumeration
> xEnumeration( xText
->createEnumeration(), css::uno::UNO_SET_THROW
);
198 sal_Int32 nPara
= aParaTarget
.Paragraph
;
200 while( xEnumeration
->hasMoreElements() && nPara
)
202 xEnumeration
->nextElement();
206 DBG_ASSERT( xEnumeration
->hasMoreElements(), "sd::CustomAnimationEffect::prepareText(), paragraph out of range!" );
208 if( xEnumeration
->hasMoreElements() )
210 Reference
< XTextRange
> xParagraph
;
211 xEnumeration
->nextElement() >>= xParagraph
;
213 if( xParagraph
.is() )
214 aDescription
= xParagraph
->getString();
219 Reference
< XShape
> xShape
;
222 aDescription
= getShapeDescription( xShape
, bWithText
);
228 class CustomAnimationListEntryItem
231 CustomAnimationListEntryItem(OUString aDescription
,
232 CustomAnimationEffectPtr pEffect
);
233 const CustomAnimationEffectPtr
& getEffect() const { return mpEffect
; }
235 Size
GetSize(const vcl::RenderContext
& rRenderContext
);
236 void Paint(vcl::RenderContext
& rRenderContext
, const ::tools::Rectangle
& rRect
, bool bSelected
);
237 void PaintEffect(vcl::RenderContext
& rRenderContext
, const ::tools::Rectangle
& rRect
, bool bSelected
);
238 void PaintTrigger(vcl::RenderContext
& rRenderContext
, const ::tools::Rectangle
& rRect
);
241 OUString msDescription
;
242 OUString msEffectName
;
243 CustomAnimationEffectPtr mpEffect
;
246 static const ::tools::Long nIconWidth
= 19;
247 static const ::tools::Long nItemMinHeight
= 38;
250 CustomAnimationListEntryItem::CustomAnimationListEntryItem(OUString aDescription
, CustomAnimationEffectPtr pEffect
)
251 : msDescription(std::move(aDescription
))
252 , mpEffect(std::move(pEffect
))
256 switch (mpEffect
->getPresetClass())
258 case EffectPresetClass::ENTRANCE
:
259 msEffectName
= SdResId(STR_CUSTOMANIMATION_ENTRANCE
); break;
260 case EffectPresetClass::EXIT
:
261 msEffectName
= SdResId(STR_CUSTOMANIMATION_EXIT
); break;
262 case EffectPresetClass::EMPHASIS
:
263 msEffectName
= SdResId(STR_CUSTOMANIMATION_EMPHASIS
); break;
264 case EffectPresetClass::MOTIONPATH
:
265 msEffectName
= SdResId(STR_CUSTOMANIMATION_MOTION_PATHS
); break;
267 msEffectName
= SdResId(STR_CUSTOMANIMATION_MISC
); break;
269 msEffectName
= msEffectName
.replaceFirst( "%1" , CustomAnimationPresets::getCustomAnimationPresets().getUINameForPresetId(mpEffect
->getPresetId()));
272 IMPL_STATIC_LINK(CustomAnimationList
, CustomRenderHdl
, weld::TreeView::render_args
, aPayload
, void)
274 vcl::RenderContext
& rRenderContext
= std::get
<0>(aPayload
);
275 const ::tools::Rectangle
& rRect
= std::get
<1>(aPayload
);
276 bool bSelected
= std::get
<2>(aPayload
);
277 const OUString
& rId
= std::get
<3>(aPayload
);
279 CustomAnimationListEntryItem
* pItem
= weld::fromId
<CustomAnimationListEntryItem
*>(rId
);
281 pItem
->Paint(rRenderContext
, rRect
, bSelected
);
284 IMPL_STATIC_LINK(CustomAnimationList
, CustomGetSizeHdl
, weld::TreeView::get_size_args
, aPayload
, Size
)
286 vcl::RenderContext
& rRenderContext
= aPayload
.first
;
287 const OUString
& rId
= aPayload
.second
;
289 CustomAnimationListEntryItem
* pItem
= weld::fromId
<CustomAnimationListEntryItem
*>(rId
);
291 return Size(CustomAnimationListEntryItem::nIconWidth
, CustomAnimationListEntryItem::nItemMinHeight
);
292 return pItem
->GetSize(rRenderContext
);
295 Size
CustomAnimationListEntryItem::GetSize(const vcl::RenderContext
& rRenderContext
)
297 auto width
= rRenderContext
.GetTextWidth( msDescription
) + nIconWidth
;
298 if (width
< (rRenderContext
.GetTextWidth( msEffectName
) + 2*nIconWidth
))
299 width
= rRenderContext
.GetTextWidth( msEffectName
) + 2*nIconWidth
;
301 Size
aSize(width
, rRenderContext
.GetTextHeight() * 2);
302 if (aSize
.Height() < nItemMinHeight
)
303 aSize
.setHeight(nItemMinHeight
);
307 void CustomAnimationListEntryItem::PaintTrigger(vcl::RenderContext
& rRenderContext
, const ::tools::Rectangle
& rRect
)
309 Size
aSize(rRect
.GetSize());
311 ::tools::Rectangle
aOutRect(rRect
);
313 rRenderContext
.Push();
314 rRenderContext
.SetFillColor(rRenderContext
.GetSettings().GetStyleSettings().GetDialogColor());
315 rRenderContext
.SetLineColor();
316 // fill the background with the dialog bg color
317 rRenderContext
.DrawRect(aOutRect
);
319 // Erase the four corner pixels to make the rectangle appear rounded.
320 rRenderContext
.SetLineColor(rRenderContext
.GetSettings().GetStyleSettings().GetWindowColor());
321 rRenderContext
.DrawPixel(aOutRect
.TopLeft());
322 rRenderContext
.DrawPixel(Point(aOutRect
.Right(), aOutRect
.Top()));
323 rRenderContext
.DrawPixel(Point(aOutRect
.Left(), aOutRect
.Bottom()));
324 rRenderContext
.DrawPixel(Point(aOutRect
.Right(), aOutRect
.Bottom()));
326 // draw the category title
328 int nVertBorder
= ((aSize
.Height() - rRenderContext
.GetTextHeight()) >> 1);
329 int nHorzBorder
= rRenderContext
.LogicToPixel(Size(3, 3), MapMode(MapUnit::MapAppFont
)).Width();
331 aOutRect
.AdjustLeft(nHorzBorder
);
332 aOutRect
.AdjustRight( -nHorzBorder
);
333 aOutRect
.AdjustTop( nVertBorder
);
334 aOutRect
.AdjustBottom( -nVertBorder
);
336 // Draw the text with the dialog text color
337 rRenderContext
.SetTextColor(rRenderContext
.GetSettings().GetStyleSettings().GetDialogTextColor());
338 rRenderContext
.DrawText(aOutRect
, rRenderContext
.GetEllipsisString(msDescription
, aOutRect
.GetWidth()));
339 rRenderContext
.Pop();
342 void CustomAnimationListEntryItem::PaintEffect(vcl::RenderContext
& rRenderContext
, const ::tools::Rectangle
& rRect
, bool bSelected
)
344 rRenderContext
.Push(vcl::PushFlags::TEXTCOLOR
);
345 const StyleSettings
& rStyleSettings
= Application::GetSettings().GetStyleSettings();
347 rRenderContext
.SetTextColor(rStyleSettings
.GetHighlightTextColor());
349 rRenderContext
.SetTextColor(rStyleSettings
.GetDialogTextColor());
351 Point
aPos(rRect
.TopLeft());
352 int nItemHeight
= rRect
.GetHeight();
354 Size nImageSize
= Image(StockImage::Yes
, BMP_CUSTOMANIMATION_AFTER_PREVIOUS
).GetSizePixel();
356 sal_Int16 nNodeType
= mpEffect
->getNodeType();
357 if (nNodeType
== EffectNodeType::ON_CLICK
)
359 Image
aImage(Image(StockImage::Yes
, BMP_CUSTOMANIMATION_ON_CLICK
));
360 nImageSize
= aImage
.GetSizePixel();
361 aPos
.AdjustY(nItemHeight
/ 4 - nImageSize
.Height() / 2);
362 rRenderContext
.DrawImage(aPos
, aImage
);
364 else if (nNodeType
== EffectNodeType::AFTER_PREVIOUS
)
366 Image
aImage(Image(StockImage::Yes
, BMP_CUSTOMANIMATION_AFTER_PREVIOUS
));
367 nImageSize
= aImage
.GetSizePixel();
368 aPos
.AdjustY(nItemHeight
/ 4 - nImageSize
.Height() / 2);
369 rRenderContext
.DrawImage(aPos
, aImage
);
371 else if (nNodeType
== EffectNodeType::WITH_PREVIOUS
)
373 //FIXME With previous image not defined in CustomAnimation.src
376 aPos
= rRect
.TopLeft();
377 aPos
.AdjustX(nImageSize
.Width() + 5);
379 //TODO, full width of widget ?
380 rRenderContext
.DrawText(aPos
, rRenderContext
.GetEllipsisString(msDescription
, rRect
.GetWidth()));
383 switch (mpEffect
->getPresetClass())
385 case EffectPresetClass::ENTRANCE
:
386 sImage
= BMP_CUSTOMANIMATION_ENTRANCE_EFFECT
; break;
387 case EffectPresetClass::EXIT
:
388 sImage
= BMP_CUSTOMANIMATION_EXIT_EFFECT
; break;
389 case EffectPresetClass::EMPHASIS
:
390 sImage
= BMP_CUSTOMANIMATION_EMPHASIS_EFFECT
; break;
391 case EffectPresetClass::MOTIONPATH
:
392 sImage
= BMP_CUSTOMANIMATION_MOTION_PATH
; break;
393 case EffectPresetClass::OLEACTION
:
394 sImage
= BMP_CUSTOMANIMATION_OLE
; break;
395 case EffectPresetClass::MEDIACALL
:
396 switch (mpEffect
->getCommand())
398 case EffectCommands::TOGGLEPAUSE
:
399 sImage
= BMP_CUSTOMANIMATION_MEDIA_PAUSE
; break;
400 case EffectCommands::STOP
:
401 sImage
= BMP_CUSTOMANIMATION_MEDIA_STOP
; break;
402 case EffectCommands::PLAY
:
404 sImage
= BMP_CUSTOMANIMATION_MEDIA_PLAY
; break;
411 if (!sImage
.isEmpty())
413 Image
aImage(StockImage::Yes
, sImage
);
414 nImageSize
= aImage
.GetSizePixel();
415 Point
aImagePos(aPos
);
416 aImagePos
.AdjustY(nItemHeight
* 3 / 4 - nImageSize
.Height() / 2);
417 rRenderContext
.DrawImage(aImagePos
, aImage
);
421 Image
aImage(StockImage::Yes
, BMP_CUSTOMANIMATION_ENTRANCE_EFFECT
);
422 nImageSize
= aImage
.GetSizePixel();
425 aPos
.AdjustX(nImageSize
.Width() + 5);
426 aPos
.AdjustY(nItemHeight
/ 2);
428 rRenderContext
.DrawText(aPos
, rRenderContext
.GetEllipsisString(msEffectName
, rRect
.GetWidth()));
429 rRenderContext
.Pop();
432 void CustomAnimationListEntryItem::Paint(vcl::RenderContext
& rRenderContext
, const ::tools::Rectangle
& rRect
, bool bSelected
)
435 PaintEffect(rRenderContext
, rRect
, bSelected
);
437 PaintTrigger(rRenderContext
, rRect
);
440 CustomAnimationList::CustomAnimationList(std::unique_ptr
<weld::TreeView
> xTreeView
,
441 std::unique_ptr
<weld::Label
> xLabel
,
442 std::unique_ptr
<weld::Widget
> xScrolledWindow
)
443 : mxTreeView(std::move(xTreeView
))
444 , maDropTargetHelper(*this)
445 , mxEmptyLabel(std::move(xLabel
))
446 , mxEmptyLabelParent(std::move(xScrolledWindow
))
447 , mbIgnorePaint(false)
448 , mpController(nullptr)
450 , mnPostExpandEvent(nullptr)
451 , mnPostCollapseEvent(nullptr)
453 mxEmptyLabel
->set_stack_background();
455 mxTreeView
->set_selection_mode(SelectionMode::Multiple
);
456 mxTreeView
->connect_selection_changed(LINK(this, CustomAnimationList
, SelectHdl
));
457 mxTreeView
->connect_key_press(LINK(this, CustomAnimationList
, KeyInputHdl
));
458 mxTreeView
->connect_popup_menu(LINK(this, CustomAnimationList
, CommandHdl
));
459 mxTreeView
->connect_row_activated(LINK(this, CustomAnimationList
, DoubleClickHdl
));
460 mxTreeView
->connect_expanding(LINK(this, CustomAnimationList
, ExpandHdl
));
461 mxTreeView
->connect_collapsing(LINK(this, CustomAnimationList
, CollapseHdl
));
462 mxTreeView
->connect_drag_begin(LINK(this, CustomAnimationList
, DragBeginHdl
));
463 mxTreeView
->connect_custom_get_size(LINK(this, CustomAnimationList
, CustomGetSizeHdl
));
464 mxTreeView
->connect_custom_render(LINK(this, CustomAnimationList
, CustomRenderHdl
));
465 mxTreeView
->set_column_custom_renderer(1, true);
468 CustomAnimationListDropTarget::CustomAnimationListDropTarget(CustomAnimationList
& rTreeView
)
469 : DropTargetHelper(rTreeView
.get_widget().get_drop_target())
470 , m_rTreeView(rTreeView
)
474 sal_Int8
CustomAnimationListDropTarget::AcceptDrop(const AcceptDropEvent
& rEvt
)
476 sal_Int8 nAccept
= m_rTreeView
.AcceptDrop(rEvt
);
478 if (nAccept
!= DND_ACTION_NONE
)
480 // to enable the autoscroll when we're close to the edges
481 weld::TreeView
& rWidget
= m_rTreeView
.get_widget();
482 rWidget
.get_dest_row_at_pos(rEvt
.maPosPixel
, nullptr, true);
488 sal_Int8
CustomAnimationListDropTarget::ExecuteDrop(const ExecuteDropEvent
& rEvt
)
490 return m_rTreeView
.ExecuteDrop(rEvt
);
493 // D'n'D #1: Record selected effects for drag'n'drop.
494 IMPL_LINK(CustomAnimationList
, DragBeginHdl
, bool&, rUnsetDragIcon
, bool)
496 rUnsetDragIcon
= false;
498 // Record which effects are selected:
499 // Since NextSelected(..) iterates through the selected items in the order they
500 // were selected, create a sorted list for simpler drag'n'drop algorithms.
501 mDndEffectsSelected
.clear();
502 mxTreeView
->selected_foreach([this](weld::TreeIter
& rEntry
){
503 mDndEffectsSelected
.emplace_back(mxTreeView
->make_iterator(&rEntry
));
507 // Note: pEntry is the effect with focus (if multiple effects are selected)
508 mxDndEffectDragging
= mxTreeView
->make_iterator();
509 if (!mxTreeView
->get_cursor(mxDndEffectDragging
.get()))
510 mxDndEffectDragging
.reset();
512 // Allow normal processing.
516 // D'n'D #3: Called each time mouse moves during drag
517 sal_Int8
CustomAnimationList::AcceptDrop( const AcceptDropEvent
& rEvt
)
519 sal_Int8 ret
= DND_ACTION_NONE
;
521 const bool bIsMove
= DND_ACTION_MOVE
== rEvt
.mnAction
;
522 if (mxDndEffectDragging
&& !rEvt
.mbLeaving
&& bIsMove
)
523 ret
= DND_ACTION_MOVE
;
527 // D'n'D #5: Tell model to update effect order.
528 sal_Int8
CustomAnimationList::ExecuteDrop(const ExecuteDropEvent
& rEvt
)
530 std::unique_ptr
<weld::TreeIter
> xDndEffectInsertBefore(mxTreeView
->make_iterator());
531 if (!mxTreeView
->get_dest_row_at_pos(rEvt
.maPosPixel
, xDndEffectInsertBefore
.get(), true))
532 xDndEffectInsertBefore
.reset();
534 const bool bMovingEffect
= ( mxDndEffectDragging
!= nullptr );
535 const bool bMoveNotSelf
= !xDndEffectInsertBefore
|| (mxDndEffectDragging
&& mxTreeView
->iter_compare(*xDndEffectInsertBefore
, *mxDndEffectDragging
) != 0);
536 const bool bHaveSequence(mpMainSequence
);
538 if( bMovingEffect
&& bMoveNotSelf
&& bHaveSequence
)
540 CustomAnimationListEntryItem
* pTarget
= xDndEffectInsertBefore
?
541 weld::fromId
<CustomAnimationListEntryItem
*>(mxTreeView
->get_id(*xDndEffectInsertBefore
)) :
544 // Build list of effects
545 std::vector
< CustomAnimationEffectPtr
> aEffects
;
546 for( const auto &pEntry
: mDndEffectsSelected
)
548 CustomAnimationListEntryItem
* pCustomAnimationEffect
= weld::fromId
<CustomAnimationListEntryItem
*>(mxTreeView
->get_id(*pEntry
));
549 aEffects
.push_back(pCustomAnimationEffect
->getEffect());
552 // Callback to observer to have it update the model.
553 // If pTarget is null, pass nullptr to indicate end of list.
554 mpController
->onDragNDropComplete(
556 pTarget
? pTarget
->getEffect() : nullptr );
559 mxTreeView
->select(*mxDndEffectDragging
);
563 // NOTE: Don't call default handler because all required
564 // move operations have been completed here to update the model.
565 return DND_ACTION_NONE
;
568 CustomAnimationList::~CustomAnimationList()
570 if (mnPostExpandEvent
)
572 Application::RemoveUserEvent(mnPostExpandEvent
);
573 mnPostExpandEvent
= nullptr;
576 if (mnPostCollapseEvent
)
578 Application::RemoveUserEvent(mnPostCollapseEvent
);
579 mnPostCollapseEvent
= nullptr;
583 mpMainSequence
->removeListener( this );
588 IMPL_LINK(CustomAnimationList
, KeyInputHdl
, const KeyEvent
&, rKEvt
, bool)
590 const int nKeyCode
= rKEvt
.GetKeyCode().GetCode();
594 mpController
->onContextMenu(u
"remove"_ustr
);
597 mpController
->onContextMenu(u
"create"_ustr
);
601 std::unique_ptr
<weld::TreeIter
> xEntry
= mxTreeView
->make_iterator();
602 if (mxTreeView
->get_cursor(xEntry
.get()))
604 auto aRect
= mxTreeView
->get_row_area(*xEntry
);
605 const Point
aPos(aRect
.getOpenWidth() / 2, aRect
.getOpenHeight() / 2);
606 const CommandEvent
aCEvt(aPos
, CommandEventId::ContextMenu
);
615 /** selects or deselects the given effect.
616 Selections of other effects are not changed */
617 void CustomAnimationList::select( const CustomAnimationEffectPtr
& pEffect
)
619 CustomAnimationListEntryItem
* pEntry
= nullptr;
621 std::unique_ptr
<weld::TreeIter
> xEntry
= mxTreeView
->make_iterator();
622 if (mxTreeView
->get_iter_first(*xEntry
))
626 CustomAnimationListEntryItem
* pTestEntry
= weld::fromId
<CustomAnimationListEntryItem
*>(mxTreeView
->get_id(*xEntry
));
627 if (pTestEntry
->getEffect() == pEffect
)
629 mxTreeView
->select(*xEntry
);
630 mxTreeView
->scroll_to_row(*xEntry
);
634 } while (mxTreeView
->iter_next(*xEntry
));
644 void CustomAnimationList::clear()
649 mxEmptyLabelParent
->show();
652 mxLastParentEntry
.reset();
653 mxLastTargetShape
= nullptr;
656 void CustomAnimationList::update( const MainSequencePtr
& pMainSequence
)
659 mpMainSequence
->removeListener( this );
661 mpMainSequence
= pMainSequence
;
665 mpMainSequence
->addListener( this );
668 struct stl_append_effect_func
670 explicit stl_append_effect_func( CustomAnimationList
& rList
) : mrList( rList
) {}
671 void operator()(const CustomAnimationEffectPtr
& pEffect
);
672 CustomAnimationList
& mrList
;
675 void stl_append_effect_func::operator()(const CustomAnimationEffectPtr
& pEffect
)
677 mrList
.append( pEffect
);
680 void CustomAnimationList::update()
682 mbIgnorePaint
= true;
684 std::vector
< CustomAnimationEffectPtr
> aVisible
;
685 std::vector
< CustomAnimationEffectPtr
> aSelected
;
686 CustomAnimationEffectPtr aCurrent
;
688 CustomAnimationEffectPtr pFirstSelEffect
;
689 CustomAnimationEffectPtr pLastSelEffect
;
690 ::tools::Long nFirstVis
= -1;
691 ::tools::Long nLastVis
= -1;
692 ::tools::Long nFirstSelOld
= -1;
693 ::tools::Long nLastSelOld
= -1;
695 std::unique_ptr
<weld::TreeIter
> xEntry
= mxTreeView
->make_iterator();
699 std::unique_ptr
<weld::TreeIter
> xLastSelectedEntry
;
700 std::unique_ptr
<weld::TreeIter
> xLastVisibleEntry
;
702 // save selection, current, and expand (visible) states
703 mxTreeView
->all_foreach([this, &aVisible
, &nFirstVis
, &xLastVisibleEntry
,
704 &aSelected
, &nFirstSelOld
, &pFirstSelEffect
, &xLastSelectedEntry
](weld::TreeIter
& rEntry
){
705 CustomAnimationListEntryItem
* pEntry
= weld::fromId
<CustomAnimationListEntryItem
*>(mxTreeView
->get_id(rEntry
));
706 CustomAnimationEffectPtr
pEffect(pEntry
->getEffect());
709 if (weld::IsEntryVisible(*mxTreeView
, rEntry
))
711 aVisible
.push_back(pEffect
);
712 // save scroll position
714 nFirstVis
= weld::GetAbsPos(*mxTreeView
, rEntry
);
715 if (!xLastVisibleEntry
)
716 xLastVisibleEntry
= mxTreeView
->make_iterator(&rEntry
);
718 mxTreeView
->copy_iterator(rEntry
, *xLastVisibleEntry
);
721 if (mxTreeView
->is_selected(rEntry
))
723 aSelected
.push_back(pEffect
);
724 if (nFirstSelOld
== -1)
726 pFirstSelEffect
= std::move(pEffect
);
727 nFirstSelOld
= weld::GetAbsPos(*mxTreeView
, rEntry
);
729 if (!xLastSelectedEntry
)
730 xLastSelectedEntry
= mxTreeView
->make_iterator(&rEntry
);
732 mxTreeView
->copy_iterator(rEntry
, *xLastSelectedEntry
);
739 if (xLastSelectedEntry
)
741 CustomAnimationListEntryItem
* pEntry
= weld::fromId
<CustomAnimationListEntryItem
*>(mxTreeView
->get_id(*xLastSelectedEntry
));
742 pLastSelEffect
= pEntry
->getEffect();
743 nLastSelOld
= weld::GetAbsPos(*mxTreeView
, *xLastSelectedEntry
);
746 if (xLastVisibleEntry
)
747 nLastVis
= weld::GetAbsPos(*mxTreeView
, *xLastVisibleEntry
);
749 if (mxTreeView
->get_cursor(xEntry
.get()))
751 CustomAnimationListEntryItem
* pEntry
= weld::fromId
<CustomAnimationListEntryItem
*>(mxTreeView
->get_id(*xEntry
));
752 aCurrent
= pEntry
->getEffect();
758 mxTreeView
->freeze();
764 std::for_each( mpMainSequence
->getBegin(), mpMainSequence
->getEnd(), stl_append_effect_func( *this ) );
765 mxLastParentEntry
.reset();
767 auto rInteractiveSequenceVector
= mpMainSequence
->getInteractiveSequenceVector();
769 for (InteractiveSequencePtr
const& pIS
: rInteractiveSequenceVector
)
771 Reference
< XShape
> xShape( pIS
->getTriggerShape() );
774 OUString aDescription
= SdResId(STR_CUSTOMANIMATION_TRIGGER
) + ": " +
775 getShapeDescription( xShape
, false );
777 mxEntries
.emplace_back(std::make_unique
<CustomAnimationListEntryItem
>(aDescription
, nullptr));
779 OUString
sId(weld::toId(mxEntries
.back().get()));
780 mxTreeView
->insert(nullptr, -1, &aDescription
, &sId
, nullptr, nullptr, false, nullptr);
781 std::for_each( pIS
->getBegin(), pIS
->getEnd(), stl_append_effect_func( *this ) );
782 mxLastParentEntry
.reset();
789 if (mxTreeView
->n_children())
791 mxEmptyLabelParent
->hide();
797 ::tools::Long nFirstSelNew
= -1;
798 ::tools::Long nLastSelNew
= -1;
800 std::vector
<std::unique_ptr
<weld::TreeIter
>> aNewSelection
;
802 // restore selection state, expand state, and current-entry (under cursor)
803 if (mxTreeView
->get_iter_first(*xEntry
))
807 CustomAnimationListEntryItem
* pEntry
= weld::fromId
<CustomAnimationListEntryItem
*>(mxTreeView
->get_id(*xEntry
));
809 CustomAnimationEffectPtr
pEffect( pEntry
->getEffect() );
812 // Any effects that were visible should still be visible, so expand their parents.
813 // (a previously expanded parent may have moved leaving a child to now be the new parent to expand)
814 if( std::find( aVisible
.begin(), aVisible
.end(), pEffect
) != aVisible
.end() )
816 if (mxTreeView
->get_iter_depth(*xEntry
))
818 std::unique_ptr
<weld::TreeIter
> xParentEntry
= mxTreeView
->make_iterator(xEntry
.get());
819 mxTreeView
->iter_parent(*xParentEntry
);
820 mxTreeView
->expand_row(*xParentEntry
);
824 if( std::find( aSelected
.begin(), aSelected
.end(), pEffect
) != aSelected
.end() )
825 aNewSelection
.emplace_back(mxTreeView
->make_iterator(xEntry
.get()));
827 // Restore the cursor, as it may deselect other effects wait until
828 // after the loop to reset the selection
829 if( pEffect
== aCurrent
)
830 mxTreeView
->set_cursor(*xEntry
);
832 if (pEffect
== pFirstSelEffect
)
833 nFirstSelNew
= weld::GetAbsPos(*mxTreeView
, *xEntry
);
835 if (pEffect
== pLastSelEffect
)
836 nLastSelNew
= weld::GetAbsPos(*mxTreeView
, *xEntry
);
838 } while (mxTreeView
->iter_next(*xEntry
));
841 // tdf#147032 unselect what previous set_cursor may have caused to get selected as a side-effect
842 mxTreeView
->unselect_all();
843 for (const auto& rEntry
: aNewSelection
)
844 mxTreeView
->select(*rEntry
);
846 // Scroll to a selected entry, depending on where the selection moved.
847 const bool bMoved
= nFirstSelNew
!= nFirstSelOld
;
848 const bool bMovedUp
= nFirstSelNew
< nFirstSelOld
;
849 const bool bMovedDown
= nFirstSelNew
> nFirstSelOld
;
851 if( bMoved
&& nLastSelOld
< nFirstVis
&& nLastSelNew
< nFirstVis
)
853 // The selection is above the visible area.
854 // Scroll up to show the last few selected entries.
855 if( nLastSelNew
- (nLastVis
- nFirstVis
) > nFirstSelNew
)
857 // The entries in the selection range can't fit in view.
858 // Scroll so the last selected entry is last in view.
859 mxTreeView
->vadjustment_set_value(nLastSelNew
- (nLastVis
- nFirstVis
));
862 mxTreeView
->vadjustment_set_value(nFirstSelNew
);
864 else if( bMoved
&& nFirstSelOld
> nLastVis
&& nFirstSelNew
> nLastVis
)
866 // The selection is below the visible area.
867 // Scroll down to the first few selected entries.
868 mxTreeView
->vadjustment_set_value(nFirstSelNew
);
870 else if( bMovedUp
&& nFirstSelOld
<= nFirstVis
)
872 // A visible entry has moved up out of view; scroll up one.
873 mxTreeView
->vadjustment_set_value(nFirstVis
- 1);
875 else if( bMovedDown
&& nLastSelOld
>= nLastVis
)
877 // An entry has moved down out of view; scroll down one.
878 mxTreeView
->vadjustment_set_value(nFirstVis
+ 1);
880 else if ( nFirstVis
!= -1 )
882 // The selection is still in view, or it hasn't moved.
883 mxTreeView
->vadjustment_set_value(nFirstVis
);
887 mbIgnorePaint
= false;
892 void CustomAnimationList::append( CustomAnimationEffectPtr pEffect
)
894 Any
aTarget( pEffect
->getTarget() );
895 if( !aTarget
.hasValue() )
900 // create a ui description
901 OUString aDescription
= getDescription(aTarget
, pEffect
->getTargetSubItem() != ShapeAnimationSubType::ONLY_BACKGROUND
);
903 std::unique_ptr
<weld::TreeIter
> xParentEntry
;
905 Reference
< XShape
> xTargetShape( pEffect
->getTargetShape() );
906 sal_Int32 nGroupId
= pEffect
->getGroupId();
908 // if this effect has the same target and group-id as the last root effect,
909 // the last root effect is also this effects parent
910 if (mxLastParentEntry
&& nGroupId
!= -1 && mxLastTargetShape
== xTargetShape
&& mnLastGroupId
== nGroupId
)
911 xParentEntry
= mxTreeView
->make_iterator(mxLastParentEntry
.get());
913 // create an entry for the effect
914 std::unique_ptr
<weld::TreeIter
> xEntry
= mxTreeView
->make_iterator();
916 mxEntries
.emplace_back(std::make_unique
<CustomAnimationListEntryItem
>(aDescription
, pEffect
));
918 OUString
sId(weld::toId(mxEntries
.back().get()));
923 mxTreeView
->insert(xParentEntry
.get(), -1, &aDescription
, &sId
, nullptr, nullptr, false, xEntry
.get());
928 mxTreeView
->insert(nullptr, -1, &aDescription
, &sId
, nullptr, nullptr, false, xEntry
.get());
930 // and the new root entry becomes the possible next group header
931 mxLastTargetShape
= std::move(xTargetShape
);
932 mnLastGroupId
= nGroupId
;
933 mxLastParentEntry
= std::move(xEntry
);
936 catch (const Exception
&)
938 TOOLS_WARN_EXCEPTION( "sd", "sd::CustomAnimationList::append()" );
942 static void selectShape(weld::TreeView
* pTreeList
, const Reference
< XShape
>& xShape
)
944 std::unique_ptr
<weld::TreeIter
> xEntry
= pTreeList
->make_iterator();
945 if (!pTreeList
->get_iter_first(*xEntry
))
948 bool bFirstEntry
= true;
952 CustomAnimationListEntryItem
* pEntry
= weld::fromId
<CustomAnimationListEntryItem
*>(pTreeList
->get_id(*xEntry
));
953 CustomAnimationEffectPtr
pEffect(pEntry
->getEffect());
956 if (pEffect
->getTarget() == xShape
)
958 pTreeList
->select(*xEntry
);
961 pTreeList
->scroll_to_row(*xEntry
);
966 } while (pTreeList
->iter_next(*xEntry
));
969 void CustomAnimationList::onSelectionChanged(const Any
& rSelection
)
973 mxTreeView
->unselect_all();
975 if (rSelection
.hasValue())
977 Reference
< XIndexAccess
> xShapes(rSelection
, UNO_QUERY
);
980 sal_Int32 nCount
= xShapes
->getCount();
982 for( nIndex
= 0; nIndex
< nCount
; nIndex
++ )
984 Reference
< XShape
> xShape( xShapes
->getByIndex( nIndex
), UNO_QUERY
);
986 selectShape(mxTreeView
.get(), xShape
);
991 Reference
< XShape
> xShape(rSelection
, UNO_QUERY
);
993 selectShape(mxTreeView
.get(), xShape
);
1001 TOOLS_WARN_EXCEPTION( "sd", "sd::CustomAnimationList::onSelectionChanged()" );
1005 IMPL_LINK_NOARG(CustomAnimationList
, SelectHdl
, weld::TreeView
&, void)
1010 // Notify controller to refresh UI when we are notified of selection change from base class
1011 void CustomAnimationList::Select()
1015 mpController
->onSelect();
1018 IMPL_LINK_NOARG(CustomAnimationList
, PostExpandHdl
, void*, void)
1020 std::unique_ptr
<weld::TreeIter
> xEntry
= mxTreeView
->make_iterator();
1021 if (mxTreeView
->get_selected(xEntry
.get()))
1023 for (bool bChild
= mxTreeView
->iter_children(*xEntry
); bChild
; bChild
= mxTreeView
->iter_next_sibling(*xEntry
))
1025 if (!mxTreeView
->is_selected(*xEntry
))
1026 mxTreeView
->select(*xEntry
);
1030 // Notify controller that selection has changed (it should update the UI)
1031 mpController
->onSelect();
1033 mnPostExpandEvent
= nullptr;
1036 IMPL_LINK(CustomAnimationList
, ExpandHdl
, const weld::TreeIter
&, rParent
, bool)
1038 // If expanded entry is selected, then select its children too afterwards.
1039 if (mxTreeView
->is_selected(rParent
) && !mnPostExpandEvent
) {
1040 mnPostExpandEvent
= Application::PostUserEvent(LINK(this, CustomAnimationList
, PostExpandHdl
));
1046 IMPL_LINK_NOARG(CustomAnimationList
, PostCollapseHdl
, void*, void)
1048 // Deselect all entries as SvTreeListBox::Collapse selects the last
1049 // entry to have focus (or its parent), which is not desired
1050 mxTreeView
->unselect_all();
1052 // Restore selection state for entries which are still visible
1053 for (const auto &pEntry
: lastSelectedEntries
)
1055 if (weld::IsEntryVisible(*mxTreeView
, *pEntry
))
1056 mxTreeView
->select(*pEntry
);
1059 lastSelectedEntries
.clear();
1061 // Notify controller that selection has changed (it should update the UI)
1062 mpController
->onSelect();
1064 mnPostCollapseEvent
= nullptr;
1067 IMPL_LINK_NOARG(CustomAnimationList
, CollapseHdl
, const weld::TreeIter
&, bool)
1069 if (!mnPostCollapseEvent
)
1071 // weld::TreeView::collapse() discards multi-selection state
1072 // of list entries, so first save current selection state
1073 mxTreeView
->selected_foreach([this](weld::TreeIter
& rEntry
){
1074 lastSelectedEntries
.emplace_back(mxTreeView
->make_iterator(&rEntry
));
1078 mnPostCollapseEvent
= Application::PostUserEvent(LINK(this, CustomAnimationList
, PostCollapseHdl
));
1081 // Execute collapse on base class
1085 bool CustomAnimationList::isExpanded( const CustomAnimationEffectPtr
& pEffect
) const
1087 bool bExpanded
= true; // we assume expanded by default
1089 std::unique_ptr
<weld::TreeIter
> xEntry
= mxTreeView
->make_iterator();
1090 if (mxTreeView
->get_iter_first(*xEntry
))
1094 CustomAnimationListEntryItem
* pEntry
=
1095 weld::fromId
<CustomAnimationListEntryItem
*>(mxTreeView
->get_id(*xEntry
));
1096 if (pEntry
->getEffect() == pEffect
)
1098 if (mxTreeView
->get_iter_depth(*xEntry
)) // no parent, keep expanded default of true
1100 std::unique_ptr
<weld::TreeIter
> xParentEntry
= mxTreeView
->make_iterator(xEntry
.get());
1101 if (mxTreeView
->iter_parent(*xParentEntry
))
1102 bExpanded
= mxTreeView
->get_row_expanded(*xParentEntry
);
1106 } while (mxTreeView
->iter_next(*xEntry
));
1112 bool CustomAnimationList::isVisible(const CustomAnimationEffectPtr
& pEffect
) const
1114 std::unique_ptr
<weld::TreeIter
> xEntry
= mxTreeView
->make_iterator();
1115 if (mxTreeView
->get_iter_first(*xEntry
))
1119 CustomAnimationListEntryItem
* pTestEntry
= weld::fromId
<CustomAnimationListEntryItem
*>(mxTreeView
->get_id(*xEntry
));
1120 if (pTestEntry
->getEffect() == pEffect
)
1121 return weld::IsEntryVisible(*mxTreeView
, *xEntry
);
1122 } while (mxTreeView
->iter_next(*xEntry
));
1127 EffectSequence
CustomAnimationList::getSelection() const
1129 EffectSequence aSelection
;
1131 mxTreeView
->selected_foreach([this, &aSelection
](weld::TreeIter
& rEntry
){
1132 CustomAnimationListEntryItem
* pEntry
= weld::fromId
<CustomAnimationListEntryItem
*>(mxTreeView
->get_id(rEntry
));
1133 CustomAnimationEffectPtr
pEffect(pEntry
->getEffect());
1135 aSelection
.push_back(pEffect
);
1137 // if the selected effect is not expanded and has children
1138 // we say that the children are automatically selected
1139 if (!mxTreeView
->get_row_expanded(rEntry
) && mxTreeView
->iter_has_child(rEntry
))
1141 std::unique_ptr
<weld::TreeIter
> xChild
= mxTreeView
->make_iterator(&rEntry
);
1142 (void)mxTreeView
->iter_children(*xChild
);
1146 if (!mxTreeView
->is_selected(*xChild
))
1148 CustomAnimationListEntryItem
* pChild
= weld::fromId
<CustomAnimationListEntryItem
*>(mxTreeView
->get_id(*xChild
));
1149 const CustomAnimationEffectPtr
& pChildEffect( pChild
->getEffect() );
1151 aSelection
.push_back( pChildEffect
);
1153 } while (mxTreeView
->iter_next_sibling(*xChild
));
1162 IMPL_LINK_NOARG(CustomAnimationList
, DoubleClickHdl
, weld::TreeView
&, bool)
1164 mpController
->onDoubleClick();
1168 IMPL_LINK(CustomAnimationList
, CommandHdl
, const CommandEvent
&, rCEvt
, bool)
1170 if (rCEvt
.GetCommand() != CommandEventId::ContextMenu
)
1173 if (rCEvt
.IsMouseEvent())
1175 ::Point aPos
= rCEvt
.GetMousePosPixel();
1176 std::unique_ptr
<weld::TreeIter
> xIter(mxTreeView
->make_iterator());
1177 if (mxTreeView
->get_dest_row_at_pos(aPos
, xIter
.get(), false) && !mxTreeView
->is_selected(*xIter
))
1179 mxTreeView
->unselect_all();
1180 mxTreeView
->set_cursor(*xIter
);
1181 mxTreeView
->select(*xIter
);
1182 SelectHdl(*mxTreeView
);
1186 if (!mxTreeView
->get_selected(nullptr))
1189 std::unique_ptr
<weld::Builder
> xBuilder(Application::CreateBuilder(mxTreeView
.get(), u
"modules/simpress/ui/effectmenu.ui"_ustr
));
1190 std::unique_ptr
<weld::Menu
> xMenu
= xBuilder
->weld_menu(u
"menu"_ustr
);
1192 sal_Int16 nNodeType
= -1;
1193 sal_Int16 nEntries
= 0;
1195 mxTreeView
->selected_foreach([this, &nNodeType
, &nEntries
](weld::TreeIter
& rEntry
){
1196 CustomAnimationListEntryItem
* pEntry
= weld::fromId
<CustomAnimationListEntryItem
*>(mxTreeView
->get_id(rEntry
));
1197 CustomAnimationEffectPtr
pEffect(pEntry
->getEffect());
1202 if( nNodeType
== -1 )
1204 nNodeType
= pEffect
->getNodeType();
1208 if( nNodeType
!= pEffect
->getNodeType() )
1219 xMenu
->set_active(u
"onclick"_ustr
, nNodeType
== EffectNodeType::ON_CLICK
);
1220 xMenu
->set_active(u
"withprev"_ustr
, nNodeType
== EffectNodeType::WITH_PREVIOUS
);
1221 xMenu
->set_active(u
"afterprev"_ustr
, nNodeType
== EffectNodeType::AFTER_PREVIOUS
);
1222 xMenu
->set_sensitive(u
"options"_ustr
, nEntries
== 1);
1223 xMenu
->set_sensitive(u
"timing"_ustr
, nEntries
== 1);
1225 OUString sCommand
= xMenu
->popup_at_rect(mxTreeView
.get(), ::tools::Rectangle(rCEvt
.GetMousePosPixel(), Size(1,1)));
1226 if (!sCommand
.isEmpty())
1227 ExecuteContextMenuAction(sCommand
);
1232 void CustomAnimationList::ExecuteContextMenuAction(const OUString
& rIdent
)
1234 mpController
->onContextMenu(rIdent
);
1237 void CustomAnimationList::notify_change()
1240 mpController
->onSelect();
1245 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */