1 /*****************************************************************************
2  * ctrl_tree.cpp
3  *****************************************************************************
4  * Copyright (C) 2003 the VideoLAN team
5  * $Id: f851a25070e4546a181241aa6256260fd51a33c6 $
6  *
7  * Authors: Antoine Cellerier <dionoea@videolan.org>
8  *          Clément Stenac <zorglub@videolan.org>
9  *          Erwan Tulou  <erwan10 At videolan DoT org>
10  *
11  * This program is free software; you can redistribute it and/or modify
12  * it under the terms of the GNU General Public License as published by
13  * the Free Software Foundation; either version 2 of the License, or
14  * (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with this program; if not, write to the Free Software
23  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
24  *****************************************************************************/
25 
26 #include <math.h>
27 #include "../utils/var_bool.hpp"
28 #include "ctrl_tree.hpp"
29 #include "../src/os_factory.hpp"
30 #include "../src/os_graphics.hpp"
31 #include "../src/generic_bitmap.hpp"
32 #include "../src/generic_font.hpp"
33 #include "../src/scaled_bitmap.hpp"
34 #include "../src/dialogs.hpp"
35 #include "../utils/position.hpp"
36 #include "../utils/ustring.hpp"
37 #include "../events/evt_key.hpp"
38 #include "../events/evt_mouse.hpp"
39 #include "../events/evt_scroll.hpp"
40 #include "../events/evt_dragndrop.hpp"
41 #include "../vars/playtree.hpp"
42 #include <vlc_actions.h>
43 
44 #define LINE_INTERVAL 1  // Number of pixels inserted between 2 lines
45 
46 
CtrlTree(intf_thread_t * pIntf,VarTree & rTree,const GenericFont & rFont,const GenericBitmap * pBgBitmap,const GenericBitmap * pItemBitmap,const GenericBitmap * pOpenBitmap,const GenericBitmap * pClosedBitmap,uint32_t fgColor,uint32_t playColor,uint32_t bgColor1,uint32_t bgColor2,uint32_t selColor,const UString & rHelp,VarBool * pVisible,VarBool * pFlat)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_pScaledBitmap( NULL ), m_pImage( NULL ),
66     m_fgColor( fgColor ), m_playColor( playColor ),
67     m_bgColor1( bgColor1 ), m_bgColor2( bgColor2 ), m_selColor( selColor ),
68     m_firstPos( m_rTree.end() ), m_lastClicked( m_rTree.end() ),
69     m_itOver( m_rTree.end() ), m_flat( pFlat->get() ), m_capacity( -1.0 ),
70     m_bRefreshOnDelete( false )
71 {
72     // Observe the tree
73     m_rTree.addObserver( this );
74     m_rTree.setFlat( m_flat );
75 }
76 
~CtrlTree()77 CtrlTree::~CtrlTree()
78 {
79     m_rTree.delObserver( this );
80     delete m_pImage;
81     delete m_pScaledBitmap;
82 }
83 
itemHeight()84 int CtrlTree::itemHeight()
85 {
86     int itemHeight = m_rFont.getSize();
87     if( !m_flat )
88     {
89         if( m_pClosedBitmap )
90         {
91             itemHeight = __MAX( m_pClosedBitmap->getHeight(), itemHeight );
92         }
93         if( m_pOpenBitmap )
94         {
95             itemHeight = __MAX( m_pOpenBitmap->getHeight(), itemHeight );
96         }
97     }
98     if( m_pItemBitmap )
99     {
100         itemHeight = __MAX( m_pItemBitmap->getHeight(), itemHeight );
101     }
102     itemHeight += LINE_INTERVAL;
103     return itemHeight;
104 }
105 
itemImageWidth()106 int CtrlTree::itemImageWidth()
107 {
108     int bitmapWidth = 5;
109     if( !m_flat )
110     {
111         if( m_pClosedBitmap )
112         {
113             bitmapWidth = __MAX( m_pClosedBitmap->getWidth(), bitmapWidth );
114         }
115         if( m_pOpenBitmap )
116         {
117             bitmapWidth = __MAX( m_pOpenBitmap->getWidth(), bitmapWidth );
118         }
119     }
120     if( m_pItemBitmap )
121     {
122         bitmapWidth = __MAX( m_pItemBitmap->getWidth(), bitmapWidth );
123     }
124     return bitmapWidth + 2;
125 }
126 
maxItems()127 float CtrlTree::maxItems()
128 {
129     const Position *pPos = getPosition();
130     if( !pPos )
131     {
132         return -1;
133     }
134     return (float)pPos->getHeight() / itemHeight();
135 }
136 
onUpdate(Subject<VarTree,tree_update> & rTree,tree_update * arg)137 void CtrlTree::onUpdate( Subject<VarTree, tree_update> &rTree,
138                          tree_update *arg )
139 {
140     (void)rTree;
141     if( arg->type == arg->ItemInserted )
142     {
143         if( isItemVisible( arg->it ) )
144         {
145             makeImage();
146             notifyLayout();
147         }
148         setSliderFromFirst();
149     }
150     else if( arg->type == arg->ItemUpdated )
151     {
152         if( arg->it->isPlaying() )
153         {
154             m_rTree.ensureExpanded( arg->it );
155             ensureVisible( arg->it );
156 
157             makeImage();
158             notifyLayout();
159             setSliderFromFirst();
160         }
161         else if( isItemVisible( arg->it ) )
162         {
163             makeImage();
164             notifyLayout();
165         }
166     }
167     else if( arg->type == arg->DeletingItem )
168     {
169         if( isItemVisible( arg->it ) )
170             m_bRefreshOnDelete = true;
171         // remove all references to arg->it
172         // if it is the one about to be deleted
173         if( m_firstPos == arg->it )
174         {
175             m_firstPos = getNearestItem( arg->it );
176         }
177         if( m_lastClicked == arg->it )
178         {
179             m_lastClicked = getNearestItem( arg->it );
180             m_lastClicked->setSelected( arg->it->isSelected() );
181         }
182     }
183     else if( arg->type == arg->ItemDeleted )
184     {
185         if( m_bRefreshOnDelete )
186         {
187             m_bRefreshOnDelete = false;
188 
189             makeImage();
190             notifyLayout();
191         }
192         setSliderFromFirst();
193     }
194     else if( arg->type == arg->ResetAll )
195     {
196         m_lastClicked = m_rTree.end();
197         m_firstPos = getFirstFromSlider();
198 
199         makeImage();
200         notifyLayout();
201         setSliderFromFirst();
202     }
203     else if( arg->type == arg->SliderChanged )
204     {
205         Iterator it = getFirstFromSlider();
206         if( m_firstPos != it )
207         {
208             m_firstPos = it;
209             makeImage();
210             notifyLayout();
211         }
212     }
213 }
214 
onResize()215 void CtrlTree::onResize()
216 {
217     onPositionChange();
218 }
219 
onPositionChange()220 void CtrlTree::onPositionChange()
221 {
222     m_capacity = maxItems();
223     setScrollStep();
224     m_firstPos = getFirstFromSlider();
225     makeImage();
226 }
227 
handleEvent(EvtGeneric & rEvent)228 void CtrlTree::handleEvent( EvtGeneric &rEvent )
229 {
230     bool needShow = false;
231     bool needRefresh = false;
232     Iterator toShow = m_firstPos;
233     if( rEvent.getAsString().find( "key:down" ) != std::string::npos )
234     {
235         int key = ((EvtKey&)rEvent).getKey();
236 
237         /* Delete the selection */
238         if( key == KEY_DELETE )
239         {
240             /* Delete selected stuff */
241             m_rTree.delSelected();
242         }
243         else if( key == KEY_PAGEDOWN )
244         {
245             int numSteps = (int)m_capacity / 2;
246             VarPercent &rVarPos = m_rTree.getPositionVar();
247             rVarPos.increment( -numSteps );
248         }
249         else if( key == KEY_PAGEUP )
250         {
251             int numSteps = (int)m_capacity / 2;
252             VarPercent &rVarPos = m_rTree.getPositionVar();
253             rVarPos.increment( numSteps );
254         }
255         else if( key == KEY_UP )
256         {
257             // Scroll up one item
258             m_rTree.unselectTree();
259             if( m_lastClicked != m_rTree.end() )
260             {
261                 if( --m_lastClicked != m_rTree.end() )
262                 {
263                     m_lastClicked->setSelected( true );
264                 }
265             }
266             if( m_lastClicked == m_rTree.end() )
267             {
268                 m_lastClicked = m_firstPos;
269                 if( m_lastClicked != m_rTree.end() )
270                     m_lastClicked->setSelected( true );
271             }
272             needRefresh = true;
273             needShow = true; toShow = m_lastClicked;
274         }
275         else if( key == KEY_DOWN )
276         {
277             // Scroll down one item
278             m_rTree.unselectTree();
279             if( m_lastClicked != m_rTree.end() )
280             {
281                 Iterator it_old = m_lastClicked;
282                 if( ++m_lastClicked != m_rTree.end() )
283                 {
284                     m_lastClicked->setSelected( true );
285                 }
286                 else
287                 {
288                     it_old->setSelected( true );
289                     m_lastClicked = it_old;
290                 }
291             }
292             else
293             {
294                 m_lastClicked = m_firstPos;
295                 if( m_lastClicked != m_rTree.end() )
296                     m_lastClicked->setSelected( true );
297             }
298             needRefresh = true;
299             needShow = true; toShow = m_lastClicked;
300         }
301         else if( key == KEY_RIGHT )
302         {
303             // Go down one level (and expand node)
304             Iterator& it = m_lastClicked;
305             if( it != m_rTree.end() )
306             {
307                 if( !m_flat && !it->isExpanded() && it->size() )
308                 {
309                     it->setExpanded( true );
310                     needRefresh = true;
311                 }
312                 else
313                 {
314                     m_rTree.unselectTree();
315                     Iterator it_old = m_lastClicked;
316                     if( ++m_lastClicked != m_rTree.end() )
317                     {
318                         m_lastClicked->setSelected( true );
319                     }
320                     else
321                     {
322                         it_old->setSelected( true );
323                         m_lastClicked = it_old;
324                     }
325                     needRefresh = true;
326                     needShow = true; toShow = m_lastClicked;
327                 }
328             }
329         }
330         else if( key == KEY_LEFT )
331         {
332             // Go up one level (and close node)
333             Iterator& it = m_lastClicked;
334             if( it != m_rTree.end() )
335             {
336                 if( m_flat )
337                 {
338                     m_rTree.unselectTree();
339                     if( --m_lastClicked != m_rTree.end() )
340                     {
341                         m_lastClicked->setSelected( true );
342                     }
343                     else
344                     {
345                         m_lastClicked = m_firstPos;
346                         if( m_lastClicked != m_rTree.end() )
347                             m_lastClicked->setSelected( true );
348                     }
349                     needRefresh = true;
350                     needShow = true; toShow = m_lastClicked;
351                 }
352                 else
353                 {
354                     if( it->isExpanded() )
355                     {
356                         it->setExpanded( false );
357                         needRefresh = true;
358                     }
359                     else
360                     {
361                         Iterator it_parent = it.getParent();
362                         if( it_parent != m_rTree.end() )
363                         {
364                             it->setSelected( false );
365                             m_lastClicked = it_parent;
366                             m_lastClicked->setSelected( true );
367                             needRefresh = true;
368                             needShow = true; toShow = m_lastClicked;
369                         }
370                     }
371                 }
372             }
373         }
374         else if( key == KEY_ENTER || key == ' ' )
375         {
376             // Go up one level (and close node)
377             if( m_lastClicked != m_rTree.end() )
378             {
379                 m_rTree.action( &*m_lastClicked );
380             }
381         }
382         else
383         {
384             // other keys to be forwarded to vlc core
385             EvtKey& rEvtKey = (EvtKey&)rEvent;
386             getIntf()->p_sys->p_dialogs->sendKey( rEvtKey.getModKey() );
387         }
388     }
389 
390     else if( rEvent.getAsString().find( "mouse:left" ) != std::string::npos )
391     {
392         EvtMouse &rEvtMouse = (EvtMouse&)rEvent;
393         const Position *pos = getPosition();
394         int xPos = rEvtMouse.getXPos() - pos->getLeft();
395         int yPos = ( rEvtMouse.getYPos() - pos->getTop() ) / itemHeight();
396 
397         Iterator itClicked = findItemAtPos( yPos );
398         if( itClicked != m_rTree.end() )
399         {
400             if( rEvent.getAsString().find( "mouse:left:down:ctrl,shift" ) !=
401                 std::string::npos )
402             {
403                 // Flag to know if the current item must be selected
404                 bool select = false;
405                 for( Iterator it = m_firstPos; it != m_rTree.end(); ++it )
406                 {
407                     bool nextSelect = select;
408                     if( it == itClicked || it == m_lastClicked )
409                     {
410                         if( select )
411                         {
412                             nextSelect = false;
413                         }
414                         else
415                         {
416                             select = true;
417                             if( itClicked != m_lastClicked )
418                                 nextSelect = true;
419                         }
420                     }
421                     it->setSelected( it->isSelected() || select );
422                     select = nextSelect;
423                     needRefresh = true;
424                 }
425             }
426             else if( rEvent.getAsString().find( "mouse:left:down:ctrl" ) !=
427                      std::string::npos )
428             {
429                 // Invert the selection of the item
430                 itClicked->toggleSelected();
431                 m_lastClicked = itClicked;
432                 needRefresh = true;
433             }
434             else if( rEvent.getAsString().find( "mouse:left:down:shift" ) !=
435                      std::string::npos )
436             {
437                 bool select = false;
438                 for( Iterator it = m_firstPos; it != m_rTree.end(); ++it )
439                 {
440                     bool nextSelect = select;
441                     if( it == itClicked || it == m_lastClicked )
442                     {
443                         if( select )
444                         {
445                             nextSelect = false;
446                         }
447                         else
448                         {
449                             select = true;
450                             if( itClicked != m_lastClicked )
451                                 nextSelect = true;
452                         }
453                     }
454                     it->setSelected( select );
455                     select = nextSelect;
456                 }
457                 needRefresh = true;
458             }
459             else if( rEvent.getAsString().find( "mouse:left:down" ) !=
460                      std::string::npos )
461             {
462                 if( !m_flat &&
463                     itClicked->size() &&
464                     xPos > (itClicked->depth() - 1) * itemImageWidth() &&
465                     xPos < itClicked->depth() * itemImageWidth() )
466                 {
467                     // Fold/unfold the item
468                     itClicked->toggleExpanded();
469                 }
470                 else
471                 {
472                     // Unselect any previously selected item
473                     m_rTree.unselectTree();
474                     // Select the new item
475                     itClicked->setSelected( true );
476                     m_lastClicked = itClicked;
477                 }
478                 needRefresh = true;
479             }
480             else if( rEvent.getAsString().find( "mouse:left:dblclick" ) !=
481                      std::string::npos )
482             {
483                // Execute the action associated to this item
484                m_rTree.action( &*itClicked );
485             }
486         }
487     }
488 
489     else if( rEvent.getAsString().find( "scroll" ) != std::string::npos )
490     {
491         int direction = static_cast<EvtScroll&>(rEvent).getDirection();
492         if( direction == EvtScroll::kUp )
493             m_rTree.getPositionVar().increment( +1 );
494         else
495             m_rTree.getPositionVar().increment( -1 );
496     }
497 
498     else if( rEvent.getAsString().find( "drag:over" ) != std::string::npos )
499     {
500         EvtDragOver& evt = static_cast<EvtDragOver&>(rEvent);
501         const Position *pos = getPosition();
502         int yPos = ( evt.getYPos() - pos->getTop() ) / itemHeight();
503 
504         Iterator it = findItemAtPos( yPos );
505         if( it != m_itOver )
506         {
507             m_itOver = it;
508             needRefresh = true;
509         }
510     }
511 
512     else if( rEvent.getAsString().find( "drag:drop" ) != std::string::npos )
513     {
514         EvtDragDrop& evt = static_cast<EvtDragDrop&>(rEvent);
515         Playtree& rPlaytree = static_cast<Playtree&>(m_rTree);
516         VarTree& item = ( m_itOver != m_rTree.end() ) ? *m_itOver : m_rTree;
517         rPlaytree.insertItems( item, evt.getFiles(), false );
518         m_itOver = m_rTree.end();
519         needRefresh = true;
520     }
521 
522     else if( rEvent.getAsString().find( "drag:leave" ) != std::string::npos )
523     {
524         m_itOver = m_rTree.end();
525         needRefresh = true;
526     }
527 
528     if( needShow )
529     {
530         if( toShow == m_rTree.end() ||
531             !ensureVisible( toShow ) )
532             needRefresh = true;
533     }
534     if( needRefresh )
535     {
536         setSliderFromFirst();
537 
538         makeImage();
539         notifyLayout();
540     }
541 }
542 
mouseOver(int x,int y) const543 bool CtrlTree::mouseOver( int x, int y ) const
544 {
545     const Position *pPos = getPosition();
546     return !pPos ? false :
547         x >= 0 && x <= pPos->getWidth() && y >= 0 && y <= pPos->getHeight();
548 }
549 
draw(OSGraphics & rImage,int xDest,int yDest,int w,int h)550 void CtrlTree::draw( OSGraphics &rImage, int xDest, int yDest, int w, int h)
551 {
552     const Position *pPos = getPosition();
553     rect region( pPos->getLeft(), pPos->getTop(),
554                  pPos->getWidth(), pPos->getHeight() );
555     rect clip( xDest, yDest, w, h );
556     rect inter;
557 
558     if( rect::intersect( region, clip, &inter ) && m_pImage )
559         rImage.drawGraphics( *m_pImage,
560                       inter.x - pPos->getLeft(),
561                       inter.y - pPos->getTop(),
562                       inter.x, inter.y, inter.width, inter.height );
563 }
564 
makeImage()565 void CtrlTree::makeImage()
566 {
567     delete m_pImage;
568 
569     // Get the size of the control
570     const Position *pPos = getPosition();
571     if( !pPos )
572         return;
573     int width = pPos->getWidth();
574     int height = pPos->getHeight();
575 
576     int i_itemHeight = itemHeight();
577 
578     // Create an image
579     OSFactory *pOsFactory = OSFactory::instance( getIntf() );
580     m_pImage = pOsFactory->createOSGraphics( width, height );
581 
582     Iterator it = m_firstPos;
583 
584     if( m_pBgBitmap )
585     {
586         // Draw the background bitmap
587         if( !m_pScaledBitmap ||
588             m_pScaledBitmap->getWidth() != width ||
589             m_pScaledBitmap->getHeight() != height )
590         {
591             delete m_pScaledBitmap;
592             m_pScaledBitmap =
593                 new ScaledBitmap( getIntf(), *m_pBgBitmap, width, height );
594         }
595         m_pImage->drawBitmap( *m_pScaledBitmap, 0, 0 );
596 
597         for( int yPos = 0;
598              yPos < height && it != m_rTree.end();
599              yPos += i_itemHeight, ++it )
600         {
601             if( it->isSelected() )
602             {
603                 int rectHeight = __MIN( i_itemHeight, height - yPos );
604                 m_pImage->fillRect( 0, yPos, width, rectHeight, m_selColor );
605             }
606         }
607     }
608     else
609     {
610         // Fill background with background color
611         uint32_t bgColor = m_bgColor1;
612         m_pImage->fillRect( 0, 0, width, height, bgColor );
613         // Overwrite with alternate colors (bgColor1, bgColor2)
614         for( int yPos = 0; yPos < height; yPos += i_itemHeight )
615         {
616             int rectHeight = __MIN( i_itemHeight, height - yPos );
617             if( it == m_rTree.end() )
618                 m_pImage->fillRect( 0, yPos, width, rectHeight, bgColor );
619             else
620             {
621                 uint32_t color = ( it->isSelected() ? m_selColor : bgColor );
622                 m_pImage->fillRect( 0, yPos, width, rectHeight, color );
623                 ++it;
624             }
625             bgColor = ( bgColor == m_bgColor1 ? m_bgColor2 : m_bgColor1 );
626         }
627     }
628 
629     int bitmapWidth = itemImageWidth();
630 
631     it = m_firstPos;
632     for( int yPos = 0; yPos < height && it != m_rTree.end(); ++it )
633     {
634         const GenericBitmap *m_pCurBitmap;
635         UString *pStr = it->getString();
636         if( pStr != NULL )
637         {
638             uint32_t color = it->isPlaying() ? m_playColor : m_fgColor;
639             int depth = m_flat ? 1 : it->depth();
640             GenericBitmap *pText =
641                 m_rFont.drawString( *pStr, color, width-bitmapWidth*depth );
642             if( !pText )
643             {
644                 return;
645             }
646             if( it->size() )
647                 m_pCurBitmap =
648                     it->isExpanded() ? m_pOpenBitmap : m_pClosedBitmap;
649             else
650                 m_pCurBitmap = m_pItemBitmap;
651 
652             if( m_pCurBitmap )
653             {
654                 // Make sure we are centered on the line
655                 int yPos2 = yPos+(i_itemHeight-m_pCurBitmap->getHeight()+1)/2;
656                 if( yPos2 >= height )
657                 {
658                     delete pText;
659                     break;
660                 }
661                 // Draw the icon in front of the text
662                 m_pImage->drawBitmap( *m_pCurBitmap, 0, 0,
663                                       bitmapWidth * (depth - 1 ), yPos2,
664                                       m_pCurBitmap->getWidth(),
665                                       __MIN( m_pCurBitmap->getHeight(),
666                                              height -  yPos2), true );
667             }
668             yPos += (i_itemHeight - pText->getHeight());
669             if( yPos >= height )
670             {
671                 delete pText;
672                 break;
673             }
674 
675             int ySrc = 0;
676             if( yPos < 0 )
677             {
678                 ySrc = - yPos;
679                 yPos = 0;
680             }
681             int lineHeight = __MIN( pText->getHeight() - ySrc, height - yPos );
682             // Draw the text
683             m_pImage->drawBitmap( *pText, 0, ySrc, bitmapWidth * depth, yPos,
684                                   pText->getWidth(),
685                                   lineHeight, true );
686             yPos += (pText->getHeight() - ySrc );
687 
688             if( it == m_itOver )
689             {
690                 // Draw the underline bar below the text for drag&drop
691                 m_pImage->fillRect(
692                     bitmapWidth * (depth - 1 ), yPos - 2,
693                     bitmapWidth + pText->getWidth(), __MAX( lineHeight/5, 3 ),
694                     m_selColor );
695             }
696             delete pText;
697         }
698     }
699 }
700 
findItemAtPos(int pos)701 CtrlTree::Iterator CtrlTree::findItemAtPos( int pos )
702 {
703     // The first item is m_firstPos.
704     // We decrement pos as we try the other items, until pos == 0.
705     Iterator it = m_firstPos;
706     for( ; it != m_rTree.end() && pos != 0; ++it, pos-- );
707 
708     return it;
709 }
710 
getFirstFromSlider()711 CtrlTree::Iterator CtrlTree::getFirstFromSlider()
712 {
713     // a simple (int)(...) causes rounding errors !
714 #ifdef _MSC_VER
715 #       define lrint (int)
716 #endif
717     VarPercent &rVarPos = m_rTree.getPositionVar();
718     double percentage = rVarPos.get();
719 
720     int excessItems = m_flat ? (m_rTree.countLeafs() - (int)m_capacity)
721                              : (m_rTree.visibleItems() - (int)m_capacity);
722 
723     int index = (excessItems > 0 ) ?
724         lrint( (1.0 - percentage)*(double)excessItems ) :
725         0;
726 
727     Iterator it_first = m_rTree.getItem( index );
728 
729     return it_first;
730 }
731 
setScrollStep()732 void CtrlTree::setScrollStep()
733 {
734     VarPercent &rVarPos = m_rTree.getPositionVar();
735 
736     int excessItems = m_flat ? (m_rTree.countLeafs() - (int)m_capacity)
737                              : (m_rTree.visibleItems() - (int)m_capacity);
738 
739     if( excessItems > 0 )
740         rVarPos.setStep( (float)1 / excessItems );
741     else
742         rVarPos.setStep( 1.0 );
743 }
744 
setSliderFromFirst()745 void CtrlTree::setSliderFromFirst()
746 {
747     VarPercent &rVarPos = m_rTree.getPositionVar();
748 
749     int excessItems = m_flat ? (m_rTree.countLeafs() - (int)m_capacity)
750                              : (m_rTree.visibleItems() - (int)m_capacity);
751 
752     int index = m_rTree.getIndex( m_firstPos );
753     if( excessItems > 0 )
754     {
755         rVarPos.set( 1.0 - (float)index/(float)excessItems );
756         rVarPos.setStep( 1.0 / excessItems );
757     }
758     else
759     {
760         rVarPos.set( 1.0 );
761         rVarPos.setStep( 1.0 );
762     }
763 }
764 
isItemVisible(const Iterator & it_ref)765 bool CtrlTree::isItemVisible( const Iterator& it_ref )
766 {
767     if( it_ref == m_rTree.end() )
768         return false;
769 
770     Iterator it = m_firstPos;
771     if( it == m_rTree.end() )
772         return true;
773 
774     // Ensure a partially visible last item is taken into account
775     int max = (int)m_capacity;
776     if( (float)max < m_capacity )
777         max++;
778 
779     for( int i = 0; i < max && it != m_rTree.end(); ++it, i++ )
780     {
781         if( it == it_ref )
782             return true;
783     }
784     return false;
785 }
786 
ensureVisible(const Iterator & item)787 bool CtrlTree::ensureVisible( const Iterator& item )
788 {
789     Iterator it = m_firstPos;
790     int max = (int)m_capacity;
791     for( int i = 0; i < max && it != m_rTree.end(); ++it, i++ )
792     {
793         if( it == item )
794             return false;
795     }
796 
797     m_rTree.setSliderFromItem( item );
798     return true;
799 }
800 
getNearestItem(const Iterator & item)801 CtrlTree::Iterator CtrlTree::getNearestItem( const Iterator& item )
802 {
803     // return the previous item if it exists
804     Iterator newItem = item;
805     if( --newItem != m_rTree.end() && newItem != item )
806         return newItem;
807 
808     // return the next item if no previous item found
809     newItem = item;
810     return ++newItem;
811 }
812