]> git.sesse.net Git - vlc/blob - modules/gui/skins2/controls/ctrl_tree.cpp
31d9cb503e484d75cf2ed306a0cd5bae9e93a372
[vlc] / modules / gui / skins2 / controls / ctrl_tree.cpp
1 /*****************************************************************************
2  * ctrl_tree.cpp
3  *****************************************************************************
4  * Copyright (C) 2003 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Antoine Cellerier <dionoea@videolan.org>
8  *          ClĂ©ment Stenac <zorglub@videolan.org>
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23  *****************************************************************************/
24
25 #include <math.h>
26 #include "../utils/var_bool.hpp"
27 #include "ctrl_tree.hpp"
28 #include "../src/os_factory.hpp"
29 #include "../src/os_graphics.hpp"
30 #include "../src/generic_bitmap.hpp"
31 #include "../src/generic_font.hpp"
32 #include "../src/scaled_bitmap.hpp"
33 #include "../utils/position.hpp"
34 #include "../utils/ustring.hpp"
35 #include "../events/evt_key.hpp"
36 #include "../events/evt_mouse.hpp"
37 #include "../events/evt_scroll.hpp"
38 #include <vlc_keys.h>
39
40 #define SCROLL_STEP 0.05
41 #define LINE_INTERVAL 1  // Number of pixels inserted between 2 lines
42
43
44 CtrlTree::CtrlTree( intf_thread_t *pIntf,
45                     VarTree &rTree,
46                     const GenericFont &rFont,
47                     const GenericBitmap *pBgBitmap,
48                     const GenericBitmap *pItemBitmap,
49                     const GenericBitmap *pOpenBitmap,
50                     const GenericBitmap *pClosedBitmap,
51                     uint32_t fgColor,
52                     uint32_t playColor,
53                     uint32_t bgColor1,
54                     uint32_t bgColor2,
55                     uint32_t selColor,
56                     const UString &rHelp,
57                     VarBool *pVisible,
58                     VarBool *pFlat ):
59     CtrlGeneric( pIntf,rHelp, pVisible), m_rTree( rTree), m_rFont( rFont ),
60     m_pBgBitmap( pBgBitmap ), m_pItemBitmap( pItemBitmap ),
61     m_pOpenBitmap( pOpenBitmap ), m_pClosedBitmap( pClosedBitmap ),
62     m_fgColor( fgColor ), m_playColor( playColor ), m_bgColor1( bgColor1 ),
63     m_bgColor2( bgColor2 ), m_selColor( selColor ),
64     m_pLastSelected( NULL ), m_pImage( NULL ), m_dontMove( false )
65 {
66     // Observe the tree and position variables
67     m_rTree.addObserver( this );
68     m_rTree.getPositionVar().addObserver( this );
69
70     m_flat = pFlat->get();
71
72     m_firstPos = m_flat ? m_rTree.firstLeaf() : m_rTree.begin();
73
74     makeImage();
75 }
76
77 CtrlTree::~CtrlTree()
78 {
79     m_rTree.getPositionVar().delObserver( this );
80     m_rTree.delObserver( this );
81     delete m_pImage;
82 }
83
84 int CtrlTree::itemHeight()
85 {
86     int itemHeight = m_rFont.getSize();
87     if( !m_flat )
88     {
89         if( m_pClosedBitmap )
90         {
91             itemHeight = __MAX( m_pClosedBitmap->getHeight(), itemHeight );
92         }
93         if( m_pOpenBitmap )
94         {
95             itemHeight = __MAX( m_pOpenBitmap->getHeight(), itemHeight );
96         }
97     }
98     if( m_pItemBitmap )
99     {
100         itemHeight = __MAX( m_pItemBitmap->getHeight(), itemHeight );
101     }
102     itemHeight += LINE_INTERVAL;
103     return itemHeight;
104 }
105
106 int CtrlTree::itemImageWidth()
107 {
108     int bitmapWidth = 5;
109     if( !m_flat )
110     {
111         if( m_pClosedBitmap )
112         {
113             bitmapWidth = __MAX( m_pClosedBitmap->getWidth(), bitmapWidth );
114         }
115         if( m_pOpenBitmap )
116         {
117             bitmapWidth = __MAX( m_pOpenBitmap->getWidth(), bitmapWidth );
118         }
119     }
120     if( m_pItemBitmap )
121     {
122         bitmapWidth = __MAX( m_pItemBitmap->getWidth(), bitmapWidth );
123     }
124     return bitmapWidth + 2;
125 }
126
127 int CtrlTree::maxItems()
128 {
129     const Position *pPos = getPosition();
130     if( !pPos )
131     {
132         return -1;
133     }
134     return pPos->getHeight() / itemHeight();
135 }
136
137
138 void CtrlTree::onUpdate( Subject<VarTree, tree_update> &rTree,
139                          tree_update *arg )
140 {
141     if( arg->i_type == 0 ) // Item update
142     {
143         if( arg->b_active_item )
144         {
145             autoScroll();
146             ///\todo We should make image if we are visible in the view
147             makeImage();
148         }
149     }
150     /// \todo handle delete in a more clever way
151     else if ( arg->i_type == 1 ) // Global change or deletion
152     {
153         m_firstPos = m_flat ? m_rTree.firstLeaf() : m_rTree.begin();
154         makeImage();
155     }
156     else if ( arg->i_type == 2 ) // Item-append
157     {
158         if( m_flat && m_firstPos->size() )
159             m_firstPos = m_rTree.getNextLeaf( m_firstPos );
160         /// \todo Check if the item is really visible in the view
161         // (we only check if it in the document)
162         if( arg->b_visible == true )
163         {
164             makeImage();
165         }
166     }
167     else if( arg->i_type == 3 ) // item-del
168     {
169         /* Make sure firstPos and lastSelected are still valid */
170         while( m_firstPos->m_deleted && m_firstPos != m_rTree.root()->begin() )
171         {
172             m_firstPos = m_flat ? m_rTree.getPrevLeaf( m_firstPos )
173                                 : m_rTree.getPrevVisibleItem( m_firstPos );
174         }
175         if( m_firstPos->m_deleted )
176             m_firstPos = m_flat ? m_rTree.firstLeaf()
177                                 : m_rTree.root()->begin();
178
179         if( arg->b_visible == true )
180         {
181             makeImage();
182         }
183     }
184     notifyLayout();
185 }
186
187 void CtrlTree::onUpdate( Subject<VarPercent> &rPercent, void* arg)
188 {
189     // Determine what is the first item to display
190     VarTree::Iterator it = m_flat ? m_rTree.firstLeaf() : m_rTree.begin();
191
192     if( m_dontMove ) return;
193
194     int excessItems;
195     if( m_flat )
196         excessItems = m_rTree.countLeafs() - maxItems();
197     else
198         excessItems = m_rTree.visibleItems() - maxItems();
199
200     if( excessItems > 0)
201     {
202         VarPercent &rVarPos = m_rTree.getPositionVar();
203         // a simple (int)(...) causes rounding errors !
204 #ifdef _MSC_VER
205 #   define lrint (int)
206 #endif
207         if( m_flat )
208             it = m_rTree.getLeaf(lrint( (1.0 - rVarPos.get()) * (double)excessItems ) + 1 );
209         else
210             it = m_rTree.getVisibleItem(lrint( (1.0 - rVarPos.get()) * (double)excessItems ) + 1 );
211     }
212     if( m_firstPos != it )
213     {
214         // Redraw the control if the position has changed
215         m_firstPos = it;
216         makeImage();
217         notifyLayout();
218     }
219 }
220
221 void CtrlTree::onResize()
222 {
223     // Determine what is the first item to display
224     VarTree::Iterator it = m_flat ? m_rTree.firstLeaf() : m_rTree.begin();
225
226     int excessItems;
227     if( m_flat )
228         excessItems = m_rTree.countLeafs() - maxItems();
229     else
230         excessItems = m_rTree.visibleItems() - maxItems();
231
232     if( excessItems > 0)
233     {
234         VarPercent &rVarPos = m_rTree.getPositionVar();
235         // a simple (int)(...) causes rounding errors !
236 #ifdef _MSC_VER
237 #   define lrint (int)
238 #endif
239         if( m_flat )
240             it = m_rTree.getLeaf(lrint( (1.0 - rVarPos.get()) * (double)excessItems ) + 1 );
241         else
242             it = m_rTree.getVisibleItem(lrint( (1.0 - rVarPos.get()) * (double)excessItems ) + 1 );
243     }
244     // Redraw the control if the position has changed
245     m_firstPos = it;
246     makeImage();
247 }
248
249 void CtrlTree::onPositionChange()
250 {
251     makeImage();
252 }
253
254 void CtrlTree::handleEvent( EvtGeneric &rEvent )
255 {
256     bool bChangedPosition = false;
257     VarTree::Iterator toShow; bool needShow = false;
258     if( rEvent.getAsString().find( "key:down" ) != string::npos )
259     {
260         int key = ((EvtKey&)rEvent).getKey();
261         VarTree::Iterator it;
262         bool previousWasSelected = false;
263
264         /* Delete the selection */
265         if( key == KEY_DELETE )
266         {
267             /* Find first non selected item before m_pLastSelected */
268             VarTree::Iterator it_sel = m_flat ? m_rTree.firstLeaf()
269                                               : m_rTree.begin();
270             for( it = m_flat ? m_rTree.firstLeaf() : m_rTree.begin();
271                  it != m_rTree.end();
272                  it = m_flat ? m_rTree.getNextLeaf( it )
273                              : m_rTree.getNextVisibleItem( it ) )
274             {
275                 if( &*it == m_pLastSelected ) break;
276                 if( !it->m_selected ) it_sel = it;
277             }
278
279             /* Delete selected stuff */
280             m_rTree.delSelected();
281
282             /* Select it_sel */
283             it_sel->m_selected = true;
284             m_pLastSelected = &*it_sel;
285         }
286         else if( key == KEY_PAGEDOWN )
287         {
288             it = m_firstPos;
289             int i = (int)(maxItems()*1.5);
290             while( i >= 0 )
291             {
292                 VarTree::Iterator it_old = it;
293                 it = m_flat ? m_rTree.getNextLeaf( it )
294                             : m_rTree.getNextVisibleItem( it );
295                 /* End is already visible, dont' scroll */
296                 if( it == m_rTree.end() )
297                 {
298                     it = it_old;
299                     break;
300                 }
301                 needShow = true;
302                 i--;
303             }
304             if( needShow )
305             {
306                 ensureVisible( it );
307                 makeImage();
308                 notifyLayout();
309                 return;
310             }
311         }
312         else if (key == KEY_PAGEUP )
313         {
314             it = m_firstPos;
315             int i = maxItems();
316             while( i >= maxItems()/2 )
317             {
318                 it = m_flat ? m_rTree.getPrevLeaf( it )
319                             : m_rTree.getPrevVisibleItem( it );
320                 /* End is already visible, dont' scroll */
321                 if( it == ( m_flat ? m_rTree.firstLeaf() : m_rTree.begin() ) )
322                 {
323                     break;
324                 }
325                 i--;
326             }
327             ensureVisible( it );
328             makeImage();
329             notifyLayout();
330             return;
331         }
332
333
334         for( it = m_flat ? m_rTree.firstLeaf() : m_rTree.begin();
335              it != m_rTree.end();
336              it = m_flat ? m_rTree.getNextLeaf( it )
337                          : m_rTree.getNextVisibleItem( it ) )
338         {
339             VarTree::Iterator next = m_flat ? m_rTree.getNextLeaf( it )
340                                             : m_rTree.getNextVisibleItem( it );
341             if( key == KEY_UP )
342             {
343                 // Scroll up one item
344                 if( ( it->parent()
345                       && it != it->parent()->begin() )
346                     || &*it != m_pLastSelected )
347                 {
348                     bool nextWasSelected = ( &*next == m_pLastSelected );
349                     it->m_selected = nextWasSelected;
350                     if( nextWasSelected )
351                     {
352                         m_pLastSelected = &*it;
353                         needShow = true; toShow = it;
354                     }
355                 }
356             }
357             else if( key == KEY_DOWN )
358             {
359                 // Scroll down one item
360                 if( ( it->parent()
361                       && next != it->parent()->end() )
362                     || &*it != m_pLastSelected )
363                 {
364                     (*it).m_selected = previousWasSelected;
365                 }
366                 if( previousWasSelected )
367                 {
368                     m_pLastSelected = &*it;
369                     needShow = true; toShow = it;
370                     previousWasSelected = false;
371                 }
372                 else
373                 {
374                     previousWasSelected = ( &*it == m_pLastSelected );
375                 }
376
377                 // Fix last tree item selection
378                 if( ( m_flat ? m_rTree.getNextLeaf( it )
379                     : m_rTree.getNextVisibleItem( it ) ) == m_rTree.end()
380                  && &*it == m_pLastSelected )
381                 {
382                     (*it).m_selected = true;
383                 }
384             }
385             else if( key == KEY_RIGHT )
386             {
387                 // Go down one level (and expand node)
388                 if( &*it == m_pLastSelected )
389                 {
390                     if( it->m_expanded )
391                     {
392                         if( it->size() )
393                         {
394                             it->m_selected = false;
395                             it->begin()->m_selected = true;
396                             m_pLastSelected = &*(it->begin());
397                         }
398                         else
399                         {
400                             m_rTree.action( &*it );
401                         }
402                     }
403                     else
404                     {
405                         it->m_expanded = true;
406                         bChangedPosition = true;
407                     }
408                 }
409             }
410             else if( key == KEY_LEFT )
411             {
412                 // Go up one level (and close node)
413                 if( &*it == m_pLastSelected )
414                 {
415                     if( it->m_expanded && it->size() )
416                     {
417                         it->m_expanded = false;
418                         bChangedPosition = true;
419                     }
420                     else
421                     {
422                         if( it->parent() && it->parent() != &m_rTree)
423                         {
424                             it->m_selected = false;
425                             m_pLastSelected = it->parent();
426                             m_pLastSelected->m_selected = true;
427                         }
428                     }
429                 }
430             }
431             else if( key == KEY_ENTER || key == ' ' )
432             {
433                 // Go up one level (and close node)
434                 if( &*it == m_pLastSelected )
435                 {
436                     m_rTree.action( &*it );
437                 }
438             }
439         }
440         if( needShow )
441             ensureVisible( toShow );
442
443         // Redraw the control
444         makeImage();
445         notifyLayout();
446     }
447
448     else if( rEvent.getAsString().find( "mouse:left" ) != string::npos )
449     {
450         EvtMouse &rEvtMouse = (EvtMouse&)rEvent;
451         const Position *pos = getPosition();
452         int yPos = ( rEvtMouse.getYPos() - pos->getTop() ) / itemHeight();
453         int xPos = rEvtMouse.getXPos() - pos->getLeft();
454         VarTree::Iterator it;
455
456         if( rEvent.getAsString().find( "mouse:left:down:ctrl,shift" ) !=
457             string::npos )
458         {
459             VarTree::Iterator itClicked = findItemAtPos( yPos );
460             // Flag to know if the current item must be selected
461             bool select = false;
462             for( it = m_flat ? m_rTree.firstLeaf() : m_rTree.begin();
463                  it != m_rTree.end();
464                  it = m_flat ? m_rTree.getNextLeaf( it )
465                              : m_rTree.getNextVisibleItem( it ) )
466             {
467                 bool nextSelect = select;
468                 if( it == itClicked || &*it == m_pLastSelected )
469                 {
470                     if( select )
471                     {
472                         nextSelect = false;
473                     }
474                     else
475                     {
476                         select = true;
477                         nextSelect = true;
478                     }
479                 }
480                 it->m_selected = (*it).m_selected || select;
481                 select = nextSelect;
482             }
483         }
484         else if( rEvent.getAsString().find( "mouse:left:down:ctrl" ) !=
485                  string::npos )
486         {
487             // Invert the selection of the item
488             it = findItemAtPos( yPos );
489             if( it != m_rTree.end() )
490             {
491                 it->m_selected = !it->m_selected;
492                 m_pLastSelected = &*it;
493             }
494         }
495         else if( rEvent.getAsString().find( "mouse:left:down:shift" ) !=
496                  string::npos )
497         {
498             VarTree::Iterator itClicked = findItemAtPos( yPos );
499             // Flag to know if the current item must be selected
500             bool select = false;
501             for( it = m_flat ? m_rTree.firstLeaf() : m_rTree.begin();
502                  it != m_rTree.end();
503                  it = m_flat ? m_rTree.getNextLeaf( it )
504                              : m_rTree.getNextVisibleItem( it ) )
505             {
506                 bool nextSelect = select;
507                 if( it == itClicked || &*it == m_pLastSelected )
508                 {
509                     if( select )
510                     {
511                         nextSelect = false;
512                     }
513                     else
514                     {
515                         select = true;
516                         nextSelect = true;
517                     }
518                 }
519                 it->m_selected = select;
520                 select = nextSelect;
521             }
522         }
523         else if( rEvent.getAsString().find( "mouse:left:down" ) !=
524                  string::npos )
525         {
526             it = findItemAtPos(yPos);
527             if( it != m_rTree.end() )
528             {
529                 if( ( it->size() && xPos > (it->depth() - 1) * itemImageWidth()
530                       && xPos < it->depth() * itemImageWidth() )
531                  && !m_flat )
532                 {
533                     // Fold/unfold the item
534                     it->m_expanded = !it->m_expanded;
535                     bChangedPosition = true;
536                 }
537                 else
538                 {
539                     // Unselect any previously selected item
540                     VarTree::Iterator it2;
541                     for( it2 = m_flat ? m_rTree.firstLeaf() : m_rTree.begin();
542                          it2 != m_rTree.end();
543                          it2 = m_flat ? m_rTree.getNextLeaf( it2 )
544                                       : m_rTree.getNextVisibleItem( it2 ) )
545                     {
546                         it2->m_selected = false;
547                     }
548                     // Select the new item
549                     if( it != m_rTree.end() )
550                     {
551                         it->m_selected = true;
552                         m_pLastSelected = &*it;
553                     }
554                 }
555             }
556         }
557
558         else if( rEvent.getAsString().find( "mouse:left:dblclick" ) !=
559                  string::npos )
560         {
561             it = findItemAtPos(yPos);
562             if( it != m_rTree.end() )
563             {
564                // Execute the action associated to this item
565                m_rTree.action( &*it );
566             }
567         }
568         // Redraw the control
569         makeImage();
570         notifyLayout();
571     }
572
573     else if( rEvent.getAsString().find( "scroll" ) != string::npos )
574     {
575         // XXX ctrl_slider.cpp has two more (but slightly different)
576         // XXX implementations of `scroll'. Figure out where it belongs.
577
578         int direction = static_cast<EvtScroll&>(rEvent).getDirection();
579
580         double percentage = m_rTree.getPositionVar().get();
581         double step = 2.0 / (double)( m_flat ? m_rTree.countLeafs()
582                                              : m_rTree.visibleItems() );
583         if( direction == EvtScroll::kUp )
584         {
585             percentage += step;
586         }
587         else
588         {
589             percentage -= step;
590         }
591         m_rTree.getPositionVar().set( percentage );
592     }
593
594     /* We changed the nodes, let's fix teh position var */
595     if( bChangedPosition )
596     {
597         VarTree::Iterator it;
598         int i = 0;
599         int iFirst = 0;
600         for( it = m_flat ? m_rTree.firstLeaf() : m_rTree.begin();
601              it != m_rTree.end();
602              it = m_flat ? m_rTree.getNextLeaf( it )
603                          : m_rTree.getNextVisibleItem( it ) )
604         {
605             i++;
606             if( it == m_firstPos )
607             {
608                 iFirst = i;
609                 break;
610             }
611         }
612         iFirst += maxItems();
613         if( iFirst >= m_flat ? m_rTree.countLeafs() : m_rTree.visibleItems() )
614             iFirst = m_flat ? m_rTree.countLeafs() : m_rTree.visibleItems();
615         float f_new = (float)iFirst / (float)( m_flat ? m_rTree.countLeafs()
616                                                       :m_rTree.visibleItems() );
617         m_dontMove = true;
618         m_rTree.getPositionVar().set( 1.0 - f_new );
619         m_dontMove = false;
620     }
621 }
622
623 bool CtrlTree::mouseOver( int x, int y ) const
624 {
625     const Position *pPos = getPosition();
626     return !pPos ? false :
627         x >= 0 && x <= pPos->getWidth() && y >= 0 && y <= pPos->getHeight();
628 }
629
630 void CtrlTree::draw( OSGraphics &rImage, int xDest, int yDest )
631 {
632     if( m_pImage )
633         rImage.drawGraphics( *m_pImage, 0, 0, xDest, yDest );
634 }
635
636 bool CtrlTree::ensureVisible( VarTree::Iterator item )
637 {
638     // Find the item to focus
639     int focusItemIndex = 0;
640     VarTree::Iterator it;
641
642     m_rTree.ensureExpanded( item );
643
644     for( it = m_flat ? m_rTree.firstLeaf() : m_rTree.begin();
645          it != m_rTree.end();
646          it = m_flat ? m_rTree.getNextLeaf( it )
647                      : m_rTree.getNextVisibleItem( it ) )
648     {
649         if( it->m_id == item->m_id ) break;
650         focusItemIndex++;
651     }
652    return ensureVisible( focusItemIndex );
653 }
654
655 bool CtrlTree::ensureVisible( int focusItemIndex )
656 {
657     // Find  m_firstPos
658     VarTree::Iterator it;
659     int firstPosIndex = 0;
660     for( it = m_flat ? m_rTree.firstLeaf() : m_rTree.begin();
661          it != m_rTree.end();
662          it = m_flat ? m_rTree.getNextLeaf( it )
663                      : m_rTree.getNextVisibleItem( it ) )
664     {
665         if( it == m_firstPos ) break;
666         firstPosIndex++;
667     }
668
669     if( it == m_rTree.end() ) return false;
670
671
672     if( it != m_rTree.end()
673         && ( focusItemIndex < firstPosIndex
674            || focusItemIndex > firstPosIndex + maxItems() ) )
675     {
676         // Scroll to have the wanted stream visible
677         VarPercent &rVarPos = m_rTree.getPositionVar();
678         rVarPos.set( 1.0 - (double)focusItemIndex /
679                            (double)( m_flat ? m_rTree.countLeafs()
680                                             : m_rTree.visibleItems() ) );
681         return true;
682     }
683     return false;
684 }
685
686 void CtrlTree::autoScroll()
687 {
688     // Find the current playing stream
689     int playIndex = 0;
690     VarTree::Iterator it;
691
692     for( it = m_flat ? m_rTree.firstLeaf() : m_rTree.begin();
693          it != m_rTree.end();
694          it = m_flat ? m_rTree.getNextLeaf( it )
695                      : m_rTree.getNextItem( it ) )
696     {
697         if( it->m_playing )
698         {
699            m_rTree.ensureExpanded( it );
700            break;
701         }
702     }
703     for( it = m_flat ? m_rTree.firstLeaf() : m_rTree.begin();
704          it != m_rTree.end();
705          it = m_flat ? m_rTree.getNextLeaf( it )
706                      : m_rTree.getNextVisibleItem( it ) )
707     {
708         if( it->m_playing )
709            break;
710         playIndex++;
711     }
712
713     if( it == m_rTree.end() ) return;
714
715
716     ensureVisible( playIndex );
717 }
718
719
720 void CtrlTree::makeImage()
721 {
722     stats_TimerStart( getIntf(), "[Skins] Playlist image",
723                       STATS_TIMER_SKINS_PLAYTREE_IMAGE );
724     delete m_pImage;
725
726     // Get the size of the control
727     const Position *pPos = getPosition();
728     if( !pPos )
729     {
730         stats_TimerStop( getIntf(), STATS_TIMER_SKINS_PLAYTREE_IMAGE );
731         return;
732     }
733     int width = pPos->getWidth();
734     int height = pPos->getHeight();
735
736     int i_itemHeight = itemHeight();
737
738     // Create an image
739     OSFactory *pOsFactory = OSFactory::instance( getIntf() );
740     m_pImage = pOsFactory->createOSGraphics( width, height );
741
742     VarTree::Iterator it = m_firstPos;
743
744     if( m_pBgBitmap )
745     {
746         // Draw the background bitmap
747         ScaledBitmap bmp( getIntf(), *m_pBgBitmap, width, height );
748         m_pImage->drawBitmap( bmp, 0, 0 );
749
750         for( int yPos = 0; yPos < height; yPos += i_itemHeight )
751         {
752             if( it != m_rTree.end() )
753             {
754                 if( (*it).m_selected )
755                 {
756                     int rectHeight = __MIN( i_itemHeight, height - yPos );
757                     m_pImage->fillRect( 0, yPos, width, rectHeight,
758                                         m_selColor );
759                 }
760                 do
761                 {
762                     it = m_flat ? m_rTree.getNextLeaf( it )
763                                 : m_rTree.getNextVisibleItem( it );
764                 } while( it != m_rTree.end() && it->m_deleted );
765             }
766         }
767     }
768     else
769     {
770         // FIXME (TRYME)
771         // Fill background with background color
772         uint32_t bgColor = m_bgColor1;
773         m_pImage->fillRect( 0, 0, width, height, bgColor );
774         for( int yPos = 0; yPos < height; yPos += i_itemHeight )
775         {
776             int rectHeight = __MIN( i_itemHeight, height - yPos );
777             if( it == m_rTree.end() )
778                 m_pImage->fillRect( 0, yPos, width, rectHeight, bgColor );
779             else
780             {
781                 uint32_t color = ( it->m_selected ? m_selColor : bgColor );
782                 m_pImage->fillRect( 0, yPos, width, rectHeight, color );
783                 do
784                 {
785                     it = m_flat ? m_rTree.getNextLeaf( it )
786                                 : m_rTree.getNextVisibleItem( it );
787                 } while( it != m_rTree.end() && it->m_deleted );
788             }
789             bgColor = ( bgColor == m_bgColor1 ? m_bgColor2 : m_bgColor1 );
790         }
791     }
792
793     int bitmapWidth = itemImageWidth();
794
795     int yPos = 0;
796     it = m_firstPos;
797     while( it != m_rTree.end() && yPos < height )
798     {
799         const GenericBitmap *m_pCurBitmap;
800         UString *pStr = (UString*)(it->m_cString.get());
801         uint32_t color = ( it->m_playing ? m_playColor : m_fgColor );
802
803         // Draw the text
804         if( pStr != NULL )
805         {
806             int depth = m_flat ? 1 : it->depth();
807             GenericBitmap *pText = m_rFont.drawString( *pStr, color, width - bitmapWidth * depth );
808             if( !pText )
809             {
810                 stats_TimerStop( getIntf(), STATS_TIMER_SKINS_PLAYTREE_IMAGE );
811                 return;
812             }
813             if( it->size() )
814                 m_pCurBitmap = it->m_expanded ? m_pOpenBitmap : m_pClosedBitmap;
815             else
816                 m_pCurBitmap = m_pItemBitmap;
817
818             if( m_pCurBitmap )
819             {
820                 // Make sure we are centered on the line
821                 int yPos2 = yPos+(i_itemHeight-m_pCurBitmap->getHeight()+1)/2;
822                 if( yPos2 >= height )
823                 {
824                     delete pText;
825                     break;
826                 }
827                 m_pImage->drawBitmap( *m_pCurBitmap, 0, 0,
828                                       bitmapWidth * (depth - 1 ), yPos2,
829                                       m_pCurBitmap->getWidth(),
830                                       __MIN( m_pCurBitmap->getHeight(),
831                                              height -  yPos2), true );
832             }
833             yPos += i_itemHeight - pText->getHeight();
834             int ySrc = 0;
835             if( yPos < 0 )
836             {
837                 ySrc = - yPos;
838                 yPos = 0;
839             }
840             int lineHeight = __MIN( pText->getHeight() - ySrc, height - yPos );
841             m_pImage->drawBitmap( *pText, 0, ySrc, bitmapWidth * depth, yPos,
842                                   pText->getWidth(),
843                                   lineHeight, true );
844             yPos += (pText->getHeight() - ySrc );
845             delete pText;
846         }
847         do
848         {
849             it = m_flat ? m_rTree.getNextLeaf( it )
850                 : m_rTree.getNextVisibleItem( it );
851         } while( it != m_rTree.end() && it->m_deleted );
852     }
853     stats_TimerStop( getIntf(), STATS_TIMER_SKINS_PLAYTREE_IMAGE );
854 }
855
856 VarTree::Iterator CtrlTree::findItemAtPos( int pos )
857 {
858     // The first item is m_firstPos.
859     // We decrement pos as we try the other items, until pos == 0.
860     VarTree::Iterator it;
861     for( it = m_firstPos; it != m_rTree.end() && pos != 0;
862          it = m_flat ? m_rTree.getNextLeaf( it )
863                      : m_rTree.getNextVisibleItem( it ) )
864     {
865         pos--;
866     }
867
868     return it;
869 }