summaryrefslogtreecommitdiff
path: root/sd
diff options
context:
space:
mode:
authorBrian Fraser <andthebrain@softfrog.ca>2018-11-27 13:03:14 -0800
committerJim Raykowski <raykowj@gmail.com>2018-12-11 19:03:04 +0100
commit61323f3594909e3fbff858d93d97e5c86d9e3408 (patch)
tree9dd204c1395be0f888ec9edae9aa1bc6f3fbd42d /sd
parent7c35d5f0669f461254668c1854291e1324b37c21 (diff)
Impress: Support drag'n'drop of multiple effects in animation pane
Change-Id: I004b2738929e9755dba03c8cab5cb2e2ccae707d Reviewed-on: https://gerrit.libreoffice.org/64129 Tested-by: Jenkins Reviewed-by: Jim Raykowski <raykowj@gmail.com>
Diffstat (limited to 'sd')
-rw-r--r--sd/source/ui/animations/CustomAnimationList.cxx188
-rw-r--r--sd/source/ui/animations/CustomAnimationList.hxx5
-rw-r--r--sd/source/ui/animations/CustomAnimationPane.cxx32
-rw-r--r--sd/source/ui/animations/CustomAnimationPane.hxx2
4 files changed, 167 insertions, 60 deletions
diff --git a/sd/source/ui/animations/CustomAnimationList.cxx b/sd/source/ui/animations/CustomAnimationList.cxx
index 9019a412482b..19513ab58193 100644
--- a/sd/source/ui/animations/CustomAnimationList.cxx
+++ b/sd/source/ui/animations/CustomAnimationList.cxx
@@ -452,16 +452,41 @@ CustomAnimationList::CustomAnimationList( vcl::Window* pParent )
SetDragDropMode(DragDropMode::CTRL_MOVE);
}
-// D'n'D #1: Prepare selected element for moving.
+// D'n'D #1: Record selected effects for drag'n'drop.
+void CustomAnimationList::StartDrag( sal_Int8 nAction, const Point& rPosPixel )
+{
+ // Record which effects are selected:
+ // Since NextSelected(..) iterates through the selected items in the order they
+ // were selected, create a sorted list for simpler drag'n'drop algorithms.
+ mDndEffectsSelected.clear();
+ for( SvTreeListEntry* pEntry = First(); pEntry; pEntry = Next(pEntry) )
+ {
+ if( IsSelected(pEntry) )
+ {
+ mDndEffectsSelected.push_back( pEntry );
+ }
+ }
+
+ // Allow normal proccessing; this calls our NotifyStartDrag().
+ SvTreeListBox::StartDrag( nAction, rPosPixel );
+}
+
+// D'n'D #2: Prepare selected element for moving.
DragDropMode CustomAnimationList::NotifyStartDrag( TransferDataContainer& /*rData*/, SvTreeListEntry* pEntry )
{
+ // Restore selection for multiple selected effects.
+ // Do it here to remove a flicker on the UI with effects being unselected and reselected.
+ for( auto &pEffect : mDndEffectsSelected )
+ SelectListEntry( pEffect, true);
+
+ // Note: pEntry is the effect with focus (if multiple effects are selected)
mpDndEffectDragging = pEntry;
mpDndEffectInsertBefore = pEntry;
return DragDropMode::CTRL_MOVE;
}
-// D'n'D #2: Called each time mouse moves during drag
+// D'n'D #3: Called each time mouse moves during drag
sal_Int8 CustomAnimationList::AcceptDrop( const AcceptDropEvent& rEvt )
{
/*
@@ -470,41 +495,17 @@ sal_Int8 CustomAnimationList::AcceptDrop( const AcceptDropEvent& rEvt )
*/
sal_Int8 ret = DND_ACTION_NONE;
- const bool bIsMove = ( DND_ACTION_MOVE == rEvt.mnAction );
+ 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 )
+ const bool bOverASelectedEffect =
+ std::find( mDndEffectsSelected.begin(), mDndEffectsSelected.end(), pEntry ) != mDndEffectsSelected.end();
+ if( pEntry && !bOverASelectedEffect )
{
- /*
- 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 );
- }
+ ReparentChildrenDuringDrag();
ReorderEffectsInUiDuringDragOver( pEntry );
}
@@ -517,17 +518,74 @@ sal_Int8 CustomAnimationList::AcceptDrop( const AcceptDropEvent& rEvt )
return ret;
}
+// D'n'D: For each dragged effect, re-parent (only in the UI) non-selected
+// visible children so they are not dragged with the parent.
+void CustomAnimationList::ReparentChildrenDuringDrag()
+{
+ /*
+ Re-parent (only in the UI!):
+ a) the dragged effect's first non-selected child to the root, and
+ b) the remaining non-selected children to that re-parented 1st child.
+ */
+ for( auto &pEffect : mDndEffectsSelected )
+ {
+ const bool bExpandedWithChildren = GetVisibleChildCount( pEffect ) > 0;
+ if( bExpandedWithChildren )
+ {
+ SvTreeListEntry* pEntryParent = GetParent( pEffect );
+
+ SvTreeListEntry* pFirstNonSelectedChild = nullptr;
+ sal_uLong nInsertNextChildPos = 0;
+
+ // Process all children of this effect
+ SvTreeListEntry* pChild = FirstChild( pEffect );
+ while( pChild && ( GetParent( pChild ) == pEffect ) )
+ {
+ // Start by finding next child because if pChild moves, we cannot then
+ // ask it what the next child is because it's no longer with its siblings.
+ SvTreeListEntry* pNextChild = Next( pChild );
+
+ // Skip selected effects: they stay with their previous parent to be moved.
+ // During drag, the IsSelected() set changes, so use mDndEffectsSelected instead
+ const bool bIsSelected = std::find( mDndEffectsSelected.begin(), mDndEffectsSelected.end(), pChild ) != mDndEffectsSelected.end();
+ if( !bIsSelected )
+ {
+ // Re-parent 1st non-selected child to root, below all the other children.
+ if( !pFirstNonSelectedChild )
+ {
+ pFirstNonSelectedChild = pChild;
+ sal_uLong nInsertAfterPos = SvTreeList::GetRelPos( pEffect ) + 1;
+ pModel->Move( pFirstNonSelectedChild, pEntryParent, nInsertAfterPos );
+ }
+ else
+ {
+ // Re-parent remaining non-selected children to 1st child
+ ++nInsertNextChildPos;
+ pModel->Move( pChild, pFirstNonSelectedChild, nInsertNextChildPos );
+ }
+ }
+
+ pChild = pNextChild;
+ }
+
+ // Expand all children (they were previously visible)
+ if( pFirstNonSelectedChild )
+ Expand( pFirstNonSelectedChild );
+
+ }
+ }
+}
+
// 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.
+ Update the order of effects in *just the UI* while the user is dragging.
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.
+ the effect so that there is minimal work to do if the drag is canceled.
+ Plus only one undo record should be created per drag, and changing
+ the model recreates all effects (on a background timer) which invalidates
+ all effect pointers.
*/
// Compute new location in *UI*
@@ -565,14 +623,46 @@ void CustomAnimationList::ReorderEffectsInUiDuringDragOver( SvTreeListEntry* pOv
}
}
- // Update *just* the UI to show where dragged element would currently be if dropped.
- pModel->Move( mpDndEffectDragging, pNewParent, nInsertAfterPos );
+ // Move each selected effect in *just* the UI to show where it would be if dropped.
+ // This leaves the exist parent relationships in the non-dragged elements so that
+ // the list does not seem to change structure during drag. Parent relationships will
+ // be correctly recreated on drop.
+ for( auto aItr = mDndEffectsSelected.rbegin();
+ aItr != mDndEffectsSelected.rend();
+ ++aItr)
+ {
+ SvTreeListEntry* pEffect = *aItr;
+
+ // Move only effects whose parents is not selected because
+ // they will automatically move when their parent is moved.
+ const bool bParentIsSelected =
+ std::find(mDndEffectsSelected.begin(), mDndEffectsSelected.end(), GetParent(pEffect)) != mDndEffectsSelected.end();
- // Restore selection
- Select( mpDndEffectDragging );
+ if( !bParentIsSelected )
+ {
+ // If the current effect is being moved down, the insert position must be decremented
+ // after move if it will have the same parent as it currently does because it moves
+ // from above the insertion point to below it, hence changing its index.
+ // Must decide move-up vs move-down for each effect being dragged because we may be
+ // processing a discontinuous set of selected effects (some below, some above insertion point)
+ Point aCurPosOverEffect( GetEntryPosition( pOverEntry ) );
+ Point aCurPosMovedEffect( GetEntryPosition( pEffect ) );
+ const bool bCurDraggingDown = ( aCurPosMovedEffect.Y() - aCurPosOverEffect.Y() ) < 0;
+ const bool bWillHaveSameParent = ( pNewParent == GetParent(pEffect) );
+
+ pModel->Move( pEffect, pNewParent, nInsertAfterPos );
+
+ if( bCurDraggingDown && bWillHaveSameParent )
+ --nInsertAfterPos;
+ }
+ }
+
+ // Restore selection (calling Select() is slow; SelectListEntry() is faster)
+ for( auto &pEffect : mDndEffectsSelected )
+ SelectListEntry( pEffect, true);
}
-// D'n'D #4: Tell model to update effect order.
+// D'n'D #5: Tell model to update effect order.
sal_Int8 CustomAnimationList::ExecuteDrop( const ExecuteDropEvent& /*rEvt*/ )
{
// NOTE: We cannot just override NotifyMoving() because it's not called
@@ -586,13 +676,20 @@ sal_Int8 CustomAnimationList::ExecuteDrop( const ExecuteDropEvent& /*rEvt*/ )
if( bMovingEffect && bMoveNotSelf && bHaveSequence )
{
- CustomAnimationListEntry* pEntryMoved = static_cast< CustomAnimationListEntry* >( mpDndEffectDragging );
CustomAnimationListEntry* pTarget = static_cast< CustomAnimationListEntry* >( mpDndEffectInsertBefore );
+ // Build list of effects
+ std::vector< CustomAnimationEffectPtr > aEffects;
+ for( auto &pEntry : mDndEffectsSelected )
+ {
+ CustomAnimationListEntry* pCustomAnimationEffect = static_cast< CustomAnimationListEntry* >( pEntry );
+ aEffects.push_back( pCustomAnimationEffect->getEffect() );
+ }
+
// Callback to observer to have it update the model.
// If pTarget is null, pass nullptr to indicate end of list.
mpController->onDragNDropComplete(
- pEntryMoved->getEffect(),
+ aEffects,
pTarget ? pTarget->getEffect() : nullptr );
// Reset selection
@@ -604,11 +701,12 @@ sal_Int8 CustomAnimationList::ExecuteDrop( const ExecuteDropEvent& /*rEvt*/ )
return ret;
}
-// D'n'D #5: Cleanup (regardless of if we were target of drop or not)
+// D'n'D #6: Cleanup (regardless of if we were target of drop or not)
void CustomAnimationList::DragFinished( sal_Int8 nDropAction )
{
mpDndEffectDragging = nullptr;
mpDndEffectInsertBefore = nullptr;
+ mDndEffectsSelected.clear();
// Rebuild because we may have re-parented the dragged effect's first child.
// Can hit this without running ExecuteDrop(...) when drag canceled.
diff --git a/sd/source/ui/animations/CustomAnimationList.hxx b/sd/source/ui/animations/CustomAnimationList.hxx
index d06b04d4c989..e72f427a367e 100644
--- a/sd/source/ui/animations/CustomAnimationList.hxx
+++ b/sd/source/ui/animations/CustomAnimationList.hxx
@@ -40,7 +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 void onDragNDropComplete( std::vector< CustomAnimationEffectPtr > pEffectsDragged, CustomAnimationEffectPtr pEffectInsertBefore ) = 0;
virtual ~ICustomAnimationListController() {}
};
@@ -97,8 +97,10 @@ public:
protected:
// drag & drop
+ virtual void StartDrag( sal_Int8 nAction, const Point& rPosPixel ) override;
virtual DragDropMode NotifyStartDrag( TransferDataContainer& rData, SvTreeListEntry* pEntry ) override;
virtual sal_Int8 AcceptDrop( const AcceptDropEvent& rEvt ) override;
+ void ReparentChildrenDuringDrag();
void ReorderEffectsInUiDuringDragOver( SvTreeListEntry* pOverEntry);
virtual sal_Int8 ExecuteDrop( const ExecuteDropEvent& rEvt ) override;
virtual void DragFinished( sal_Int8 nDropAction ) override;
@@ -123,6 +125,7 @@ private:
// drag & drop
SvTreeListEntry* mpDndEffectDragging;
SvTreeListEntry* mpDndEffectInsertBefore;
+ std::vector< SvTreeListEntry* > mDndEffectsSelected;
};
OUString getPropertyName( sal_Int32 nPropertyType );
diff --git a/sd/source/ui/animations/CustomAnimationPane.cxx b/sd/source/ui/animations/CustomAnimationPane.cxx
index aee762153871..fedff46553e8 100644
--- a/sd/source/ui/animations/CustomAnimationPane.cxx
+++ b/sd/source/ui/animations/CustomAnimationPane.cxx
@@ -2510,7 +2510,7 @@ void CustomAnimationPane::onSelect()
// ICustomAnimationListController
// pEffectInsertBefore may be null if moving to end of list.
-void CustomAnimationPane::onDragNDropComplete(CustomAnimationEffectPtr pEffectDragged, CustomAnimationEffectPtr pEffectInsertBefore)
+void CustomAnimationPane::onDragNDropComplete(std::vector< CustomAnimationEffectPtr > pEffectsDragged, CustomAnimationEffectPtr pEffectInsertBefore)
{
if ( mpMainSequence.get() )
{
@@ -2518,23 +2518,29 @@ void CustomAnimationPane::onDragNDropComplete(CustomAnimationEffectPtr pEffectDr
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 )
+ // Move all selected effects
+ for( auto const& pEffectDragged : pEffectsDragged )
{
- CustomAnimationEffectPtr pEffect = *aIter++;
+ // Move this 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 );
+ // 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;
+ }
- // Done moving effect and its hidden sub-effects when *next* effect is visible.
- if ( mpCustomAnimationList->isVisible( *aIter ) )
- break;
}
+
updateControls();
mrBase.GetDocShell()->SetModified();
}
diff --git a/sd/source/ui/animations/CustomAnimationPane.hxx b/sd/source/ui/animations/CustomAnimationPane.hxx
index 596d2b53bde2..707ecf929155 100644
--- a/sd/source/ui/animations/CustomAnimationPane.hxx
+++ b/sd/source/ui/animations/CustomAnimationPane.hxx
@@ -88,7 +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;
+ virtual void onDragNDropComplete( std::vector< CustomAnimationEffectPtr > pEffectsDragged, CustomAnimationEffectPtr pEffectInsertBefore ) override;
// Window
virtual void DataChanged (const DataChangedEvent& rEvent) override;