]> git.sesse.net Git - vlc/blob - modules/gui/skins2/controls/ctrl_tree.cpp
KEY_SPACE = 32, simplify several outputs and interfaces
[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     notifyLayout();
248 }
249
250 void CtrlTree::onPositionChange()
251 {
252     makeImage();
253     notifyLayout();
254 }
255
256 void CtrlTree::handleEvent( EvtGeneric &rEvent )
257 {
258     bool bChangedPosition = false;
259     VarTree::Iterator toShow; bool needShow = false;
260     if( rEvent.getAsString().find( "key:down" ) != string::npos )
261     {
262         int key = ((EvtKey&)rEvent).getKey();
263         VarTree::Iterator it;
264         bool previousWasSelected = false;
265
266         /* Delete the selection */
267         if( key == KEY_DELETE )
268         {
269             /* Find first non selected item before m_pLastSelected */
270             VarTree::Iterator it_sel = m_flat ? m_rTree.firstLeaf()
271                                               : m_rTree.begin();
272             for( it = m_flat ? m_rTree.firstLeaf() : m_rTree.begin();
273                  it != m_rTree.end();
274                  it = m_flat ? m_rTree.getNextLeaf( it )
275                              : m_rTree.getNextVisibleItem( it ) )
276             {
277                 if( &*it == m_pLastSelected ) break;
278                 if( !it->m_selected ) it_sel = it;
279             }
280
281             /* Delete selected stuff */
282             m_rTree.delSelected();
283
284             /* Select it_sel */
285             it_sel->m_selected = true;
286             m_pLastSelected = &*it_sel;
287         }
288         else if( key == KEY_PAGEDOWN )
289         {
290             it = m_firstPos;
291             int i = (int)(maxItems()*1.5);
292             while( i >= 0 )
293             {
294                 VarTree::Iterator it_old = it;
295                 it = m_flat ? m_rTree.getNextLeaf( it )
296                             : m_rTree.getNextVisibleItem( it );
297                 /* End is already visible, dont' scroll */
298                 if( it == m_rTree.end() )
299                 {
300                     it = it_old;
301                     break;
302                 }
303                 needShow = true;
304                 i--;
305             }
306             if( needShow )
307             {
308                 ensureVisible( it );
309                 makeImage();
310                 notifyLayout();
311                 return;
312             }
313         }
314         else if (key == KEY_PAGEUP )
315         {
316             it = m_firstPos;
317             int i = maxItems();
318             while( i >= maxItems()/2 )
319             {
320                 it = m_flat ? m_rTree.getPrevLeaf( it )
321                             : m_rTree.getPrevVisibleItem( it );
322                 /* End is already visible, dont' scroll */
323                 if( it == ( m_flat ? m_rTree.firstLeaf() : m_rTree.begin() ) )
324                 {
325                     break;
326                 }
327                 i--;
328             }
329             ensureVisible( it );
330             makeImage();
331             notifyLayout();
332             return;
333         }
334
335
336         for( it = m_flat ? m_rTree.firstLeaf() : m_rTree.begin();
337              it != m_rTree.end();
338              it = m_flat ? m_rTree.getNextLeaf( it )
339                          : m_rTree.getNextVisibleItem( it ) )
340         {
341             VarTree::Iterator next = m_flat ? m_rTree.getNextLeaf( it )
342                                             : m_rTree.getNextVisibleItem( it );
343             if( key == KEY_UP )
344             {
345                 // Scroll up one item
346                 if( ( it->parent()
347                       && it != it->parent()->begin() )
348                     || &*it != m_pLastSelected )
349                 {
350                     bool nextWasSelected = ( &*next == m_pLastSelected );
351                     it->m_selected = nextWasSelected;
352                     if( nextWasSelected )
353                     {
354                         m_pLastSelected = &*it;
355                         needShow = true; toShow = it;
356                     }
357                 }
358             }
359             else if( key == KEY_DOWN )
360             {
361                 // Scroll down one item
362                 if( ( it->parent()
363                       && next != it->parent()->end() )
364                     || &*it != m_pLastSelected )
365                 {
366                     (*it).m_selected = previousWasSelected;
367                 }
368                 if( previousWasSelected )
369                 {
370                     m_pLastSelected = &*it;
371                     needShow = true; toShow = it;
372                     previousWasSelected = false;
373                 }
374                 else
375                 {
376                     previousWasSelected = ( &*it == m_pLastSelected );
377                 }
378
379                 // Fix last tree item selection
380                 if( ( m_flat ? m_rTree.getNextLeaf( it )
381                     : m_rTree.getNextVisibleItem( it ) ) == m_rTree.end()
382                  && &*it == m_pLastSelected )
383                 {
384                     (*it).m_selected = true;
385                 }
386             }
387             else if( key == KEY_RIGHT )
388             {
389                 // Go down one level (and expand node)
390                 if( &*it == m_pLastSelected )
391                 {
392                     if( it->m_expanded )
393                     {
394                         if( it->size() )
395                         {
396                             it->m_selected = false;
397                             it->begin()->m_selected = true;
398                             m_pLastSelected = &*(it->begin());
399                         }
400                         else
401                         {
402                             m_rTree.action( &*it );
403                         }
404                     }
405                     else
406                     {
407                         it->m_expanded = true;
408                         bChangedPosition = true;
409                     }
410                 }
411             }
412             else if( key == KEY_LEFT )
413             {
414                 // Go up one level (and close node)
415                 if( &*it == m_pLastSelected )
416                 {
417                     if( it->m_expanded && it->size() )
418                     {
419                         it->m_expanded = false;
420                         bChangedPosition = true;
421                     }
422                     else
423                     {
424                         if( it->parent() && it->parent() != &m_rTree)
425                         {
426                             it->m_selected = false;
427                             m_pLastSelected = it->parent();
428                             m_pLastSelected->m_selected = true;
429                         }
430                     }
431                 }
432             }
433             else if( key == KEY_ENTER || key == ' ' )
434             {
435                 // Go up one level (and close node)
436                 if( &*it == m_pLastSelected )
437                 {
438                     m_rTree.action( &*it );
439                 }
440             }
441         }
442         if( needShow )
443             ensureVisible( toShow );
444
445         // Redraw the control
446         makeImage();
447         notifyLayout();
448     }
449
450     else if( rEvent.getAsString().find( "mouse:left" ) != string::npos )
451     {
452         EvtMouse &rEvtMouse = (EvtMouse&)rEvent;
453         const Position *pos = getPosition();
454         int yPos = ( rEvtMouse.getYPos() - pos->getTop() ) / itemHeight();
455         int xPos = rEvtMouse.getXPos() - pos->getLeft();
456         VarTree::Iterator it;
457
458         if( rEvent.getAsString().find( "mouse:left:down:ctrl,shift" ) !=
459             string::npos )
460         {
461             VarTree::Iterator itClicked = findItemAtPos( yPos );
462             // Flag to know if the current item must be selected
463             bool select = false;
464             for( it = m_flat ? m_rTree.firstLeaf() : m_rTree.begin();
465                  it != m_rTree.end();
466                  it = m_flat ? m_rTree.getNextLeaf( it )
467                              : m_rTree.getNextVisibleItem( it ) )
468             {
469                 bool nextSelect = select;
470                 if( it == itClicked || &*it == m_pLastSelected )
471                 {
472                     if( select )
473                     {
474                         nextSelect = false;
475                     }
476                     else
477                     {
478                         select = true;
479                         nextSelect = true;
480                     }
481                 }
482                 it->m_selected = (*it).m_selected || select;
483                 select = nextSelect;
484             }
485         }
486         else if( rEvent.getAsString().find( "mouse:left:down:ctrl" ) !=
487                  string::npos )
488         {
489             // Invert the selection of the item
490             it = findItemAtPos( yPos );
491             if( it != m_rTree.end() )
492             {
493                 it->m_selected = !it->m_selected;
494                 m_pLastSelected = &*it;
495             }
496         }
497         else if( rEvent.getAsString().find( "mouse:left:down:shift" ) !=
498                  string::npos )
499         {
500             VarTree::Iterator itClicked = findItemAtPos( yPos );
501             // Flag to know if the current item must be selected
502             bool select = false;
503             for( it = m_flat ? m_rTree.firstLeaf() : m_rTree.begin();
504                  it != m_rTree.end();
505                  it = m_flat ? m_rTree.getNextLeaf( it )
506                              : m_rTree.getNextVisibleItem( it ) )
507             {
508                 bool nextSelect = select;
509                 if( it == itClicked || &*it == m_pLastSelected )
510                 {
511                     if( select )
512                     {
513                         nextSelect = false;
514                     }
515                     else
516                     {
517                         select = true;
518                         nextSelect = true;
519                     }
520                 }
521                 it->m_selected = select;
522                 select = nextSelect;
523             }
524         }
525         else if( rEvent.getAsString().find( "mouse:left:down" ) !=
526                  string::npos )
527         {
528             it = findItemAtPos(yPos);
529             if( it != m_rTree.end() )
530             {
531                 if( ( it->size() && xPos > (it->depth() - 1) * itemImageWidth()
532                       && xPos < it->depth() * itemImageWidth() )
533                  && !m_flat )
534                 {
535                     // Fold/unfold the item
536                     it->m_expanded = !it->m_expanded;
537                     bChangedPosition = true;
538                 }
539                 else
540                 {
541                     // Unselect any previously selected item
542                     VarTree::Iterator it2;
543                     for( it2 = m_flat ? m_rTree.firstLeaf() : m_rTree.begin();
544                          it2 != m_rTree.end();
545                          it2 = m_flat ? m_rTree.getNextLeaf( it2 )
546                                       : m_rTree.getNextVisibleItem( it2 ) )
547                     {
548                         it2->m_selected = false;
549                     }
550                     // Select the new item
551                     if( it != m_rTree.end() )
552                     {
553                         it->m_selected = true;
554                         m_pLastSelected = &*it;
555                     }
556                 }
557             }
558         }
559
560         else if( rEvent.getAsString().find( "mouse:left:dblclick" ) !=
561                  string::npos )
562         {
563             it = findItemAtPos(yPos);
564             if( it != m_rTree.end() )
565             {
566                // Execute the action associated to this item
567                m_rTree.action( &*it );
568             }
569         }
570         // Redraw the control
571         makeImage();
572         notifyLayout();
573     }
574
575     else if( rEvent.getAsString().find( "scroll" ) != string::npos )
576     {
577         int direction = ((EvtScroll&)rEvent).getDirection();
578
579         double percentage = m_rTree.getPositionVar().get();
580         double step = 2.0 / (double)( m_flat ? m_rTree.countLeafs()
581                                              : m_rTree.visibleItems() );
582         if( direction == EvtScroll::kUp )
583         {
584             percentage += step;
585         }
586         else
587         {
588             percentage -= step;
589         }
590         m_rTree.getPositionVar().set( percentage );
591     }
592
593     /* We changed the nodes, let's fix teh position var */
594     if( bChangedPosition )
595     {
596         VarTree::Iterator it;
597         int i = 0;
598         int iFirst = 0;
599         for( it = m_flat ? m_rTree.firstLeaf() : m_rTree.begin();
600              it != m_rTree.end();
601              it = m_flat ? m_rTree.getNextLeaf( it )
602                          : m_rTree.getNextVisibleItem( it ) )
603         {
604             i++;
605             if( it == m_firstPos )
606             {
607                 iFirst = i;
608                 break;
609             }
610         }
611         iFirst += maxItems();
612         if( iFirst >= m_flat ? m_rTree.countLeafs() : m_rTree.visibleItems() )
613             iFirst = m_flat ? m_rTree.countLeafs() : m_rTree.visibleItems();
614         float f_new = (float)iFirst / (float)( m_flat ? m_rTree.countLeafs()
615                                                       :m_rTree.visibleItems() );
616         m_dontMove = true;
617         m_rTree.getPositionVar().set( 1.0 - f_new );
618         m_dontMove = false;
619     }
620 }
621
622 bool CtrlTree::mouseOver( int x, int y ) const
623 {
624     const Position *pPos = getPosition();
625     return ( pPos
626        ? x >= 0 && x <= pPos->getWidth() && y >= 0 && y <= pPos->getHeight()
627        : false);
628 }
629
630 void CtrlTree::draw( OSGraphics &rImage, int xDest, int yDest )
631 {
632     if( m_pImage )
633     {
634         rImage.drawGraphics( *m_pImage, 0, 0, xDest, yDest );
635     }
636 }
637
638 bool CtrlTree::ensureVisible( VarTree::Iterator item )
639 {
640     // Find the item to focus
641     int focusItemIndex = 0;
642     VarTree::Iterator it;
643
644     m_rTree.ensureExpanded( item );
645
646     for( it = m_flat ? m_rTree.firstLeaf() : m_rTree.begin();
647          it != m_rTree.end();
648          it = m_flat ? m_rTree.getNextLeaf( it )
649                      : m_rTree.getNextVisibleItem( it ) )
650     {
651         if( it->m_id == item->m_id ) break;
652         focusItemIndex++;
653     }
654    return ensureVisible( focusItemIndex );
655 }
656
657 bool CtrlTree::ensureVisible( int focusItemIndex )
658 {
659     // Find  m_firstPos
660     VarTree::Iterator it;
661     int firstPosIndex = 0;
662     for( it = m_flat ? m_rTree.firstLeaf() : m_rTree.begin();
663          it != m_rTree.end();
664          it = m_flat ? m_rTree.getNextLeaf( it )
665                      : m_rTree.getNextVisibleItem( it ) )
666     {
667         if( it == m_firstPos ) break;
668         firstPosIndex++;
669     }
670
671     if( it == m_rTree.end() ) return false;
672
673
674     if( it != m_rTree.end()
675         && ( focusItemIndex < firstPosIndex
676            || focusItemIndex > firstPosIndex + maxItems() ) )
677     {
678         // Scroll to have the wanted stream visible
679         VarPercent &rVarPos = m_rTree.getPositionVar();
680         rVarPos.set( 1.0 - (double)focusItemIndex /
681                            (double)( m_flat ? m_rTree.countLeafs()
682                                             : m_rTree.visibleItems() ) );
683         return true;
684     }
685     return false;
686 }
687
688 void CtrlTree::autoScroll()
689 {
690     // Find the current playing stream
691     int playIndex = 0;
692     VarTree::Iterator it;
693
694     for( it = m_flat ? m_rTree.firstLeaf() : m_rTree.begin();
695          it != m_rTree.end();
696          it = m_flat ? m_rTree.getNextLeaf( it )
697                      : m_rTree.getNextItem( it ) )
698     {
699         if( it->m_playing )
700         {
701            m_rTree.ensureExpanded( it );
702            break;
703         }
704     }
705     for( it = m_flat ? m_rTree.firstLeaf() : m_rTree.begin();
706          it != m_rTree.end();
707          it = m_flat ? m_rTree.getNextLeaf( it )
708                      : m_rTree.getNextVisibleItem( it ) )
709     {
710         if( it->m_playing )
711            break;
712         playIndex++;
713     }
714
715     if( it == m_rTree.end() ) return;
716
717
718     ensureVisible( playIndex );
719 }
720
721
722 void CtrlTree::makeImage()
723 {
724     stats_TimerStart( getIntf(), "[Skins] Playlist image",
725                       STATS_TIMER_SKINS_PLAYTREE_IMAGE );
726     delete m_pImage;
727
728     // Get the size of the control
729     const Position *pPos = getPosition();
730     if( !pPos )
731     {
732         stats_TimerStop( getIntf(), STATS_TIMER_SKINS_PLAYTREE_IMAGE );
733         return;
734     }
735     int width = pPos->getWidth();
736     int height = pPos->getHeight();
737
738     int i_itemHeight = itemHeight();
739
740     // Create an image
741     OSFactory *pOsFactory = OSFactory::instance( getIntf() );
742     m_pImage = pOsFactory->createOSGraphics( width, height );
743
744     VarTree::Iterator it = m_firstPos;
745
746     if( m_pBgBitmap )
747     {
748         // Draw the background bitmap
749         ScaledBitmap bmp( getIntf(), *m_pBgBitmap, width, height );
750         m_pImage->drawBitmap( bmp, 0, 0 );
751
752         for( int yPos = 0; yPos < height; yPos += i_itemHeight )
753         {
754             if( it != m_rTree.end() )
755             {
756                 if( (*it).m_selected )
757                 {
758                     int rectHeight = __MIN( i_itemHeight, height - yPos );
759                     m_pImage->fillRect( 0, yPos, width, rectHeight,
760                                         m_selColor );
761                 }
762                 do
763                 {
764                     it = m_flat ? m_rTree.getNextLeaf( it )
765                                 : m_rTree.getNextVisibleItem( it );
766                 } while( it != m_rTree.end() && it->m_deleted );
767             }
768         }
769     }
770     else
771     {
772         // FIXME (TRYME)
773         // Fill background with background color
774         uint32_t bgColor = m_bgColor1;
775         m_pImage->fillRect( 0, 0, width, height, bgColor );
776         for( int yPos = 0; yPos < height; yPos += i_itemHeight )
777         {
778             int rectHeight = __MIN( i_itemHeight, height - yPos );
779             if( it != m_rTree.end() )
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             else
790             {
791                 m_pImage->fillRect( 0, yPos, width, rectHeight, bgColor );
792             }
793             bgColor = ( bgColor == m_bgColor1 ? m_bgColor2 : m_bgColor1 );
794         }
795     }
796
797     int bitmapWidth = itemImageWidth();
798
799     int yPos = 0;
800     it = m_firstPos;
801     while( it != m_rTree.end() && yPos < height )
802     {
803         const GenericBitmap *m_pCurBitmap;
804         UString *pStr = (UString*)(it->m_cString.get());
805         uint32_t color = ( it->m_playing ? m_playColor : m_fgColor );
806
807         // Draw the text
808         if( pStr != NULL )
809         {
810             int depth = m_flat ? 1 : it->depth();
811             GenericBitmap *pText = m_rFont.drawString( *pStr, color, width - bitmapWidth * depth );
812             if( !pText )
813             {
814                 stats_TimerStop( getIntf(), STATS_TIMER_SKINS_PLAYTREE_IMAGE );
815                 return;
816             }
817             if( it->size() )
818                 m_pCurBitmap = it->m_expanded ? m_pOpenBitmap : m_pClosedBitmap;
819             else
820                 m_pCurBitmap = m_pItemBitmap;
821
822             if( m_pCurBitmap )
823             {
824                 // Make sure we are centered on the line
825                 int yPos2 = yPos+(i_itemHeight-m_pCurBitmap->getHeight()+1)/2;
826                 if( yPos2 >= height )
827                 {
828                     delete pText;
829                     break;
830                 }
831                 m_pImage->drawBitmap( *m_pCurBitmap, 0, 0,
832                                       bitmapWidth * (depth - 1 ), yPos2,
833                                       m_pCurBitmap->getWidth(),
834                                       __MIN( m_pCurBitmap->getHeight(),
835                                              height -  yPos2), true );
836             }
837             yPos += i_itemHeight - pText->getHeight();
838             int ySrc = 0;
839             if( yPos < 0 )
840             {
841                 ySrc = - yPos;
842                 yPos = 0;
843             }
844             int lineHeight = __MIN( pText->getHeight() - ySrc, height - yPos );
845             m_pImage->drawBitmap( *pText, 0, ySrc, bitmapWidth * depth, yPos,
846                                   pText->getWidth(),
847                                   lineHeight, true );
848             yPos += (pText->getHeight() - ySrc );
849             delete pText;
850         }
851         do
852         {
853             it = m_flat ? m_rTree.getNextLeaf( it )
854                 : m_rTree.getNextVisibleItem( it );
855         } while( it != m_rTree.end() && it->m_deleted );
856     }
857     stats_TimerStop( getIntf(), STATS_TIMER_SKINS_PLAYTREE_IMAGE );
858 }
859
860 VarTree::Iterator CtrlTree::findItemAtPos( int pos )
861 {
862     // The first item is m_firstPos.
863     // We decrement pos as we try the other items, until pos == 0.
864     VarTree::Iterator it;
865     for( it = m_firstPos; it != m_rTree.end() && pos != 0;
866          it = m_flat ? m_rTree.getNextLeaf( it )
867                      : m_rTree.getNextVisibleItem( it ) )
868     {
869         pos--;
870     }
871
872     return it;
873 }