[Libreoffice-commits] core.git: sd/inc sd/source

Libreoffice Gerrit user logerrit at kemper.freedesktop.org
Fri Nov 16 00:37:55 UTC 2018


 sd/inc/CustomAnimationEffect.hxx                |    1 
 sd/source/core/CustomAnimationEffect.cxx        |   14 +
 sd/source/ui/animations/CustomAnimationList.cxx |  212 +++++++++++++++++++++++-
 sd/source/ui/animations/CustomAnimationList.hxx |   16 +
 sd/source/ui/animations/CustomAnimationPane.cxx |   33 +++
 sd/source/ui/animations/CustomAnimationPane.hxx |    1 
 6 files changed, 269 insertions(+), 8 deletions(-)

New commits:
commit 759ee18f4cbadd1f4fbdccbc599c04a183066788
Author:     Brian Fraser <andthebrain at softfrog.ca>
AuthorDate: Thu Oct 25 00:37:09 2018 -0700
Commit:     Thorsten Behrens <Thorsten.Behrens at CIB.de>
CommitDate: Fri Nov 16 01:37:31 2018 +0100

    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 at CIB.de>
    Tested-by: Thorsten Behrens <Thorsten.Behrens at CIB.de>

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;


More information about the Libreoffice-commits mailing list