]> git.sesse.net Git - vlc/blobdiff - modules/gui/skins2/controls/ctrl_tree.cpp
Use Brackets for global headers.
[vlc] / modules / gui / skins2 / controls / ctrl_tree.cpp
index 71afa8b84cefa0cf21556f5430a96d4ff980ac4c..a3a70ece9483e196a6996cd22113138baf3f60a4 100644 (file)
@@ -1,10 +1,11 @@
 /*****************************************************************************
  * ctrl_tree.cpp
  *****************************************************************************
- * Copyright (C) 2003 VideoLAN
- * $Id: ctrl_list.cpp 11009 2005-05-14 14:39:05Z ipkiss $
+ * Copyright (C) 2003 the VideoLAN team
+ * $Id$
  *
  * Authors: Antoine Cellerier <dionoea@videolan.org>
+ *          ClĂ©ment Stenac <zorglub@videolan.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
  *
  * You should have received a copy of the GNU General Public License
  * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
  *****************************************************************************/
 
 #include <math.h>
+#include "../utils/var_bool.hpp"
 #include "ctrl_tree.hpp"
 #include "../src/os_factory.hpp"
 #include "../src/os_graphics.hpp"
@@ -33,7 +35,7 @@
 #include "../events/evt_key.hpp"
 #include "../events/evt_mouse.hpp"
 #include "../events/evt_scroll.hpp"
-#include "vlc_keys.h"
+#include <vlc_keys.h>
 #ifdef sun
 #   include "solaris_specific.h" // for lrint
 #endif
 #define SCROLL_STEP 0.05
 #define LINE_INTERVAL 1  // Number of pixels inserted between 2 lines
 
+
 CtrlTree::CtrlTree( intf_thread_t *pIntf,
-                  VarTree &rTree,
-                  const GenericFont &rFont,
-                  const GenericBitmap *pBgBitmap,
-                  const GenericBitmap *pItemBitmap,
-                  const GenericBitmap *pOpenBitmap,
-                  const GenericBitmap *pClosedBitmap,
-                  uint32_t fgColor,
-                  uint32_t playColor,
-                  uint32_t bgColor1,
-                  uint32_t bgColor2,
-                  uint32_t selColor,
-                  const UString &rHelp,
-                  VarBool *pVisible ):
+                    VarTree &rTree,
+                    const GenericFont &rFont,
+                    const GenericBitmap *pBgBitmap,
+                    const GenericBitmap *pItemBitmap,
+                    const GenericBitmap *pOpenBitmap,
+                    const GenericBitmap *pClosedBitmap,
+                    uint32_t fgColor,
+                    uint32_t playColor,
+                    uint32_t bgColor1,
+                    uint32_t bgColor2,
+                    uint32_t selColor,
+                    const UString &rHelp,
+                    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_pLastSelected( NULL ), m_pImage( NULL ), m_dontMove( false )
 {
     // Observe the tree and position variables
     m_rTree.addObserver( this );
     m_rTree.getPositionVar().addObserver( this );
 
-    m_lastPos = m_rTree.begin();
+    m_flat = pFlat->get();
+
+    m_firstPos = m_flat ? m_rTree.firstLeaf() : m_rTree.begin();
 
     makeImage();
 }
@@ -75,22 +81,22 @@ CtrlTree::~CtrlTree()
 {
     m_rTree.getPositionVar().delObserver( this );
     m_rTree.delObserver( this );
-    if( m_pImage )
-    {
-        delete m_pImage;
-    }
+    delete m_pImage;
 }
 
 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 )
     {
@@ -100,6 +106,27 @@ int CtrlTree::itemHeight()
     return itemHeight;
 }
 
+int CtrlTree::itemImageWidth()
+{
+    int bitmapWidth = 5;
+    if( !m_flat )
+    {
+        if( m_pClosedBitmap )
+        {
+            bitmapWidth = __MAX( m_pClosedBitmap->getWidth(), bitmapWidth );
+        }
+        if( m_pOpenBitmap )
+        {
+            bitmapWidth = __MAX( m_pOpenBitmap->getWidth(), bitmapWidth );
+        }
+    }
+    if( m_pItemBitmap )
+    {
+        bitmapWidth = __MAX( m_pItemBitmap->getWidth(), bitmapWidth );
+    }
+    return bitmapWidth + 2;
+}
+
 int CtrlTree::maxItems()
 {
     const Position *pPos = getPosition();
@@ -111,18 +138,67 @@ int CtrlTree::maxItems()
 }
 
 
-void CtrlTree::onUpdate( Subject<VarTree> &rTree )
+void CtrlTree::onUpdate( Subject<VarTree, tree_update> &rTree,
+                         tree_update *arg )
 {
-    autoScroll();
-    m_pLastSelected = NULL;
+    if( arg->i_type == 0 ) // Item update
+    {
+        if( arg->b_active_item )
+        {
+            autoScroll();
+            ///\todo We should make image if we are visible in the view
+            makeImage();
+        }
+    }
+    /// \todo handle delete in a more clever way
+    else if ( arg->i_type == 1 ) // Global change or deletion
+    {
+        m_firstPos = m_flat ? m_rTree.firstLeaf() : m_rTree.begin();
+        makeImage();
+    }
+    else if ( arg->i_type == 2 ) // Item-append
+    {
+        if( m_flat && m_firstPos->size() )
+            m_firstPos = m_rTree.getNextLeaf( m_firstPos );
+        /// \todo Check if the item is really visible in the view
+        // (we only check if it in the document)
+        if( arg->b_visible == true )
+        {
+            makeImage();
+        }
+    }
+    else if( arg->i_type == 3 ) // item-del
+    {
+        /* Make sure firstPos and lastSelected are still valid */
+        while( m_firstPos->m_deleted && m_firstPos != m_rTree.root()->begin() )
+        {
+            m_firstPos = m_flat ? m_rTree.getPrevLeaf( m_firstPos )
+                                : m_rTree.getPrevVisibleItem( m_firstPos );
+        }
+        if( m_firstPos->m_deleted )
+            m_firstPos = m_flat ? m_rTree.firstLeaf()
+                                : m_rTree.root()->begin();
+
+        if( arg->b_visible == true )
+        {
+            makeImage();
+        }
+    }
+    notifyLayout();
 }
 
-void CtrlTree::onUpdate( Subject<VarPercent> &rPercent )
+void CtrlTree::onUpdate( Subject<VarPercent> &rPercent, void* arg)
 {
     // Determine what is the first item to display
-    VarTree::Iterator it = m_rTree.begin();
+    VarTree::Iterator it = m_flat ? m_rTree.firstLeaf() : m_rTree.begin();
 
-    int excessItems = m_rTree.visibleItems() - maxItems();
+    if( m_dontMove ) return;
+
+    int excessItems;
+    if( m_flat )
+        excessItems = m_rTree.countLeafs() - maxItems();
+    else
+        excessItems = m_rTree.visibleItems() - maxItems();
 
     if( excessItems > 0)
     {
@@ -131,12 +207,15 @@ void CtrlTree::onUpdate( Subject<VarPercent> &rPercent )
 #ifdef _MSC_VER
 #   define lrint (int)
 #endif
-        it = m_rTree.visibleItem(lrint( (1.0 - rVarPos.get()) * (double)excessItems ) + 1); /* FIXME : shouldn't need this +1 */
+        if( m_flat )
+            it = m_rTree.getLeaf(lrint( (1.0 - rVarPos.get()) * (double)excessItems ) + 1 );
+        else
+            it = m_rTree.getVisibleItem(lrint( (1.0 - rVarPos.get()) * (double)excessItems ) + 1 );
     }
-    if( m_lastPos != it )
+    if( m_firstPos != it )
     {
         // Redraw the control if the position has changed
-        m_lastPos = it;
+        m_firstPos = it;
         makeImage();
         notifyLayout();
     }
@@ -144,11 +223,14 @@ void CtrlTree::onUpdate( Subject<VarPercent> &rPercent )
 
 void CtrlTree::onResize()
 {
-// FIXME : shouldn't be the same as the onUpdate function ... but i'm lazy
     // Determine what is the first item to display
-    VarTree::Iterator it = m_rTree.begin();
+    VarTree::Iterator it = m_flat ? m_rTree.firstLeaf() : m_rTree.begin();
 
-    int excessItems = m_rTree.visibleItems() - maxItems();
+    int excessItems;
+    if( m_flat )
+        excessItems = m_rTree.countLeafs() - maxItems();
+    else
+        excessItems = m_rTree.visibleItems() - maxItems();
 
     if( excessItems > 0)
     {
@@ -157,37 +239,15 @@ void CtrlTree::onResize()
 #ifdef _MSC_VER
 #   define lrint (int)
 #endif
-        it = m_rTree.visibleItem(lrint( (1.0 - rVarPos.get()) * (double)excessItems ) + 1); /* FIXME : shouldn't need this +1 */
-    }
-    // Redraw the control if the position has changed
-    m_lastPos = it;
-    makeImage();
-    notifyLayout();
-#if 0
-    // Determine what is the first item to display
-    VarTree::Iterator it = m_rTree.begin();
-
-    int excessItems = m_rTree.visibleItems() - maxItems();
-
-    if( excessItems > 0)
-    {
-        /* FIXME VarPercent &rVarPos = m_rTree.getPositionVar();
-        double newVal = 1.0 - (double)m_lastPos / excessItems;
-        if( newVal >= 0 )
-        {
-            // Change the position to keep the same first displayed item
-            rVarPos.set( 1.0 - (double)m_lastPos / excessItems );
-        }
+        if( m_flat )
+            it = m_rTree.getLeaf(lrint( (1.0 - rVarPos.get()) * (double)excessItems ) + 1 );
         else
-        {
-            // We cannot keep the current first item
-            m_lastPos = excessItems;
-        }*/
-        it = m_rTree.visibleItem( excessItems );
+            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();
-#endif
 }
 
 void CtrlTree::onPositionChange()
@@ -196,35 +256,96 @@ void CtrlTree::onPositionChange()
     notifyLayout();
 }
 
-#define IT_DISP_LOOP_END( a )  \
-                if( a ->m_expanded && a ->size() ) \
-                { \
-                    a = a ->begin(); \
-                } \
-                else \
-                { \
-                    VarTree::Iterator it_old = a; \
-                    a ++; \
-                    if( it_old->parent() && it_old->parent()->end() == a ) \
-                    { \
-                        a = it_old->uncle(); \
-                    } \
-                }
 void CtrlTree::handleEvent( EvtGeneric &rEvent )
 {
-    // TODO TODO FIXME TODO TODO
+    bool bChangedPosition = false;
+    VarTree::Iterator toShow; bool needShow = false;
     if( rEvent.getAsString().find( "key:down" ) != string::npos )
     {
         int key = ((EvtKey&)rEvent).getKey();
-        VarTree::Iterator it = m_rTree.begin();
+        VarTree::Iterator it;
         bool previousWasSelected = false;
-        while( it != m_rTree.end() )
+
+        /* Delete the selection */
+        if( key == KEY_DELETE )
         {
-            VarTree::Iterator next = it;
-            IT_DISP_LOOP_END( next );
+            /* Find first non selected item before m_pLastSelected */
+            VarTree::Iterator it_sel = m_flat ? m_rTree.firstLeaf()
+                                              : m_rTree.begin();
+            for( it = m_flat ? m_rTree.firstLeaf() : m_rTree.begin();
+                 it != m_rTree.end();
+                 it = m_flat ? m_rTree.getNextLeaf( it )
+                             : m_rTree.getNextVisibleItem( it ) )
+            {
+                if( &*it == m_pLastSelected ) break;
+                if( !it->m_selected ) it_sel = it;
+            }
+
+            /* Delete selected stuff */
+            m_rTree.delSelected();
+
+            /* Select it_sel */
+            it_sel->m_selected = true;
+            m_pLastSelected = &*it_sel;
+        }
+        else if( key == KEY_PAGEDOWN )
+        {
+            it = m_firstPos;
+            int i = (int)(maxItems()*1.5);
+            while( i >= 0 )
+            {
+                VarTree::Iterator it_old = it;
+                it = m_flat ? m_rTree.getNextLeaf( it )
+                            : m_rTree.getNextVisibleItem( it );
+                /* End is already visible, dont' scroll */
+                if( it == m_rTree.end() )
+                {
+                    it = it_old;
+                    break;
+                }
+                needShow = true;
+                i--;
+            }
+            if( needShow )
+            {
+                ensureVisible( it );
+                makeImage();
+                notifyLayout();
+                return;
+            }
+        }
+        else if (key == KEY_PAGEUP )
+        {
+            it = m_firstPos;
+            int i = maxItems();
+            while( i >= maxItems()/2 )
+            {
+                it = m_flat ? m_rTree.getPrevLeaf( it )
+                            : m_rTree.getPrevVisibleItem( it );
+                /* End is already visible, dont' scroll */
+                if( it == ( m_flat ? m_rTree.firstLeaf() : m_rTree.begin() ) )
+                {
+                    break;
+                }
+                i--;
+            }
+            ensureVisible( it );
+            makeImage();
+            notifyLayout();
+            return;
+        }
+
+
+        for( it = m_flat ? m_rTree.firstLeaf() : m_rTree.begin();
+             it != m_rTree.end();
+             it = m_flat ? m_rTree.getNextLeaf( it )
+                         : m_rTree.getNextVisibleItem( it ) )
+        {
+            VarTree::Iterator next = m_flat ? m_rTree.getNextLeaf( it )
+                                            : m_rTree.getNextVisibleItem( it );
             if( key == KEY_UP )
             {
-                //Scroll up one item
+                // Scroll up one item
                 if( ( it->parent()
                       && it != it->parent()->begin() )
                     || &*it != m_pLastSelected )
@@ -234,6 +355,7 @@ void CtrlTree::handleEvent( EvtGeneric &rEvent )
                     if( nextWasSelected )
                     {
                         m_pLastSelected = &*it;
+                        needShow = true; toShow = it;
                     }
                 }
             }
@@ -249,37 +371,79 @@ void CtrlTree::handleEvent( EvtGeneric &rEvent )
                 if( previousWasSelected )
                 {
                     m_pLastSelected = &*it;
+                    needShow = true; toShow = it;
                     previousWasSelected = false;
                 }
                 else
                 {
                     previousWasSelected = ( &*it == m_pLastSelected );
                 }
+
+                // Fix last tree item selection
+                if( ( m_flat ? m_rTree.getNextLeaf( it )
+                    : m_rTree.getNextVisibleItem( it ) ) == m_rTree.end()
+                 && &*it == m_pLastSelected )
+                {
+                    (*it).m_selected = true;
+                }
             }
             else if( key == KEY_RIGHT )
             {
-                // Go down one level
-                if( it->m_expanded )
+                // Go down one level (and expand node)
+                if( &*it == m_pLastSelected )
                 {
-                    if( it->size() )
+                    if( it->m_expanded )
                     {
-                        /* FIXME : finir */
-                        m_pLastSelected = &*(it->begin());
+                        if( it->size() )
+                        {
+                            it->m_selected = false;
+                            it->begin()->m_selected = true;
+                            m_pLastSelected = &*(it->begin());
+                        }
+                        else
+                        {
+                            m_rTree.action( &*it );
+                        }
+                    }
+                    else
+                    {
+                        it->m_expanded = true;
+                        bChangedPosition = true;
                     }
                 }
-                else
+            }
+            else if( key == KEY_LEFT )
+            {
+                // Go up one level (and close node)
+                if( &*it == m_pLastSelected )
                 {
-                    it->m_expanded = true;
+                    if( it->m_expanded && it->size() )
+                    {
+                        it->m_expanded = false;
+                        bChangedPosition = true;
+                    }
+                    else
+                    {
+                        if( it->parent() && it->parent() != &m_rTree)
+                        {
+                            it->m_selected = false;
+                            m_pLastSelected = it->parent();
+                            m_pLastSelected->m_selected = true;
+                        }
+                    }
                 }
             }
-            else if( key == KEY_LEFT )
+            else if( key == KEY_ENTER || key == KEY_SPACE )
             {
                 // Go up one level (and close node)
-                // TODO
-                it->m_expanded = false;
+                if( &*it == m_pLastSelected )
+                {
+                    m_rTree.action( &*it );
+                }
             }
-            it = next;
         }
+        if( needShow )
+            ensureVisible( toShow );
 
         // Redraw the control
         makeImage();
@@ -291,50 +455,121 @@ void CtrlTree::handleEvent( EvtGeneric &rEvent )
         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 index = 0;
 
-        // TODO : add all other mouse controls
-        /**/ if( rEvent.getAsString().find( "mouse:left:down" ) !=
-                 string::npos )
+        if( rEvent.getAsString().find( "mouse:left:down:ctrl,shift" ) !=
+            string::npos )
         {
-            for( it = m_lastPos; it != m_rTree.end(); )
+            VarTree::Iterator itClicked = findItemAtPos( yPos );
+            // Flag to know if the current item must be selected
+            bool select = false;
+            for( it = m_flat ? m_rTree.firstLeaf() : m_rTree.begin();
+                 it != m_rTree.end();
+                 it = m_flat ? m_rTree.getNextLeaf( it )
+                             : m_rTree.getNextVisibleItem( it ) )
             {
-                if( index == yPos )
+                bool nextSelect = select;
+                if( it == itClicked || &*it == m_pLastSelected )
                 {
-                    it->m_selected = true;
-                    m_pLastSelected = &*it;
+                    if( select )
+                    {
+                        nextSelect = false;
+                    }
+                    else
+                    {
+                        select = true;
+                        nextSelect = true;
+                    }
                 }
-                else
+                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() )
+            {
+                it->m_selected = !it->m_selected;
+                m_pLastSelected = &*it;
+            }
+        }
+        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_flat ? m_rTree.firstLeaf() : m_rTree.begin();
+                 it != m_rTree.end();
+                 it = m_flat ? m_rTree.getNextLeaf( it )
+                             : m_rTree.getNextVisibleItem( it ) )
+            {
+                bool nextSelect = select;
+                if( it == itClicked || &*it == m_pLastSelected )
                 {
-                    it->m_selected = false;
+                    if( select )
+                    {
+                        nextSelect = false;
+                    }
+                    else
+                    {
+                        select = true;
+                        nextSelect = true;
+                    }
                 }
-                index ++;
-                IT_DISP_LOOP_END( it );
+                it->m_selected = select;
+                select = nextSelect;
             }
         }
-
-        else if( rEvent.getAsString().find( "mouse:left:dblclick" ) !=
+        else if( rEvent.getAsString().find( "mouse:left:down" ) !=
                  string::npos )
         {
-            for( it = m_lastPos; it != m_rTree.end(); )
+            it = findItemAtPos(yPos);
+            if( it != m_rTree.end() )
             {
-                if( index == yPos )
+                if( ( it->size() && xPos > (it->depth() - 1) * itemImageWidth()
+                      && xPos < it->depth() * itemImageWidth() )
+                 && !m_flat )
                 {
-                    it->m_selected = true;
-                    m_pLastSelected = &*it;
-                    // Execute the action associated to this item
-                    m_rTree.action( &*it );
+                    // Fold/unfold the item
+                    it->m_expanded = !it->m_expanded;
+                    bChangedPosition = true;
                 }
                 else
                 {
-                    it->m_selected = false;
+                    // Unselect any previously selected item
+                    VarTree::Iterator it2;
+                    for( it2 = m_flat ? m_rTree.firstLeaf() : m_rTree.begin();
+                         it2 != m_rTree.end();
+                         it2 = m_flat ? m_rTree.getNextLeaf( it2 )
+                                      : m_rTree.getNextVisibleItem( it2 ) )
+                    {
+                        it2->m_selected = false;
+                    }
+                    // Select the new item
+                    if( it != m_rTree.end() )
+                    {
+                        it->m_selected = true;
+                        m_pLastSelected = &*it;
+                    }
                 }
-                index ++;
-                IT_DISP_LOOP_END( it );
             }
         }
 
+        else if( rEvent.getAsString().find( "mouse:left:dblclick" ) !=
+                 string::npos )
+        {
+            it = findItemAtPos(yPos);
+            if( it != m_rTree.end() )
+            {
+               // Execute the action associated to this item
+               m_rTree.action( &*it );
+            }
+        }
         // Redraw the control
         makeImage();
         notifyLayout();
@@ -345,16 +580,46 @@ void CtrlTree::handleEvent( EvtGeneric &rEvent )
         int direction = ((EvtScroll&)rEvent).getDirection();
 
         double percentage = m_rTree.getPositionVar().get();
+        double step = 2.0 / (double)( m_flat ? m_rTree.countLeafs()
+                                             : m_rTree.visibleItems() );
         if( direction == EvtScroll::kUp )
         {
-            percentage += SCROLL_STEP;
+            percentage += step;
         }
         else
         {
-            percentage -= SCROLL_STEP;
+            percentage -= step;
         }
         m_rTree.getPositionVar().set( percentage );
     }
+
+    /* We changed the nodes, let's fix teh position var */
+    if( bChangedPosition )
+    {
+        VarTree::Iterator it;
+        int i = 0;
+        int iFirst = 0;
+        for( it = m_flat ? m_rTree.firstLeaf() : m_rTree.begin();
+             it != m_rTree.end();
+             it = m_flat ? m_rTree.getNextLeaf( it )
+                         : m_rTree.getNextVisibleItem( it ) )
+        {
+            i++;
+            if( it == m_firstPos )
+            {
+                iFirst = i;
+                break;
+            }
+        }
+        iFirst += maxItems();
+        if( iFirst >= m_flat ? m_rTree.countLeafs() : m_rTree.visibleItems() )
+            iFirst = m_flat ? m_rTree.countLeafs() : m_rTree.visibleItems();
+        float f_new = (float)iFirst / (float)( m_flat ? m_rTree.countLeafs()
+                                                      :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
@@ -373,24 +638,101 @@ void CtrlTree::draw( OSGraphics &rImage, int xDest, int yDest )
     }
 }
 
-void CtrlTree::autoScroll()
+bool CtrlTree::ensureVisible( VarTree::Iterator item )
 {
-    // TODO FIXME TODO
-    makeImage();
-    notifyLayout();
+    // Find the item to focus
+    int focusItemIndex = 0;
+    VarTree::Iterator it;
+
+    m_rTree.ensureExpanded( item );
+
+    for( it = m_flat ? m_rTree.firstLeaf() : m_rTree.begin();
+         it != m_rTree.end();
+         it = m_flat ? m_rTree.getNextLeaf( it )
+                     : m_rTree.getNextVisibleItem( it ) )
+    {
+        if( it->m_id == item->m_id ) break;
+        focusItemIndex++;
+    }
+   return ensureVisible( focusItemIndex );
 }
 
-void CtrlTree::makeImage()
+bool CtrlTree::ensureVisible( int focusItemIndex )
 {
-    if( m_pImage )
+    // Find  m_firstPos
+    VarTree::Iterator it;
+    int firstPosIndex = 0;
+    for( it = m_flat ? m_rTree.firstLeaf() : m_rTree.begin();
+         it != m_rTree.end();
+         it = m_flat ? m_rTree.getNextLeaf( it )
+                     : m_rTree.getNextVisibleItem( it ) )
     {
-        delete m_pImage;
+        if( it == m_firstPos ) break;
+        firstPosIndex++;
+    }
+
+    if( it == m_rTree.end() ) return false;
+
+
+    if( it != m_rTree.end()
+        && ( focusItemIndex < firstPosIndex
+           || focusItemIndex > firstPosIndex + maxItems() ) )
+    {
+        // Scroll to have the wanted stream visible
+        VarPercent &rVarPos = m_rTree.getPositionVar();
+        rVarPos.set( 1.0 - (double)focusItemIndex /
+                           (double)( m_flat ? m_rTree.countLeafs()
+                                            : m_rTree.visibleItems() ) );
+        return true;
     }
+    return false;
+}
+
+void CtrlTree::autoScroll()
+{
+    // Find the current playing stream
+    int playIndex = 0;
+    VarTree::Iterator it;
+
+    for( it = m_flat ? m_rTree.firstLeaf() : m_rTree.begin();
+         it != m_rTree.end();
+         it = m_flat ? m_rTree.getNextLeaf( it )
+                     : m_rTree.getNextItem( it ) )
+    {
+        if( it->m_playing )
+        {
+           m_rTree.ensureExpanded( it );
+           break;
+        }
+    }
+    for( it = m_flat ? m_rTree.firstLeaf() : m_rTree.begin();
+         it != m_rTree.end();
+         it = m_flat ? m_rTree.getNextLeaf( it )
+                     : m_rTree.getNextVisibleItem( it ) )
+    {
+        if( it->m_playing )
+           break;
+        playIndex++;
+    }
+
+    if( it == m_rTree.end() ) return;
+
+
+    ensureVisible( playIndex );
+}
+
+
+void CtrlTree::makeImage()
+{
+    stats_TimerStart( getIntf(), "[Skins] Playlist image",
+                      STATS_TIMER_SKINS_PLAYTREE_IMAGE );
+    delete m_pImage;
 
     // Get the size of the control
     const Position *pPos = getPosition();
     if( !pPos )
     {
+        stats_TimerStop( getIntf(), STATS_TIMER_SKINS_PLAYTREE_IMAGE );
         return;
     }
     int width = pPos->getWidth();
@@ -402,7 +744,7 @@ void CtrlTree::makeImage()
     OSFactory *pOsFactory = OSFactory::instance( getIntf() );
     m_pImage = pOsFactory->createOSGraphics( width, height );
 
-    VarTree::Iterator it = m_lastPos;
+    VarTree::Iterator it = m_firstPos;
 
     if( m_pBgBitmap )
     {
@@ -410,18 +752,21 @@ void CtrlTree::makeImage()
         ScaledBitmap bmp( getIntf(), *m_pBgBitmap, width, height );
         m_pImage->drawBitmap( bmp, 0, 0 );
 
-        // FIXME : Take care of the selection color
         for( int yPos = 0; yPos < height; yPos += i_itemHeight )
         {
-            int rectHeight = __MIN( i_itemHeight, height - yPos );
             if( it != m_rTree.end() )
             {
                 if( (*it).m_selected )
                 {
+                    int rectHeight = __MIN( i_itemHeight, height - yPos );
                     m_pImage->fillRect( 0, yPos, width, rectHeight,
                                         m_selColor );
                 }
-                IT_DISP_LOOP_END( it );
+                do
+                {
+                    it = m_flat ? m_rTree.getNextLeaf( it )
+                                : m_rTree.getNextVisibleItem( it );
+                } while( it != m_rTree.end() && it->m_deleted );
             }
         }
     }
@@ -438,7 +783,11 @@ void CtrlTree::makeImage()
             {
                 uint32_t color = ( it->m_selected ? m_selColor : bgColor );
                 m_pImage->fillRect( 0, yPos, width, rectHeight, color );
-                IT_DISP_LOOP_END( it );
+                do
+                {
+                    it = m_flat ? m_rTree.getNextLeaf( it )
+                                : m_rTree.getNextVisibleItem( it );
+                } while( it != m_rTree.end() && it->m_deleted );
             }
             else
             {
@@ -447,48 +796,46 @@ void CtrlTree::makeImage()
             bgColor = ( bgColor == m_bgColor1 ? m_bgColor2 : m_bgColor1 );
         }
     }
-//    fprintf( stderr, "done\n");
 
-    int bitmapWidth = 5;
-    if( m_pClosedBitmap )
-    {
-        bitmapWidth = __MAX( m_pClosedBitmap->getWidth(), bitmapWidth );
-    }
-    if( m_pOpenBitmap )
-    {
-        bitmapWidth = __MAX( m_pOpenBitmap->getWidth(), bitmapWidth );
-    }
-    if( m_pItemBitmap )
-    {
-        bitmapWidth = __MAX( m_pItemBitmap->getWidth(), bitmapWidth );
-    }
-    bitmapWidth += 2;
+    int bitmapWidth = itemImageWidth();
 
-    // FIXME : Draw the items
     int yPos = 0;
-    it = m_lastPos;
+    it = m_firstPos;
     while( it != m_rTree.end() && yPos < height )
     {
         const GenericBitmap *m_pCurBitmap;
         UString *pStr = (UString*)(it->m_cString.get());
         uint32_t color = ( it->m_playing ? m_playColor : m_fgColor );
+
         // Draw the text
-        if( pStr != NULL ){
-            int depth = it->depth();
+        if( pStr != NULL )
+        {
+            int depth = m_flat ? 1 : it->depth();
             GenericBitmap *pText = m_rFont.drawString( *pStr, color, width - bitmapWidth * depth );
             if( !pText )
             {
+                stats_TimerStop( getIntf(), STATS_TIMER_SKINS_PLAYTREE_IMAGE );
                 return;
             }
-            m_pCurBitmap = it->size() ? ( it->m_expanded ? m_pOpenBitmap : m_pClosedBitmap ) : m_pItemBitmap ;
+            if( it->size() )
+                m_pCurBitmap = it->m_expanded ? m_pOpenBitmap : m_pClosedBitmap;
+            else
+                m_pCurBitmap = m_pItemBitmap;
+
             if( m_pCurBitmap )
             {
+                // Make sure we are centered on the line
                 int yPos2 = yPos+(i_itemHeight-m_pCurBitmap->getHeight()+1)/2;
-                m_pImage->drawBitmap( *m_pCurBitmap, 0, 0, bitmapWidth * (depth - 1 ), yPos2, m_pCurBitmap->getWidth(), __MIN( m_pCurBitmap->getHeight(), height -  yPos2), true );
-            }
-            else
-            {
-                /* it would be nice to draw something */
+                if( yPos2 >= height )
+                {
+                    delete pText;
+                    break;
+                }
+                m_pImage->drawBitmap( *m_pCurBitmap, 0, 0,
+                                      bitmapWidth * (depth - 1 ), yPos2,
+                                      m_pCurBitmap->getWidth(),
+                                      __MIN( m_pCurBitmap->getHeight(),
+                                             height -  yPos2), true );
             }
             yPos += i_itemHeight - pText->getHeight();
             int ySrc = 0;
@@ -497,13 +844,33 @@ void CtrlTree::makeImage()
                 ySrc = - yPos;
                 yPos = 0;
             }
-            int lineHeight =  __MIN( pText->getHeight() - ySrc, height - yPos );
+            int lineHeight = __MIN( pText->getHeight() - ySrc, height - yPos );
             m_pImage->drawBitmap( *pText, 0, ySrc, bitmapWidth * depth, yPos,
                                   pText->getWidth(),
                                   lineHeight, true );
             yPos += (pText->getHeight() - ySrc );
             delete pText;
         }
-        IT_DISP_LOOP_END( it );
+        do
+        {
+            it = m_flat ? m_rTree.getNextLeaf( it )
+                : m_rTree.getNextVisibleItem( it );
+        } while( it != m_rTree.end() && it->m_deleted );
     }
+    stats_TimerStop( getIntf(), STATS_TIMER_SKINS_PLAYTREE_IMAGE );
+}
+
+VarTree::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_flat ? m_rTree.getNextLeaf( it )
+                     : m_rTree.getNextVisibleItem( it ) )
+    {
+        pos--;
+    }
+
+    return it;
 }