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