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