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