wsl-as-helper: also support winget's version of Git on aarch64
[LibreOffice.git] / sd / source / ui / animations / CustomAnimationList.cxx
blob8df6d31588fd721e38e2a648dccab4165e1bcf2c
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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>
34 #include <utility>
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>
50 #include <algorithm>
51 #include <memory>
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;
73 namespace sd {
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();
81 sal_Int32 n;
82 for( n = 0; n < nCount; n++ )
84 Reference< XShape > xChild;
85 xShapes->getByIndex( n ) >>= xChild;
86 if( xChild == xShape )
87 return true;
89 if( xChild->getShapeType() == xShape->getShapeType() )
90 nIndex++;
92 Reference< XShapes > xChildContainer( xChild, UNO_QUERY );
93 if( xChildContainer.is() )
95 if( getShapeIndex( xChildContainer, xShape, nIndex ) )
96 return true;
100 return false;
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 );
114 if( xTestPage.is() )
115 xPage.set( x, UNO_QUERY );
118 sal_Int32 nIndex = 1;
120 if( xPage.is() && getShapeIndex( xPage, xShape, nIndex ) )
121 return nIndex;
122 else
123 return -1;
126 OUString getShapeDescription( const Reference< XShape >& xShape, bool bWithText )
128 OUString aDescription;
129 Reference< XPropertySet > xSet( xShape, UNO_QUERY );
130 bool bAppendIndex = true;
132 if(xSet.is()) try
134 Reference<XPropertySetInfo> xInfo(xSet->getPropertySetInfo());
135 if (xInfo.is())
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;
148 catch( Exception& )
150 TOOLS_WARN_EXCEPTION( "sd", "sd::getShapeDescription()" );
153 if (bAppendIndex)
155 aDescription += " " + OUString::number(getShapeIndex(xShape));
158 if( bWithText )
160 Reference< XTextRange > xText( xShape, UNO_QUERY );
161 if( xText.is() )
163 OUString aText( xText->getString() );
164 if( !aText.isEmpty() )
166 aDescription += ": ";
168 aText = aText.replace( '\n', ' ' );
169 aText = aText.replace( '\r', ' ' );
171 aDescription += aText;
175 return aDescription;
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);
188 if (xLockable.is())
189 xLockable->addActionLock();
190 comphelper::ScopeGuard aGuard([&xLockable]()
192 if (xLockable.is())
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();
203 nPara--;
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();
217 else
219 Reference< XShape > xShape;
220 rTarget >>= xShape;
221 if( xShape.is() )
222 aDescription = getShapeDescription( xShape, bWithText );
225 return aDescription;
228 class CustomAnimationListEntryItem
230 public:
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);
240 private:
241 OUString msDescription;
242 OUString msEffectName;
243 CustomAnimationEffectPtr mpEffect;
245 public:
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))
254 if (!mpEffect)
255 return;
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;
266 default:
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);
290 if (!pItem)
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);
304 return aSize;
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();
346 if (bSelected)
347 rRenderContext.SetTextColor(rStyleSettings.GetHighlightTextColor());
348 else
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()));
382 OUString sImage;
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:
403 default:
404 sImage = BMP_CUSTOMANIMATION_MEDIA_PLAY; break;
406 break;
407 default:
408 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);
419 else
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)
434 if (mpEffect)
435 PaintEffect(rRenderContext, rRect, bSelected);
436 else
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)
449 , mnLastGroupId(0)
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);
485 return nAccept;
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));
504 return false;
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.
513 return false;
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;
524 return ret;
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)) :
542 nullptr;
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(
555 std::move(aEffects),
556 pTarget ? pTarget->getEffect() : nullptr );
558 // Reset selection
559 mxTreeView->select(*mxDndEffectDragging);
560 Select();
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;
582 if( mpMainSequence )
583 mpMainSequence->removeListener( this );
585 clear();
588 IMPL_LINK(CustomAnimationList, KeyInputHdl, const KeyEvent&, rKEvt, bool)
590 const int nKeyCode = rKEvt.GetKeyCode().GetCode();
591 switch (nKeyCode)
593 case KEY_DELETE:
594 mpController->onContextMenu(u"remove"_ustr);
595 return true;
596 case KEY_INSERT:
597 mpController->onContextMenu(u"create"_ustr);
598 return true;
599 case KEY_SPACE:
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);
607 CommandHdl(aCEvt);
608 return true;
612 return false;
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);
631 pEntry = pTestEntry;
632 break;
634 } while (mxTreeView->iter_next(*xEntry));
637 if( !pEntry )
639 append( pEffect );
640 select( pEffect );
644 void CustomAnimationList::clear()
646 mxEntries.clear();
647 mxTreeView->clear();
649 mxEmptyLabelParent->show();
650 mxTreeView->hide();
652 mxLastParentEntry.reset();
653 mxLastTargetShape = nullptr;
656 void CustomAnimationList::update( const MainSequencePtr& pMainSequence )
658 if( mpMainSequence )
659 mpMainSequence->removeListener( this );
661 mpMainSequence = pMainSequence;
662 update();
664 if( mpMainSequence )
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();
697 if( mpMainSequence )
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());
707 if (pEffect)
709 if (weld::IsEntryVisible(*mxTreeView, rEntry))
711 aVisible.push_back(pEffect);
712 // save scroll position
713 if (nFirstVis == -1)
714 nFirstVis = weld::GetAbsPos(*mxTreeView, rEntry);
715 if (!xLastVisibleEntry)
716 xLastVisibleEntry = mxTreeView->make_iterator(&rEntry);
717 else
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);
731 else
732 mxTreeView->copy_iterator(rEntry, *xLastSelectedEntry);
736 return false;
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();
756 // rebuild list
758 mxTreeView->freeze();
760 clear();
762 if (mpMainSequence)
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() );
772 if( xShape.is() )
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();
787 mxTreeView->thaw();
789 if (mxTreeView->n_children())
791 mxEmptyLabelParent->hide();
792 mxTreeView->show();
795 if (mpMainSequence)
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() );
810 if (pEffect)
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));
861 else
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;
889 Select();
892 void CustomAnimationList::append( CustomAnimationEffectPtr pEffect )
894 Any aTarget( pEffect->getTarget() );
895 if( !aTarget.hasValue() )
896 return;
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()));
920 if (xParentEntry)
922 // add a subentry
923 mxTreeView->insert(xParentEntry.get(), -1, &aDescription, &sId, nullptr, nullptr, false, xEntry.get());
925 else
927 // add a root entry
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))
946 return;
948 bool bFirstEntry = true;
952 CustomAnimationListEntryItem* pEntry = weld::fromId<CustomAnimationListEntryItem*>(pTreeList->get_id(*xEntry));
953 CustomAnimationEffectPtr pEffect(pEntry->getEffect());
954 if (pEffect)
956 if (pEffect->getTarget() == xShape)
958 pTreeList->select(*xEntry);
959 if (bFirstEntry)
961 pTreeList->scroll_to_row(*xEntry);
962 bFirstEntry = false;
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);
978 if( xShapes.is() )
980 sal_Int32 nCount = xShapes->getCount();
981 sal_Int32 nIndex;
982 for( nIndex = 0; nIndex < nCount; nIndex++ )
984 Reference< XShape > xShape( xShapes->getByIndex( nIndex ), UNO_QUERY );
985 if( xShape.is() )
986 selectShape(mxTreeView.get(), xShape);
989 else
991 Reference< XShape > xShape(rSelection, UNO_QUERY);
992 if( xShape.is() )
993 selectShape(mxTreeView.get(), xShape);
997 Select();
999 catch( Exception& )
1001 TOOLS_WARN_EXCEPTION( "sd", "sd::CustomAnimationList::onSelectionChanged()" );
1005 IMPL_LINK_NOARG(CustomAnimationList, SelectHdl, weld::TreeView&, void)
1007 Select();
1010 // Notify controller to refresh UI when we are notified of selection change from base class
1011 void CustomAnimationList::Select()
1013 if( mbIgnorePaint )
1014 return;
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));
1043 return true;
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));
1075 return false;
1078 mnPostCollapseEvent = Application::PostUserEvent(LINK(this, CustomAnimationList, PostCollapseHdl));
1081 // Execute collapse on base class
1082 return true;
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);
1104 break;
1106 } while (mxTreeView->iter_next(*xEntry));
1109 return bExpanded;
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));
1124 return true;
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());
1134 if (pEffect)
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() );
1150 if( pChildEffect )
1151 aSelection.push_back( pChildEffect );
1153 } while (mxTreeView->iter_next_sibling(*xChild));
1156 return false;
1159 return aSelection;
1162 IMPL_LINK_NOARG(CustomAnimationList, DoubleClickHdl, weld::TreeView&, bool)
1164 mpController->onDoubleClick();
1165 return false;
1168 IMPL_LINK(CustomAnimationList, CommandHdl, const CommandEvent&, rCEvt, bool)
1170 if (rCEvt.GetCommand() != CommandEventId::ContextMenu)
1171 return false;
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))
1187 return false;
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());
1199 nEntries++;
1200 if (pEffect)
1202 if( nNodeType == -1 )
1204 nNodeType = pEffect->getNodeType();
1206 else
1208 if( nNodeType != pEffect->getNodeType() )
1210 nNodeType = -1;
1211 return true;
1216 return false;
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);
1229 return true;
1232 void CustomAnimationList::ExecuteContextMenuAction(const OUString& rIdent)
1234 mpController->onContextMenu(rIdent);
1237 void CustomAnimationList::notify_change()
1239 update();
1240 mpController->onSelect();
1245 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */