diff options
author | Brian Fraser <andthebrain@softfrog.ca> | 2018-10-25 00:37:09 -0700 |
---|---|---|
committer | Thorsten Behrens <Thorsten.Behrens@CIB.de> | 2018-11-16 01:37:31 +0100 |
commit | 759ee18f4cbadd1f4fbdccbc599c04a183066788 (patch) | |
tree | 8dca23ceb82969236388e59ab52f5a5f36f41b98 /sd | |
parent | 525ed5d1fcb89412f0b80be0b1e35410b048c337 (diff) |
tdf#37483 Add drag'n'drop to reorder custom Impress animations
- Support drag'n'drop of single items. If the animation is collapsed,
it will move its collapsed sub-animations as well.
- Sub-lists remain expanded when its parent animation is moved, or
a sub animation becomes the new parent of the text group (due to moving).
- Maintain the cursor between custom animation list redraws.
- Don't change selection on collapse after custom animation list
rebuild.
Change-Id: I92d96e9a01c6884ef739612e456cc541218b6ebb
Reviewed-on: https://gerrit.libreoffice.org/62342
Reviewed-by: Thorsten Behrens <Thorsten.Behrens@CIB.de>
Tested-by: Thorsten Behrens <Thorsten.Behrens@CIB.de>
Diffstat (limited to 'sd')
-rw-r--r-- | sd/inc/CustomAnimationEffect.hxx | 1 | ||||
-rw-r--r-- | sd/source/core/CustomAnimationEffect.cxx | 14 | ||||
-rw-r--r-- | sd/source/ui/animations/CustomAnimationList.cxx | 212 | ||||
-rw-r--r-- | sd/source/ui/animations/CustomAnimationList.hxx | 16 | ||||
-rw-r--r-- | sd/source/ui/animations/CustomAnimationPane.cxx | 33 | ||||
-rw-r--r-- | sd/source/ui/animations/CustomAnimationPane.hxx | 1 |
6 files changed, 269 insertions, 8 deletions
diff --git a/sd/inc/CustomAnimationEffect.hxx b/sd/inc/CustomAnimationEffect.hxx index b70bbdd07f91..0319e9ea8669 100644 --- a/sd/inc/CustomAnimationEffect.hxx +++ b/sd/inc/CustomAnimationEffect.hxx @@ -277,6 +277,7 @@ public: SAL_DLLPRIVATE void replace( const CustomAnimationEffectPtr& pEffect, const CustomAnimationPresetPtr& pDescriptor, double fDuration ); SAL_DLLPRIVATE void replace( const CustomAnimationEffectPtr& pEffect, const CustomAnimationPresetPtr& pDescriptor, const OUString& rPresetSubType, double fDuration ); SAL_DLLPRIVATE void remove( const CustomAnimationEffectPtr& pEffect ); + SAL_DLLPRIVATE void moveToBeforeEffect( const CustomAnimationEffectPtr& pEffect, const CustomAnimationEffectPtr& pInsertBefore); SAL_DLLPRIVATE void create( const css::uno::Reference< css::animations::XAnimationNode >& xNode ); SAL_DLLPRIVATE void createEffectsequence( const css::uno::Reference< css::animations::XAnimationNode >& xNode ); diff --git a/sd/source/core/CustomAnimationEffect.cxx b/sd/source/core/CustomAnimationEffect.cxx index d695ef8c2dcc..230f2811db8c 100644 --- a/sd/source/core/CustomAnimationEffect.cxx +++ b/sd/source/core/CustomAnimationEffect.cxx @@ -1790,6 +1790,20 @@ void EffectSequenceHelper::remove( const CustomAnimationEffectPtr& pEffect ) rebuild(); } +void EffectSequenceHelper::moveToBeforeEffect( const CustomAnimationEffectPtr& pEffect, const CustomAnimationEffectPtr& pInsertBefore) +{ + if ( pEffect.get() ) + { + maEffects.remove( pEffect ); + EffectSequence::iterator aInsertIter( find( pInsertBefore ) ); + + // aInsertIter being end() is OK: pInsertBefore could be null, so put at end. + maEffects.insert( aInsertIter, pEffect ); + + rebuild(); + } +} + void EffectSequenceHelper::rebuild() { implRebuild(); diff --git a/sd/source/ui/animations/CustomAnimationList.cxx b/sd/source/ui/animations/CustomAnimationList.cxx index 909ea4d21bdf..0bc6f9f119be 100644 --- a/sd/source/ui/animations/CustomAnimationList.cxx +++ b/sd/source/ui/animations/CustomAnimationList.cxx @@ -440,11 +440,180 @@ CustomAnimationList::CustomAnimationList( vcl::Window* pParent ) , mpController(nullptr) , mnLastGroupId(0) , mpLastParentEntry(nullptr) + , mpDndEffectDragging(nullptr) + , mpDndEffectInsertBefore(nullptr) { EnableContextMenuHandling(); SetSelectionMode( SelectionMode::Multiple ); SetOptimalImageIndent(); SetNodeDefaultImages(); + + SetDragDropMode(DragDropMode::CTRL_MOVE); +} + +// D'n'D #1: Prepare selected element for moving. +DragDropMode CustomAnimationList::NotifyStartDrag( TransferDataContainer& /*rData*/, SvTreeListEntry* pEntry ) +{ + mpDndEffectDragging = pEntry; + mpDndEffectInsertBefore = pEntry; + + return DragDropMode::CTRL_MOVE; +} + +// D'n'D #2: Called each time mouse moves during drag +sal_Int8 CustomAnimationList::AcceptDrop( const AcceptDropEvent& rEvt ) +{ + /* + Don't call SvTreeListBox::AcceptDrop because it puts an unnecessary + highlight via ImplShowTargetEmphasis() + */ + + sal_Int8 ret = DND_ACTION_NONE; + const bool bIsMove = ( DND_ACTION_MOVE == rEvt.mnAction ); + + if( mpDndEffectDragging && !rEvt.mbLeaving && bIsMove ) + { + SvTreeListEntry* pEntry = GetDropTarget( rEvt.maPosPixel ); + + const bool bNotOverSelf = ( pEntry != mpDndEffectDragging ); + if( pEntry && bNotOverSelf ) + { + /* + If dragged effect has visible children then we must re-parent the children + first so that they are not dragged with the parent. Re-parenting (only in the UI!) + dragged effect's first child to the root, and the remaining children to 1st child. + */ + if( GetVisibleChildCount( mpDndEffectDragging ) > 0 ) + { + SvTreeListEntry* pFirstChild = FirstChild( mpDndEffectDragging ); + SvTreeListEntry* pEntryParent = GetParent( mpDndEffectDragging ); + sal_uLong nInsertAfterPos = SvTreeList::GetRelPos( mpDndEffectDragging ) + 1; + + // Re-parent 1st child to root, below all the other children. + pModel->Move( pFirstChild, pEntryParent, nInsertAfterPos ); + + // Re-parent children after 1st child to the first child + sal_uLong nInsertNextChildPos = 0; + while( FirstChild( mpDndEffectDragging ) ) + { + SvTreeListEntry* pNextChild = FirstChild( mpDndEffectDragging ); + ++nInsertNextChildPos; + pModel->Move( pNextChild, pFirstChild, nInsertNextChildPos ); + } + + // Expand all children (they were previously visible) + Expand( pFirstChild ); + } + + ReorderEffectsInUiDuringDragOver( pEntry ); + } + + // Return DND_ACTION_MOVE on internal drag'n'drops so that ExecuteDrop() is called. + // Return MOVE even if we are over othe dragged effect because dragged effect moves. + ret = DND_ACTION_MOVE; + } + + return ret; +} + +// D'n'D: Update UI to show where dragged event will appear if dropped now. +void CustomAnimationList::ReorderEffectsInUiDuringDragOver( SvTreeListEntry* pOverEntry ) +{ + /* + Update the order of effects on *just the UI* as the user drags. + The model (MainSequence) will only be changed after the user drops + the effect because this triggers a rebuild of the list which removes + and recreates all effects (on a background timer). Hence, this would + invalidate the pointer for the entry currently being dragged. + Plus, reordering the model during drag would have to reverse any model changes + if the drag were canceled, and ensure only one Undo record created per successful drag. + */ + + // Compute new location in *UI* + SvTreeListEntry* pNewParent = nullptr; + sal_uLong nInsertAfterPos = 0; + + Point aPosOverEffect( GetEntryPosition(pOverEntry) ); + Point aPosDraggedEffect( GetEntryPosition(mpDndEffectDragging) ); + const bool bDraggingUp = (aPosDraggedEffect.Y() - aPosOverEffect.Y()) > 0; + + if( bDraggingUp ) + { + // Drag up --> place above the element we are over + pNewParent = GetParent( pOverEntry ); + nInsertAfterPos = SvTreeList::GetRelPos( pOverEntry ); + mpDndEffectInsertBefore = pOverEntry; + } + else + { + // Drag down --> place below the element we are over + SvTreeListEntry* pNextVisBelowTarget = NextVisible( pOverEntry ); + if( pNextVisBelowTarget ) + { + // Match parent of NEXT visible effect (works for sub-items too) + pNewParent = GetParent( pNextVisBelowTarget ); + nInsertAfterPos = SvTreeList::GetRelPos( pNextVisBelowTarget ); + mpDndEffectInsertBefore = pNextVisBelowTarget; + } + else + { + // Over the last element: no next to work with + pNewParent = GetParent( pOverEntry ); + nInsertAfterPos = SvTreeList::GetRelPos( pOverEntry ) + 1; + mpDndEffectInsertBefore = nullptr; + } + } + + // Update *just* the UI to show where dragged element would currently be if dropped. + pModel->Move( mpDndEffectDragging, pNewParent, nInsertAfterPos ); + + // Restore selection + Select( mpDndEffectDragging ); +} + +// D'n'D #4: Tell model to update effect order. +sal_Int8 CustomAnimationList::ExecuteDrop( const ExecuteDropEvent& /*rEvt*/ ) +{ + // NOTE: We cannot just override NotifyMoving() because it's not called + // since we dynamically reorder effects during drag. + + sal_Int8 ret = DND_ACTION_NONE; + + const bool bMovingEffect = ( mpDndEffectDragging != nullptr ); + const bool bMoveNotSelf = ( mpDndEffectInsertBefore != mpDndEffectDragging ); + const bool bHaveSequence = ( mpMainSequence.get() != nullptr ); + + if( bMovingEffect && bMoveNotSelf && bHaveSequence ) + { + CustomAnimationListEntry* pEntryMoved = static_cast< CustomAnimationListEntry* >( mpDndEffectDragging ); + CustomAnimationListEntry* pTarget = static_cast< CustomAnimationListEntry* >( mpDndEffectInsertBefore ); + + // Callback to observer to have it update the model. + // If pTarget is null, pass nullptr to indicate end of list. + mpController->onDragNDropComplete( + pEntryMoved->getEffect(), + pTarget ? pTarget->getEffect() : nullptr ); + + // Reset selection + Select( mpDndEffectDragging ); + + ret = DND_ACTION_MOVE; + } + + return ret; +} + +// D'n'D #5: Cleanup (regardless of if we were target of drop or not) +void CustomAnimationList::DragFinished( sal_Int8 nDropAction ) +{ + mpDndEffectDragging = nullptr; + mpDndEffectInsertBefore = nullptr; + + // Rebuild because we may have re-parented the dragged effect's first child. + // Can hit this without running ExecuteDrop(...) when drag canceled. + mpMainSequence->rebuild(); + + SvTreeListBox::DragFinished( nDropAction ); } VCL_BUILDER_FACTORY(CustomAnimationList) @@ -553,8 +722,9 @@ void CustomAnimationList::update() CustomAnimationListEntry* pEntry = nullptr; - std::vector< CustomAnimationEffectPtr > aExpanded; + std::vector< CustomAnimationEffectPtr > aVisible; std::vector< CustomAnimationEffectPtr > aSelected; + CustomAnimationEffectPtr aCurrent; CustomAnimationEffectPtr pFirstSelEffect; CustomAnimationEffectPtr pLastSelEffect; @@ -588,7 +758,7 @@ void CustomAnimationList::update() nLastSelOld = GetAbsPos( pEntry ); } - // save selection and expand states + // save selection, current, and expand (visible) states pEntry = static_cast<CustomAnimationListEntry*>(First()); while( pEntry ) @@ -596,8 +766,8 @@ void CustomAnimationList::update() CustomAnimationEffectPtr pEffect( pEntry->getEffect() ); if( pEffect.get() ) { - if( IsExpanded( pEntry ) ) - aExpanded.push_back( pEffect ); + if( IsEntryVisible( pEntry ) ) + aVisible.push_back( pEffect ); if( IsSelected( pEntry ) ) aSelected.push_back( pEffect ); @@ -605,6 +775,10 @@ void CustomAnimationList::update() pEntry = static_cast<CustomAnimationListEntry*>(Next( pEntry )); } + + pEntry = static_cast<CustomAnimationListEntry*>(GetCurEntry()); + if( pEntry ) + aCurrent = pEntry->getEffect(); } // rebuild list @@ -639,7 +813,7 @@ void CustomAnimationList::update() } } - // restore selection and expand states + // restore selection state, expand state, and current-entry (under cursor) pEntry = static_cast<CustomAnimationListEntry*>(First()); while( pEntry ) @@ -647,12 +821,21 @@ void CustomAnimationList::update() CustomAnimationEffectPtr pEffect( pEntry->getEffect() ); if( pEffect.get() ) { - if( std::find( aExpanded.begin(), aExpanded.end(), pEffect ) != aExpanded.end() ) - Expand( pEntry ); + // 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( GetParent(pEntry) ) + Expand( GetParent(pEntry) ); + } if( std::find( aSelected.begin(), aSelected.end(), pEffect ) != aSelected.end() ) Select( pEntry ); + // Restore the cursor; don't use SetCurEntry() as it may deselect other effects + if( pEffect == aCurrent ) + SetCursor( pEntry ); + if( pEffect == pFirstSelEffect ) nFirstSelNew = GetAbsPos( pEntry ); @@ -835,6 +1018,21 @@ bool CustomAnimationList::isExpanded( const CustomAnimationEffectPtr& pEffect ) return (pEntry == nullptr) || IsExpanded( pEntry ); } +bool CustomAnimationList::isVisible( const CustomAnimationEffectPtr& pEffect ) const +{ + CustomAnimationListEntry* pEntry = static_cast<CustomAnimationListEntry*>(First()); + + while( pEntry ) + { + if( pEntry->getEffect() == pEffect ) + break; + + pEntry = static_cast<CustomAnimationListEntry*>(Next( pEntry )); + } + + return (pEntry == nullptr) || IsEntryVisible( pEntry ); +} + EffectSequence CustomAnimationList::getSelection() const { EffectSequence aSelection; diff --git a/sd/source/ui/animations/CustomAnimationList.hxx b/sd/source/ui/animations/CustomAnimationList.hxx index 2040e7f4f6eb..7252bea16ce6 100644 --- a/sd/source/ui/animations/CustomAnimationList.hxx +++ b/sd/source/ui/animations/CustomAnimationList.hxx @@ -40,6 +40,7 @@ public: virtual void onSelect() = 0; virtual void onDoubleClick() = 0; virtual void onContextMenu(const OString &rIdent) = 0; + virtual void onDragNDropComplete( CustomAnimationEffectPtr pEffectDragged, CustomAnimationEffectPtr pEffectInsertBefore ) = 0; virtual ~ICustomAnimationListController() {} }; @@ -83,8 +84,9 @@ public: virtual void notify_change() override; bool isExpanded( const CustomAnimationEffectPtr& pEffect ) const; + bool isVisible( const CustomAnimationEffectPtr& pEffect ) const; - /// clears all entries from the listbox + // clears all entries from the listbox void clear(); void setController( ICustomAnimationListController* pController ) @@ -92,6 +94,15 @@ public: mpController = pController; }; + +protected: + // drag & drop + virtual DragDropMode NotifyStartDrag( TransferDataContainer& rData, SvTreeListEntry* pEntry ) override; + virtual sal_Int8 AcceptDrop( const AcceptDropEvent& rEvt ) override; + virtual void ReorderEffectsInUiDuringDragOver( SvTreeListEntry* pOverEntry); + virtual sal_Int8 ExecuteDrop( const ExecuteDropEvent& rEvt ) override; + virtual void DragFinished( sal_Int8 nDropAction ) override; + private: std::unique_ptr<VclBuilder> mxBuilder; VclPtr<PopupMenu> mxMenu; @@ -109,6 +120,9 @@ private: sal_Int32 mnLastGroupId; SvTreeListEntry* mpLastParentEntry; + // drag & drop + SvTreeListEntry* mpDndEffectDragging; + SvTreeListEntry* mpDndEffectInsertBefore; }; OUString getPropertyName( sal_Int32 nPropertyType ); diff --git a/sd/source/ui/animations/CustomAnimationPane.cxx b/sd/source/ui/animations/CustomAnimationPane.cxx index 1e04f66d8b6a..ab695076ddc2 100644 --- a/sd/source/ui/animations/CustomAnimationPane.cxx +++ b/sd/source/ui/animations/CustomAnimationPane.cxx @@ -2509,6 +2509,39 @@ void CustomAnimationPane::onSelect() } } +// ICustomAnimationListController +// pEffectInsertBefore may be null if moving to end of list. +void CustomAnimationPane::onDragNDropComplete(CustomAnimationEffectPtr pEffectDragged, CustomAnimationEffectPtr pEffectInsertBefore) +{ + if ( mpMainSequence.get() ) + { + addUndo(); + + MainSequenceRebuildGuard aGuard( mpMainSequence ); + + // Move the dragged effect and any hidden sub-effects + EffectSequence::iterator aIter = mpMainSequence->find( pEffectDragged ); + const EffectSequence::iterator aEnd( mpMainSequence->getEnd() ); + + while( aIter != aEnd ) + { + CustomAnimationEffectPtr pEffect = (*aIter++); + + // Update model with new location (function triggers a rebuild) + // target may be null, which will insert at the end. + mpMainSequence->moveToBeforeEffect( pEffect, pEffectInsertBefore ); + + // Done moving effect and its hidden sub-effects when *next* effect is visible. + if ( mpCustomAnimationList->isVisible( *aIter ) ) + break; + } + + updateControls(); + mrBase.GetDocShell()->SetModified(); + } +} + + void CustomAnimationPane::updatePathFromMotionPathTag( const rtl::Reference< MotionPathTag >& xTag ) { MainSequenceRebuildGuard aGuard( mpMainSequence ); diff --git a/sd/source/ui/animations/CustomAnimationPane.hxx b/sd/source/ui/animations/CustomAnimationPane.hxx index f13b0cb7c0a2..596d2b53bde2 100644 --- a/sd/source/ui/animations/CustomAnimationPane.hxx +++ b/sd/source/ui/animations/CustomAnimationPane.hxx @@ -88,6 +88,7 @@ public: virtual void onSelect() override; virtual void onDoubleClick() override; virtual void onContextMenu(const OString& rIdent) override; + virtual void onDragNDropComplete( CustomAnimationEffectPtr pEffectDragged, CustomAnimationEffectPtr pEffectInsertBefore ) override; // Window virtual void DataChanged (const DataChangedEvent& rEvent) override; |