summaryrefslogtreecommitdiff
path: root/sd
diff options
context:
space:
mode:
authorBrian Fraser <andthebrain@softfrog.ca>2018-10-25 00:37:09 -0700
committerThorsten Behrens <Thorsten.Behrens@CIB.de>2018-11-16 01:37:31 +0100
commit759ee18f4cbadd1f4fbdccbc599c04a183066788 (patch)
tree8dca23ceb82969236388e59ab52f5a5f36f41b98 /sd
parent525ed5d1fcb89412f0b80be0b1e35410b048c337 (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.hxx1
-rw-r--r--sd/source/core/CustomAnimationEffect.cxx14
-rw-r--r--sd/source/ui/animations/CustomAnimationList.cxx212
-rw-r--r--sd/source/ui/animations/CustomAnimationList.hxx16
-rw-r--r--sd/source/ui/animations/CustomAnimationPane.cxx33
-rw-r--r--sd/source/ui/animations/CustomAnimationPane.hxx1
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;