]> git.sesse.net Git - vlc/blobdiff - modules/gui/skins2/controls/ctrl_tree.cpp
skins2(win): fix mouse wheel ineffective on Windows
[vlc] / modules / gui / skins2 / controls / ctrl_tree.cpp
index 86863a6c75a1ca115bc5161bb5556664bb75c524..d395626afb1fef8d96a129f51566a91f42493aee 100644 (file)
@@ -1,10 +1,12 @@
 /*****************************************************************************
  * ctrl_tree.cpp
  *****************************************************************************
- * Copyright (C) 2003 VideoLAN
+ * Copyright (C) 2003 the VideoLAN team
  * $Id$
  *
  * Authors: Antoine Cellerier <dionoea@videolan.org>
+ *          ClĂ©ment Stenac <zorglub@videolan.org>
+ *          Erwan Tulou  <erwan10 At videolan DoT org>
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -22,6 +24,7 @@
  *****************************************************************************/
 
 #include <math.h>
+#include "../utils/var_bool.hpp"
 #include "ctrl_tree.hpp"
 #include "../src/os_factory.hpp"
 #include "../src/os_graphics.hpp"
 #include "../events/evt_key.hpp"
 #include "../events/evt_mouse.hpp"
 #include "../events/evt_scroll.hpp"
-#include "vlc_keys.h"
-#ifdef sun
-#   include "solaris_specific.h" // for lrint
-#endif
+#include "../events/evt_dragndrop.hpp"
+#include "../vars/playtree.hpp"
+#include <vlc_keys.h>
 
-#define SCROLL_STEP 0.05
 #define LINE_INTERVAL 1  // Number of pixels inserted between 2 lines
 
 
@@ -55,43 +56,43 @@ CtrlTree::CtrlTree( intf_thread_t *pIntf,
                     uint32_t bgColor2,
                     uint32_t selColor,
                     const UString &rHelp,
-                    VarBool *pVisible ):
+                    VarBool *pVisible,
+                    VarBool *pFlat ):
     CtrlGeneric( pIntf,rHelp, pVisible), m_rTree( rTree), m_rFont( rFont ),
     m_pBgBitmap( pBgBitmap ), m_pItemBitmap( pItemBitmap ),
     m_pOpenBitmap( pOpenBitmap ), m_pClosedBitmap( pClosedBitmap ),
-    m_fgColor( fgColor ), m_playColor( playColor ), m_bgColor1( bgColor1 ),
-    m_bgColor2( bgColor2 ), m_selColor( selColor ),
-    m_pLastSelected( NULL ), m_pImage( NULL ), m_dontMove( false )
+    m_pScaledBitmap( NULL ), m_pImage( NULL ),
+    m_fgColor( fgColor ), m_playColor( playColor ),
+    m_bgColor1( bgColor1 ), m_bgColor2( bgColor2 ), m_selColor( selColor ),
+    m_firstPos( m_rTree.end() ), m_lastClicked( m_rTree.end() ),
+    m_itOver( m_rTree.end() ), m_flat( pFlat->get() ), m_capacity( -1.0 ),
+    m_bRefreshOnDelete( false )
 {
-    // Observe the tree and position variables
+    // Observe the tree
     m_rTree.addObserver( this );
-    m_rTree.getPositionVar().addObserver( this );
-
-    m_firstPos = m_rTree.begin();
-
-    makeImage();
+    m_rTree.setFlat( m_flat );
 }
 
 CtrlTree::~CtrlTree()
 {
-    m_rTree.getPositionVar().delObserver( this );
     m_rTree.delObserver( this );
-    if( m_pImage )
-    {
-        delete m_pImage;
-    }
+    delete m_pImage;
+    delete m_pScaledBitmap;
 }
 
 int CtrlTree::itemHeight()
 {
     int itemHeight = m_rFont.getSize();
-    if( m_pClosedBitmap )
-    {
-        itemHeight = __MAX( m_pClosedBitmap->getHeight(), itemHeight );
-    }
-    if( m_pOpenBitmap )
+    if( !m_flat )
     {
-        itemHeight = __MAX( m_pOpenBitmap->getHeight(), itemHeight );
+        if( m_pClosedBitmap )
+        {
+            itemHeight = __MAX( m_pClosedBitmap->getHeight(), itemHeight );
+        }
+        if( m_pOpenBitmap )
+        {
+            itemHeight = __MAX( m_pOpenBitmap->getHeight(), itemHeight );
+        }
     }
     if( m_pItemBitmap )
     {
@@ -104,13 +105,16 @@ int CtrlTree::itemHeight()
 int CtrlTree::itemImageWidth()
 {
     int bitmapWidth = 5;
-    if( m_pClosedBitmap )
-    {
-        bitmapWidth = __MAX( m_pClosedBitmap->getWidth(), bitmapWidth );
-    }
-    if( m_pOpenBitmap )
+    if( !m_flat )
     {
-        bitmapWidth = __MAX( m_pOpenBitmap->getWidth(), bitmapWidth );
+        if( m_pClosedBitmap )
+        {
+            bitmapWidth = __MAX( m_pClosedBitmap->getWidth(), bitmapWidth );
+        }
+        if( m_pOpenBitmap )
+        {
+            bitmapWidth = __MAX( m_pOpenBitmap->getWidth(), bitmapWidth );
+        }
     }
     if( m_pItemBitmap )
     {
@@ -119,544 +123,452 @@ int CtrlTree::itemImageWidth()
     return bitmapWidth + 2;
 }
 
-int CtrlTree::maxItems()
+float CtrlTree::maxItems()
 {
     const Position *pPos = getPosition();
     if( !pPos )
     {
         return -1;
     }
-    return pPos->getHeight() / itemHeight();
+    return (float)pPos->getHeight() / itemHeight();
 }
 
-
-void CtrlTree::onUpdate( Subject<VarTree, tree_update*> &rTree,
+void CtrlTree::onUpdate( Subject<VarTree, tree_update> &rTree,
                          tree_update *arg )
 {
-    if( arg->i_type == 0 ) // Item update
+    (void)rTree;
+    if( arg->type == arg->ItemInserted )
     {
-        if( arg->b_active_item )
+        if( isItemVisible( arg->it ) )
         {
-            autoScroll();
-            ///\todo We should make image if we are visible in the view
             makeImage();
+            notifyLayout();
         }
+        setSliderFromFirst();
     }
-    /// \todo handle delete in a more clever way
-    else if ( arg->i_type == 1 ) // Global change or deletion
-    {
-        m_firstPos = m_rTree.begin();
-        makeImage();
-    }
-    else if ( arg->i_type == 2 ) // Item-append
+    else if( arg->type == arg->ItemUpdated )
     {
-        /// \todo Check if the item is really visible in the view
-        // (we only check if it in the document)
-        if( arg->b_visible == true )
+        if( arg->it->isPlaying() )
+        {
+            m_rTree.ensureExpanded( arg->it );
+            ensureVisible( arg->it );
+
+            makeImage();
+            notifyLayout();
+            setSliderFromFirst();
+        }
+        else if( isItemVisible( arg->it ) )
         {
             makeImage();
+            notifyLayout();
         }
     }
-    else if( arg->i_type == 3 ) // item-del
+    else if( arg->type == arg->DeletingItem )
     {
-        /* Make sure firstPos and lastSelected are still valid */
-        while( m_firstPos->m_deleted && m_firstPos != m_rTree.root()->begin() )
+        if( isItemVisible( arg->it ) )
+            m_bRefreshOnDelete = true;
+        // remove all references to arg->it
+        // if it is the one about to be deleted
+        if( m_firstPos == arg->it )
         {
-            m_firstPos = m_rTree.getPrevVisibleItem( m_firstPos );
+            m_firstPos = getNearestItem( arg->it );
         }
-        if( m_firstPos->m_deleted ) m_firstPos = m_rTree.root()->begin();
-
-        if( arg->b_visible == true )
+        if( m_lastClicked == arg->it )
         {
-            makeImage();
+            m_lastClicked = getNearestItem( arg->it );
+            m_lastClicked->setSelected( arg->it->isSelected() );
         }
     }
-    notifyLayout();
-}
-
-void CtrlTree::onUpdate( Subject<VarPercent, void*> &rPercent, void* arg)
-{
-    // Determine what is the first item to display
-    VarTree::Iterator it = m_rTree.begin();
-
-    if( m_dontMove ) return;
-
-    int excessItems = m_rTree.visibleItems() - maxItems();
-
-    if( excessItems > 0)
+    else if( arg->type == arg->ItemDeleted )
     {
-        VarPercent &rVarPos = m_rTree.getPositionVar();
-        // a simple (int)(...) causes rounding errors !
-#ifdef _MSC_VER
-#   define lrint (int)
-#endif
-        it = m_rTree.getVisibleItem(lrint( (1.0 - rVarPos.get()) * (double)excessItems ) + 1);
+        if( m_bRefreshOnDelete )
+        {
+            m_bRefreshOnDelete = false;
+
+            makeImage();
+            notifyLayout();
+        }
+        setSliderFromFirst();
     }
-    if( m_firstPos != it )
+    else if( arg->type == arg->ResetAll )
     {
-        // Redraw the control if the position has changed
-        m_firstPos = it;
+        m_lastClicked = m_rTree.end();
+        m_firstPos = getFirstFromSlider();
+
         makeImage();
         notifyLayout();
+        setSliderFromFirst();
+    }
+    else if( arg->type == arg->SliderChanged )
+    {
+        Iterator it = getFirstFromSlider();
+        if( m_firstPos != it )
+        {
+            m_firstPos = it;
+            makeImage();
+            notifyLayout();
+        }
     }
 }
 
 void CtrlTree::onResize()
 {
-    // Determine what is the first item to display
-    VarTree::Iterator it = m_rTree.begin();
-
-    int excessItems = m_rTree.visibleItems() - maxItems();
-
-    if( excessItems > 0)
-    {
-        VarPercent &rVarPos = m_rTree.getPositionVar();
-        // a simple (int)(...) causes rounding errors !
-#ifdef _MSC_VER
-#   define lrint (int)
-#endif
-        it = m_rTree.getVisibleItem(lrint( (1.0 - rVarPos.get()) * (double)excessItems ) + 1);
-    }
-    // Redraw the control if the position has changed
-    m_firstPos = it;
-    makeImage();
-    notifyLayout();
+    onPositionChange();
 }
 
 void CtrlTree::onPositionChange()
 {
+    m_capacity = maxItems();
+    setScrollStep();
+    m_firstPos = getFirstFromSlider();
     makeImage();
-    notifyLayout();
 }
 
 void CtrlTree::handleEvent( EvtGeneric &rEvent )
 {
-    bool bChangedPosition = false;
-    VarTree::Iterator toShow; bool needShow = false;
+    bool needShow = false;
+    bool needRefresh = false;
+    Iterator toShow = m_firstPos;
     if( rEvent.getAsString().find( "key:down" ) != string::npos )
     {
         int key = ((EvtKey&)rEvent).getKey();
-        VarTree::Iterator it;
-        bool previousWasSelected = false;
 
         /* Delete the selection */
         if( key == KEY_DELETE )
         {
+            /* Delete selected stuff */
             m_rTree.delSelected();
         }
         else if( key == KEY_PAGEDOWN )
         {
-            it = m_firstPos;
-            int i = (int)(maxItems()*1.5);
-            while( i >= 0 )
+            int numSteps = (int)m_capacity / 2;
+            VarPercent &rVarPos = m_rTree.getPositionVar();
+            rVarPos.increment( -numSteps );
+        }
+        else if( key == KEY_PAGEUP )
+        {
+            int numSteps = (int)m_capacity / 2;
+            VarPercent &rVarPos = m_rTree.getPositionVar();
+            rVarPos.increment( numSteps );
+        }
+        else if( key == KEY_UP )
+        {
+            // Scroll up one item
+            m_rTree.unselectTree();
+            if( m_lastClicked != m_rTree.end() )
             {
-                VarTree::Iterator it_old = it;
-                it = m_rTree.getNextVisibleItem( it );
-                /* End is already visible, dont' scroll */
-                if( it == m_rTree.end() )
+                if( --m_lastClicked != m_rTree.end() )
                 {
-                    it = it_old;
-                    break;
+                    m_lastClicked->setSelected( true );
                 }
-                needShow = true;
-                i--;
             }
-            if( needShow )
+            if( m_lastClicked == m_rTree.end() )
             {
-                ensureVisible( it );
-                makeImage();
-                notifyLayout();
-                return;
+                m_lastClicked = m_firstPos;
+                if( m_lastClicked != m_rTree.end() )
+                    m_lastClicked->setSelected( true );
             }
+            needRefresh = true;
+            needShow = true; toShow = m_lastClicked;
         }
-        else if (key == KEY_PAGEUP )
+        else if( key == KEY_DOWN )
         {
-            it = m_firstPos;
-            int i = maxItems();
-            while( i >= maxItems()/2 )
+            // Scroll down one item
+            m_rTree.unselectTree();
+            if( m_lastClicked != m_rTree.end() )
             {
-                it = m_rTree.getPrevVisibleItem( it );
-                /* End is already visible, dont' scroll */
-                if( it == m_rTree.begin() )
+                Iterator it_old = m_lastClicked;
+                if( ++m_lastClicked != m_rTree.end() )
                 {
-                    break;
+                    m_lastClicked->setSelected( true );
                 }
-                i--;
-            }
-            ensureVisible( it );
-            makeImage();
-            notifyLayout();
-            return;
-        }
-
-
-        for( it = m_rTree.begin(); it != m_rTree.end();
-             it = m_rTree.getNextVisibleItem( it ) )
-        {
-            VarTree::Iterator next = m_rTree.getNextVisibleItem( it );
-            if( key == KEY_UP )
-            {
-                // Scroll up one item
-                if( ( it->parent()
-                      && it != it->parent()->begin() )
-                    || &*it != m_pLastSelected )
+                else
                 {
-                    bool nextWasSelected = ( &*next == m_pLastSelected );
-                    it->m_selected = nextWasSelected;
-                    if( nextWasSelected )
-                    {
-                        m_pLastSelected = &*it;
-                        needShow = true; toShow = it;
-                    }
+                    it_old->setSelected( true );
+                    m_lastClicked = it_old;
                 }
             }
-            else if( key == KEY_DOWN )
+            else
             {
-                // Scroll down one item
-                if( ( it->parent()
-                      && next != it->parent()->end() )
-                    || &*it != m_pLastSelected )
-                {
-                    (*it).m_selected = previousWasSelected;
-                }
-                if( previousWasSelected )
+                m_lastClicked = m_firstPos;
+                if( m_lastClicked != m_rTree.end() )
+                    m_lastClicked->setSelected( true );
+            }
+            needRefresh = true;
+            needShow = true; toShow = m_lastClicked;
+        }
+        else if( key == KEY_RIGHT )
+        {
+            // Go down one level (and expand node)
+            Iterator& it = m_lastClicked;
+            if( it != m_rTree.end() )
+            {
+                if( !m_flat && !it->isExpanded() && it->size() )
                 {
-                    m_pLastSelected = &*it;
-                    needShow = true; toShow = it;
-                    previousWasSelected = false;
+                    it->setExpanded( true );
+                    needRefresh = true;
                 }
                 else
                 {
-                    previousWasSelected = ( &*it == m_pLastSelected );
+                    m_rTree.unselectTree();
+                    Iterator it_old = m_lastClicked;
+                    if( ++m_lastClicked != m_rTree.end() )
+                    {
+                        m_lastClicked->setSelected( true );
+                    }
+                    else
+                    {
+                        it_old->setSelected( true );
+                        m_lastClicked = it_old;
+                    }
+                    needRefresh = true;
+                    needShow = true; toShow = m_lastClicked;
                 }
             }
-            else if( key == KEY_RIGHT )
+        }
+        else if( key == KEY_LEFT )
+        {
+            // Go up one level (and close node)
+            Iterator& it = m_lastClicked;
+            if( it != m_rTree.end() )
             {
-                // Go down one level (and expand node)
-                if( &*it == m_pLastSelected )
+                if( m_flat )
                 {
-                    if( it->m_expanded )
+                    m_rTree.unselectTree();
+                    if( --m_lastClicked != m_rTree.end() )
                     {
-                        if( it->size() )
-                        {
-                            it->m_selected = false;
-                            it->begin()->m_selected = true;
-                            m_pLastSelected = &*(it->begin());
-                        }
-                        else
-                        {
-                            m_rTree.action( &*it );
-                        }
+                        m_lastClicked->setSelected( true );
                     }
                     else
                     {
-                        it->m_expanded = true;
-                        bChangedPosition = true;
+                        m_lastClicked = m_firstPos;
+                        if( m_lastClicked != m_rTree.end() )
+                            m_lastClicked->setSelected( true );
                     }
+                    needRefresh = true;
+                    needShow = true; toShow = m_lastClicked;
                 }
-            }
-            else if( key == KEY_LEFT )
-            {
-                // Go up one level (and close node)
-                if( &*it == m_pLastSelected )
+                else
                 {
-                    if( it->m_expanded && it->size() )
+                    if( it->isExpanded() )
                     {
-                        it->m_expanded = false;
-                        bChangedPosition = true;
+                        it->setExpanded( false );
+                        needRefresh = true;
                     }
                     else
                     {
-                        if( it->parent() && it->parent() != &m_rTree)
+                        Iterator it_parent = it.getParent();
+                        if( it_parent != m_rTree.end() )
                         {
-                            it->m_selected = false;
-                            m_pLastSelected = it->parent();
-                            m_pLastSelected->m_selected = true;
+                            it->setSelected( false );
+                            m_lastClicked = it_parent;
+                            m_lastClicked->setSelected( true );
+                            needRefresh = true;
+                            needShow = true; toShow = m_lastClicked;
                         }
                     }
                 }
             }
-            else if( key == KEY_ENTER || key == KEY_SPACE )
+        }
+        else if( key == KEY_ENTER || key == ' ' )
+        {
+            // Go up one level (and close node)
+            if( m_lastClicked != m_rTree.end() )
             {
-                // Go up one level (and close node)
-                if( &*it == m_pLastSelected )
-                {
-                    m_rTree.action( &*it );
-                }
+                m_rTree.action( &*m_lastClicked );
             }
         }
-        if( needShow )
-            ensureVisible( toShow );
-
-        // Redraw the control
-        makeImage();
-        notifyLayout();
+        else
+        {
+            // other keys to be forwarded to vlc core
+            EvtKey& rEvtKey = (EvtKey&)rEvent;
+            var_SetInteger( getIntf()->p_libvlc, "key-pressed",
+                            rEvtKey.getModKey() );
+        }
     }
 
     else if( rEvent.getAsString().find( "mouse:left" ) != string::npos )
     {
         EvtMouse &rEvtMouse = (EvtMouse&)rEvent;
         const Position *pos = getPosition();
-        int yPos = ( rEvtMouse.getYPos() - pos->getTop() ) / itemHeight();
         int xPos = rEvtMouse.getXPos() - pos->getLeft();
-        VarTree::Iterator it;
+        int yPos = ( rEvtMouse.getYPos() - pos->getTop() ) / itemHeight();
 
-        if( rEvent.getAsString().find( "mouse:left:down:ctrl,shift" ) !=
-            string::npos )
+        Iterator itClicked = findItemAtPos( yPos );
+        if( itClicked != m_rTree.end() )
         {
-            VarTree::Iterator itClicked = findItemAtPos( yPos );
-            // Flag to know if the current item must be selected
-            bool select = false;
-            for( it = m_rTree.begin(); it != m_rTree.end();
-                 it = m_rTree.getNextVisibleItem( it ) )
+            if( rEvent.getAsString().find( "mouse:left:down:ctrl,shift" ) !=
+                string::npos )
             {
-                bool nextSelect = select;
-                if( it == itClicked || &*it == m_pLastSelected )
+                // Flag to know if the current item must be selected
+                bool select = false;
+                for( Iterator it = m_firstPos; it != m_rTree.end(); ++it )
                 {
-                    if( select )
-                    {
-                        nextSelect = false;
-                    }
-                    else
+                    bool nextSelect = select;
+                    if( it == itClicked || it == m_lastClicked )
                     {
-                        select = true;
-                        nextSelect = true;
+                        if( select )
+                        {
+                            nextSelect = false;
+                        }
+                        else
+                        {
+                            select = true;
+                            if( itClicked != m_lastClicked )
+                                nextSelect = true;
+                        }
                     }
+                    it->setSelected( it->isSelected() || select );
+                    select = nextSelect;
+                    needRefresh = true;
                 }
-                it->m_selected = (*it).m_selected || select;
-                select = nextSelect;
             }
-        }
-        else if( rEvent.getAsString().find( "mouse:left:down:ctrl" ) !=
-                 string::npos )
-        {
-            // Invert the selection of the item
-            it = findItemAtPos( yPos );
-            if( it != m_rTree.end() )
+            else if( rEvent.getAsString().find( "mouse:left:down:ctrl" ) !=
+                     string::npos )
             {
-                it->m_selected = !it->m_selected;
-                m_pLastSelected = &*it;
+                // Invert the selection of the item
+                itClicked->toggleSelected();
+                m_lastClicked = itClicked;
+                needRefresh = true;
             }
-        }
-        else if( rEvent.getAsString().find( "mouse:left:down:shift" ) !=
-                 string::npos )
-        {
-            VarTree::Iterator itClicked = findItemAtPos( yPos );
-            // Flag to know if the current item must be selected
-            bool select = false;
-            for( it = m_rTree.begin(); it != m_rTree.end();
-                 it = m_rTree.getNextVisibleItem( it ) )
+            else if( rEvent.getAsString().find( "mouse:left:down:shift" ) !=
+                     string::npos )
             {
-                bool nextSelect = select;
-                if( it == itClicked || &*it == m_pLastSelected )
+                bool select = false;
+                for( Iterator it = m_firstPos; it != m_rTree.end(); ++it )
                 {
-                    if( select )
-                    {
-                        nextSelect = false;
-                    }
-                    else
+                    bool nextSelect = select;
+                    if( it == itClicked || it == m_lastClicked )
                     {
-                        select = true;
-                        nextSelect = true;
+                        if( select )
+                        {
+                            nextSelect = false;
+                        }
+                        else
+                        {
+                            select = true;
+                            if( itClicked != m_lastClicked )
+                                nextSelect = true;
+                        }
                     }
+                    it->setSelected( select );
+                    select = nextSelect;
                 }
-                it->m_selected = select;
-                select = nextSelect;
+                needRefresh = true;
             }
-        }
-        else if( rEvent.getAsString().find( "mouse:left:down" ) !=
-                 string::npos )
-        {
-            it = findItemAtPos(yPos);
-            if( it != m_rTree.end() )
+            else if( rEvent.getAsString().find( "mouse:left:down" ) !=
+                     string::npos )
             {
-                if( it->size() && xPos > (it->depth() - 1) * itemImageWidth()
-                    && xPos < it->depth() * itemImageWidth() )
+                if( !m_flat &&
+                    itClicked->size() &&
+                    xPos > (itClicked->depth() - 1) * itemImageWidth() &&
+                    xPos < itClicked->depth() * itemImageWidth() )
                 {
                     // Fold/unfold the item
-                    it->m_expanded = !it->m_expanded;
-                    bChangedPosition = true;
+                    itClicked->toggleExpanded();
                 }
                 else
                 {
                     // Unselect any previously selected item
-                    VarTree::Iterator it2;
-                    for( it2 = m_rTree.begin(); it2 != m_rTree.end();
-                         it2 = m_rTree.getNextVisibleItem( it2 ) )
-                    {
-                        it2->m_selected = false;
-                    }
+                    m_rTree.unselectTree();
                     // Select the new item
-                    if( it != m_rTree.end() )
-                    {
-                        it->m_selected = true;
-                        m_pLastSelected = &*it;
-                    }
+                    itClicked->setSelected( true );
+                    m_lastClicked = itClicked;
                 }
+                needRefresh = true;
             }
-        }
-
-        else if( rEvent.getAsString().find( "mouse:left:dblclick" ) !=
-                 string::npos )
-        {
-            it = findItemAtPos(yPos);
-            if( it != m_rTree.end() )
+            else if( rEvent.getAsString().find( "mouse:left:dblclick" ) !=
+                     string::npos )
             {
                // Execute the action associated to this item
-               m_rTree.action( &*it );
+               m_rTree.action( &*itClicked );
             }
         }
-        // Redraw the control
-        makeImage();
-        notifyLayout();
     }
 
     else if( rEvent.getAsString().find( "scroll" ) != string::npos )
     {
-        int direction = ((EvtScroll&)rEvent).getDirection();
-
-        double percentage = m_rTree.getPositionVar().get();
-        double step = 2.0 / (double)m_rTree.visibleItems();
+        int direction = static_cast<EvtScroll&>(rEvent).getDirection();
         if( direction == EvtScroll::kUp )
-        {
-            percentage += step;
-        }
+            m_rTree.getPositionVar().increment( +1 );
         else
-        {
-            percentage -= step;
-        }
-        m_rTree.getPositionVar().set( percentage );
+            m_rTree.getPositionVar().increment( -1 );
     }
 
-    /* We changed the nodes, let's fix teh position var */
-    if( bChangedPosition )
+    else if( rEvent.getAsString().find( "drag:over" ) != string::npos )
     {
-        VarTree::Iterator it;
-        int i = 0;
-        int iFirst = 0;
-        for( it = m_rTree.begin(); it != m_rTree.end();
-             it = m_rTree.getNextVisibleItem( it ) )
+        EvtDragOver& evt = static_cast<EvtDragOver&>(rEvent);
+        const Position *pos = getPosition();
+        int yPos = ( evt.getYPos() - pos->getTop() ) / itemHeight();
+
+        Iterator it = findItemAtPos( yPos );
+        if( it != m_itOver )
         {
-            i++;
-            if( it == m_firstPos )
-            {
-                iFirst = i;
-                break;
-            }
+            m_itOver = it;
+            needRefresh = true;
         }
-        iFirst += maxItems();
-        if( iFirst >= m_rTree.visibleItems() ) iFirst = m_rTree.visibleItems();
-        float f_new = (float)iFirst / (float)m_rTree.visibleItems();
-        m_dontMove = true;
-        m_rTree.getPositionVar().set( 1.0 - f_new );
-        m_dontMove = false;
     }
-}
 
-bool CtrlTree::mouseOver( int x, int y ) const
-{
-    const Position *pPos = getPosition();
-    return ( pPos
-       ? x >= 0 && x <= pPos->getWidth() && y >= 0 && y <= pPos->getHeight()
-       : false);
-}
-
-void CtrlTree::draw( OSGraphics &rImage, int xDest, int yDest )
-{
-    if( m_pImage )
+    else if( rEvent.getAsString().find( "drag:drop" ) != string::npos )
     {
-        rImage.drawGraphics( *m_pImage, 0, 0, xDest, yDest );
+        EvtDragDrop& evt = static_cast<EvtDragDrop&>(rEvent);
+        Playtree& rPlaytree = static_cast<Playtree&>(m_rTree);
+        rPlaytree.insertItems( *m_itOver, evt.getFiles(), false );
+        m_itOver = m_rTree.end();
+        needRefresh = true;
     }
-}
 
-bool CtrlTree::ensureVisible( VarTree::Iterator item )
-{
-    // Find the item to focus
-    int focusItemIndex = 0;
-    VarTree::Iterator it;
-
-    m_rTree.ensureExpanded( item );
-
-    for( it = m_rTree.begin(); it != m_rTree.end();
-         it = m_rTree.getNextVisibleItem( it ) )
+    else if( rEvent.getAsString().find( "drag:leave" ) != string::npos )
     {
-        if( it->m_id == item->m_id ) break;
-        focusItemIndex++;
+        m_itOver = m_rTree.end();
+        needRefresh = true;
     }
-   return ensureVisible( focusItemIndex );
-}
 
-bool CtrlTree::ensureVisible( int focusItemIndex )
-{
-    // Find  m_firstPos
-    VarTree::Iterator it;
-    int firstPosIndex = 0;
-    for( it = m_rTree.begin(); it != m_rTree.end();
-         it = m_rTree.getNextVisibleItem( it ) )
+    if( needShow )
     {
-        if( it == m_firstPos ) break;
-        firstPosIndex++;
+        if( toShow == m_rTree.end() ||
+            !ensureVisible( toShow ) )
+            needRefresh = true;
     }
-
-    if( it == m_rTree.end() ) return false;
-
-
-    if( it != m_rTree.end()
-        && ( focusItemIndex < firstPosIndex
-           || focusItemIndex > firstPosIndex + maxItems() ) )
+    if( needRefresh )
     {
-        // Scroll to have the wanted stream visible
-        VarPercent &rVarPos = m_rTree.getPositionVar();
-        rVarPos.set( 1.0 - (double)focusItemIndex /
-                           (double)m_rTree.visibleItems() );
-        return true;
+        setSliderFromFirst();
+
+        makeImage();
+        notifyLayout();
     }
-    return false;
 }
 
-void CtrlTree::autoScroll()
+bool CtrlTree::mouseOver( int x, int y ) const
 {
-    // Find the current playing stream
-    int playIndex = 0;
-    VarTree::Iterator it;
-
-    for( it = m_rTree.begin(); it != m_rTree.end();
-         it = m_rTree.getNextItem( it ) )
-    {
-        if( it->m_playing )
-        {
-           m_rTree.ensureExpanded( it );
-           break;
-        }
-    }
-    for( it = m_rTree.begin(); it != m_rTree.end();
-         it = m_rTree.getNextVisibleItem( it ) )
-    {
-        if( it->m_playing )
-           break;
-        playIndex++;
-    }
-
-    if( it == m_rTree.end() ) return;
-
-
-    ensureVisible( playIndex );
+    const Position *pPos = getPosition();
+    return !pPos ? false :
+        x >= 0 && x <= pPos->getWidth() && y >= 0 && y <= pPos->getHeight();
 }
 
+void CtrlTree::draw( OSGraphics &rImage, int xDest, int yDest, int w, int h)
+{
+    const Position *pPos = getPosition();
+    rect region( pPos->getLeft(), pPos->getTop(),
+                 pPos->getWidth(), pPos->getHeight() );
+    rect clip( xDest, yDest, w, h );
+    rect inter;
+
+    if( rect::intersect( region, clip, &inter ) && m_pImage )
+        rImage.drawGraphics( *m_pImage,
+                      inter.x - pPos->getLeft(),
+                      inter.y - pPos->getTop(),
+                      inter.x, inter.y, inter.width, inter.height );
+}
 
 void CtrlTree::makeImage()
 {
-    if( m_pImage )
-    {
-        delete m_pImage;
-    }
+    delete m_pImage;
 
     // Get the size of the control
     const Position *pPos = getPosition();
     if( !pPos )
-    {
         return;
-    }
     int width = pPos->getWidth();
     int height = pPos->getHeight();
 
@@ -666,52 +578,48 @@ void CtrlTree::makeImage()
     OSFactory *pOsFactory = OSFactory::instance( getIntf() );
     m_pImage = pOsFactory->createOSGraphics( width, height );
 
-    VarTree::Iterator it = m_firstPos;
+    Iterator it = m_firstPos;
 
     if( m_pBgBitmap )
     {
         // Draw the background bitmap
-        ScaledBitmap bmp( getIntf(), *m_pBgBitmap, width, height );
-        m_pImage->drawBitmap( bmp, 0, 0 );
+        if( !m_pScaledBitmap ||
+            m_pScaledBitmap->getWidth() != width ||
+            m_pScaledBitmap->getHeight() != height )
+        {
+            delete m_pScaledBitmap;
+            m_pScaledBitmap =
+                new ScaledBitmap( getIntf(), *m_pBgBitmap, width, height );
+        }
+        m_pImage->drawBitmap( *m_pScaledBitmap, 0, 0 );
 
-        for( int yPos = 0; yPos < height; yPos += i_itemHeight )
+        for( int yPos = 0;
+             yPos < height && it != m_rTree.end();
+             yPos += i_itemHeight, ++it )
         {
-            if( it != m_rTree.end() )
+            if( it->isSelected() )
             {
-                if( (*it).m_selected )
-                {
-                    int rectHeight = __MIN( i_itemHeight, height - yPos );
-                    m_pImage->fillRect( 0, yPos, width, rectHeight,
-                                        m_selColor );
-                }
-                do
-                {
-                    it = m_rTree.getNextVisibleItem( it );
-                } while( it->m_deleted );
+                int rectHeight = __MIN( i_itemHeight, height - yPos );
+                m_pImage->fillRect( 0, yPos, width, rectHeight, m_selColor );
             }
         }
     }
     else
     {
-        // FIXME (TRYME)
         // Fill background with background color
         uint32_t bgColor = m_bgColor1;
         m_pImage->fillRect( 0, 0, width, height, bgColor );
+        // Overwrite with alternate colors (bgColor1, bgColor2)
         for( int yPos = 0; yPos < height; yPos += i_itemHeight )
         {
             int rectHeight = __MIN( i_itemHeight, height - yPos );
-            if( it != m_rTree.end() )
-            {
-                uint32_t color = ( it->m_selected ? m_selColor : bgColor );
-                m_pImage->fillRect( 0, yPos, width, rectHeight, color );
-                do
-                {
-                    it = m_rTree.getNextVisibleItem( it );
-                } while( it->m_deleted );
-            }
+            if( it == m_rTree.end() )
+                m_pImage->fillRect( 0, yPos, width, rectHeight, bgColor );
             else
             {
-                m_pImage->fillRect( 0, yPos, width, rectHeight, bgColor );
+                uint32_t color = ( it->isSelected() ? m_selColor : bgColor );
+                m_pImage->fillRect( 0, yPos, width, rectHeight, color );
+                ++it;
             }
             bgColor = ( bgColor == m_bgColor1 ? m_bgColor2 : m_bgColor1 );
         }
@@ -719,25 +627,24 @@ void CtrlTree::makeImage()
 
     int bitmapWidth = itemImageWidth();
 
-    int yPos = 0;
     it = m_firstPos;
-    while( it != m_rTree.end() && yPos < height )
+    for( int yPos = 0; yPos < height && it != m_rTree.end(); ++it )
     {
         const GenericBitmap *m_pCurBitmap;
-        UString *pStr = (UString*)(it->m_cString.get());
-        uint32_t color = ( it->m_playing ? m_playColor : m_fgColor );
-
-        // Draw the text
+        UString *pStr = it->getString();
         if( pStr != NULL )
         {
-            int depth = it->depth();
-            GenericBitmap *pText = m_rFont.drawString( *pStr, color, width - bitmapWidth * depth );
+            uint32_t color = it->isPlaying() ? m_playColor : m_fgColor;
+            int depth = m_flat ? 1 : it->depth();
+            GenericBitmap *pText =
+                m_rFont.drawString( *pStr, color, width-bitmapWidth*depth );
             if( !pText )
             {
                 return;
             }
             if( it->size() )
-                m_pCurBitmap = it->m_expanded ? m_pOpenBitmap : m_pClosedBitmap;
+                m_pCurBitmap =
+                    it->isExpanded() ? m_pOpenBitmap : m_pClosedBitmap;
             else
                 m_pCurBitmap = m_pItemBitmap;
 
@@ -750,6 +657,7 @@ void CtrlTree::makeImage()
                     delete pText;
                     break;
                 }
+                // Draw the icon in front of the text
                 m_pImage->drawBitmap( *m_pCurBitmap, 0, 0,
                                       bitmapWidth * (depth - 1 ), yPos2,
                                       m_pCurBitmap->getWidth(),
@@ -764,28 +672,140 @@ void CtrlTree::makeImage()
                 yPos = 0;
             }
             int lineHeight = __MIN( pText->getHeight() - ySrc, height - yPos );
+            // Draw the text
             m_pImage->drawBitmap( *pText, 0, ySrc, bitmapWidth * depth, yPos,
                                   pText->getWidth(),
                                   lineHeight, true );
             yPos += (pText->getHeight() - ySrc );
+
+            if( it == m_itOver )
+            {
+                // Draw the underline bar below the text for drag&drop
+                m_pImage->fillRect(
+                    bitmapWidth * (depth - 1 ), yPos - 2,
+                    bitmapWidth + pText->getWidth(), __MAX( lineHeight/5, 3 ),
+                    m_selColor );
+            }
             delete pText;
         }
-        do {
-        it = m_rTree.getNextVisibleItem( it );
-        } while( it->m_deleted );
     }
 }
 
-VarTree::Iterator CtrlTree::findItemAtPos( int pos )
+CtrlTree::Iterator CtrlTree::findItemAtPos( int pos )
 {
     // The first item is m_firstPos.
     // We decrement pos as we try the other items, until pos == 0.
-    VarTree::Iterator it;
-    for( it = m_firstPos; it != m_rTree.end() && pos != 0;
-         it = m_rTree.getNextVisibleItem( it ) )
+    Iterator it = m_firstPos;
+    for( ; it != m_rTree.end() && pos != 0; ++it, pos-- );
+
+    return it;
+}
+
+CtrlTree::Iterator CtrlTree::getFirstFromSlider()
+{
+    // a simple (int)(...) causes rounding errors !
+#ifdef _MSC_VER
+#       define lrint (int)
+#endif
+    VarPercent &rVarPos = m_rTree.getPositionVar();
+    double percentage = rVarPos.get();
+
+    int excessItems = m_flat ? (m_rTree.countLeafs() - (int)m_capacity)
+                             : (m_rTree.visibleItems() - (int)m_capacity);
+
+    int index = (excessItems > 0 ) ?
+        lrint( (1.0 - percentage)*(double)excessItems ) :
+        0;
+
+    Iterator it_first = m_rTree.getItem( index );
+
+    if( m_lastClicked == m_rTree.end() )
     {
-        pos--;
+        m_lastClicked = it_first;
+        if( m_lastClicked != m_rTree.end() )
+            m_lastClicked->setSelected( true );
     }
 
-    return it;
+    return it_first;
+}
+
+void CtrlTree::setScrollStep()
+{
+    VarPercent &rVarPos = m_rTree.getPositionVar();
+
+    int excessItems = m_flat ? (m_rTree.countLeafs() - (int)m_capacity)
+                             : (m_rTree.visibleItems() - (int)m_capacity);
+
+    if( excessItems > 0 )
+        rVarPos.setStep( (float)1 / excessItems );
+    else
+        rVarPos.setStep( 1.0 );
+}
+
+void CtrlTree::setSliderFromFirst()
+{
+    VarPercent &rVarPos = m_rTree.getPositionVar();
+
+    int excessItems = m_flat ? (m_rTree.countLeafs() - (int)m_capacity)
+                             : (m_rTree.visibleItems() - (int)m_capacity);
+
+    int index = m_rTree.getIndex( m_firstPos );
+    if( excessItems > 0 )
+    {
+        rVarPos.set( 1.0 - (float)index/(float)excessItems );
+        rVarPos.setStep( 1.0 / excessItems );
+    }
+    else
+    {
+        rVarPos.set( 1.0 );
+        rVarPos.setStep( 1.0 );
+    }
+}
+
+bool CtrlTree::isItemVisible( const Iterator& it_ref )
+{
+    if( it_ref == m_rTree.end() )
+        return false;
+
+    Iterator it = m_firstPos;
+    if( it == m_rTree.end() )
+        return true;
+
+    // Ensure a partially visible last item is taken into account
+    int max = (int)m_capacity;
+    if( (float)max < m_capacity )
+        max++;
+
+    for( int i = 0; i < max && it != m_rTree.end(); ++it, i++ )
+    {
+        if( it == it_ref )
+            return true;
+    }
+    return false;
+}
+
+bool CtrlTree::ensureVisible( const Iterator& item )
+{
+    Iterator it = m_firstPos;
+    int max = (int)m_capacity;
+    for( int i = 0; i < max && it != m_rTree.end(); ++it, i++ )
+    {
+        if( it == item )
+            return false;
+    }
+
+    m_rTree.setSliderFromItem( item );
+    return true;
+}
+
+CtrlTree::Iterator CtrlTree::getNearestItem( const Iterator& item )
+{
+    // return the previous item if it exists
+    Iterator newItem = item;
+    if( --newItem != m_rTree.end() && newItem != item )
+        return newItem;
+
+    // return the next item if no previous item found
+    newItem = item;
+    return ++newItem;
 }