]> git.sesse.net Git - vlc/blob - modules/gui/skins2/controls/ctrl_tree.cpp
7404dd6bb649cc63d2a74a73b15596efc0ef7458
[vlc] / modules / gui / skins2 / controls / ctrl_tree.cpp
1 /*****************************************************************************
2  * ctrl_tree.cpp
3  *****************************************************************************
4  * Copyright (C) 2003 VideoLAN
5  * $Id: ctrl_list.cpp 11009 2005-05-14 14:39:05Z ipkiss $
6  *
7  * Authors: Antoine Cellerier <dionoea@videolan.org>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
22  *****************************************************************************/
23
24 #include <math.h>
25 #include "ctrl_tree.hpp"
26 #include "../src/os_factory.hpp"
27 #include "../src/os_graphics.hpp"
28 #include "../src/generic_bitmap.hpp"
29 #include "../src/generic_font.hpp"
30 #include "../src/scaled_bitmap.hpp"
31 #include "../utils/position.hpp"
32 #include "../utils/ustring.hpp"
33 #include "../events/evt_key.hpp"
34 #include "../events/evt_mouse.hpp"
35 #include "../events/evt_scroll.hpp"
36 #include "vlc_keys.h"
37 #ifdef sun
38 #   include "solaris_specific.h" // for lrint
39 #endif
40
41 #define SCROLL_STEP 0.05
42 #define LINE_INTERVAL 1  // Number of pixels inserted between 2 lines
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     CtrlGeneric( pIntf,rHelp, pVisible), m_rTree( rTree), m_rFont( rFont ),
59     m_pBgBitmap( pBgBitmap ), m_pItemBitmap( pItemBitmap ),
60     m_pOpenBitmap( pOpenBitmap ), m_pClosedBitmap( pClosedBitmap ),
61     m_fgColor( fgColor ), m_playColor( playColor ), m_bgColor1( bgColor1 ),
62     m_bgColor2( bgColor2 ), m_selColor( selColor ),
63     m_pLastSelected( NULL ), m_pImage( NULL )
64 {
65     // Observe the tree and position variables
66     m_rTree.addObserver( this );
67     m_rTree.getPositionVar().addObserver( this );
68
69     m_lastPos = m_rTree.begin();
70
71     makeImage();
72 }
73
74 CtrlTree::~CtrlTree()
75 {
76     m_rTree.getPositionVar().delObserver( this );
77     m_rTree.delObserver( this );
78     if( m_pImage )
79     {
80         delete m_pImage;
81     }
82 }
83
84 int CtrlTree::itemHeight()
85 {
86     int itemHeight = m_rFont.getSize();
87     if( m_pClosedBitmap )
88     {
89         itemHeight = __MAX( m_pClosedBitmap->getHeight(), itemHeight );
90     }
91     if( m_pOpenBitmap )
92     {
93         itemHeight = __MAX( m_pOpenBitmap->getHeight(), itemHeight );
94     }
95     if( m_pItemBitmap )
96     {
97         itemHeight = __MAX( m_pItemBitmap->getHeight(), itemHeight );
98     }
99     itemHeight += LINE_INTERVAL;
100     return itemHeight;
101 }
102
103 int CtrlTree::itemImageWidth()
104 {
105     int bitmapWidth = 5;
106     if( m_pClosedBitmap )
107     {
108         bitmapWidth = __MAX( m_pClosedBitmap->getWidth(), bitmapWidth );
109     }
110     if( m_pOpenBitmap )
111     {
112         bitmapWidth = __MAX( m_pOpenBitmap->getWidth(), bitmapWidth );
113     }
114     if( m_pItemBitmap )
115     {
116         bitmapWidth = __MAX( m_pItemBitmap->getWidth(), bitmapWidth );
117     }
118     return bitmapWidth + 2;
119 }
120
121 int CtrlTree::maxItems()
122 {
123     const Position *pPos = getPosition();
124     if( !pPos )
125     {
126         return -1;
127     }
128     return pPos->getHeight() / itemHeight();
129 }
130
131
132 void CtrlTree::onUpdate( Subject<VarTree> &rTree )
133 {
134     autoScroll();
135     m_pLastSelected = NULL;
136 }
137
138 void CtrlTree::onUpdate( Subject<VarPercent> &rPercent )
139 {
140     // Determine what is the first item to display
141     VarTree::Iterator it = m_rTree.begin();
142
143     int excessItems = m_rTree.visibleItems() - maxItems();
144
145     fprintf( stderr, "Hullo\n");
146
147     if( excessItems > 0)
148     {
149         VarPercent &rVarPos = m_rTree.getPositionVar();
150         // a simple (int)(...) causes rounding errors !
151 #ifdef _MSC_VER
152 #   define lrint (int)
153 #endif
154         it = m_rTree.visibleItem(lrint( (1.0 - rVarPos.get()) * (double)excessItems ) + 1);
155     }
156     if( m_lastPos != it )
157     {
158         fprintf( stderr, "updating\n" );
159         // Redraw the control if the position has changed
160         m_lastPos = it;
161         makeImage();
162         notifyLayout();
163     }
164     else
165         fprintf( stderr, "not updating\n" );
166 }
167
168 void CtrlTree::onResize()
169 {
170 // FIXME : shouldn't be the same as the onUpdate function ... but i'm lazy
171     // Determine what is the first item to display
172     VarTree::Iterator it = m_rTree.begin();
173
174     int excessItems = m_rTree.visibleItems() - maxItems();
175
176     if( excessItems > 0)
177     {
178         VarPercent &rVarPos = m_rTree.getPositionVar();
179         // a simple (int)(...) causes rounding errors !
180 #ifdef _MSC_VER
181 #   define lrint (int)
182 #endif
183         it = m_rTree.visibleItem(lrint( (1.0 - rVarPos.get()) * (double)excessItems ) + 1);
184     }
185     // Redraw the control if the position has changed
186     m_lastPos = it;
187     makeImage();
188     notifyLayout();
189 #if 0
190     // Determine what is the first item to display
191     VarTree::Iterator it = m_rTree.begin();
192
193     int excessItems = m_rTree.visibleItems() - maxItems();
194
195     if( excessItems > 0)
196     {
197         /* FIXME VarPercent &rVarPos = m_rTree.getPositionVar();
198         double newVal = 1.0 - (double)m_lastPos / excessItems;
199         if( newVal >= 0 )
200         {
201             // Change the position to keep the same first displayed item
202             rVarPos.set( 1.0 - (double)m_lastPos / excessItems );
203         }
204         else
205         {
206             // We cannot keep the current first item
207             m_lastPos = excessItems;
208         }*/
209         it = m_rTree.visibleItem( excessItems );
210     }
211     makeImage();
212     notifyLayout();
213 #endif
214 }
215
216 void CtrlTree::onPositionChange()
217 {
218     makeImage();
219     notifyLayout();
220 }
221
222 #define IT_DISP_LOOP_END( a )  \
223                 if( a ->m_expanded && a ->size() ) \
224                 { \
225                     a = a ->begin(); \
226                 } \
227                 else \
228                 { \
229                     VarTree::Iterator it_old = a; \
230                     a ++; \
231                     if( it_old->parent() && it_old->parent()->end() == a ) \
232                     { \
233                         a = it_old->uncle(); \
234                     } \
235                 }
236 void CtrlTree::handleEvent( EvtGeneric &rEvent )
237 {
238     if( rEvent.getAsString().find( "key:down" ) != string::npos )
239     {
240         int key = ((EvtKey&)rEvent).getKey();
241         VarTree::Iterator it;
242         bool previousWasSelected = false;
243         for( it = m_rTree.begin(); it != m_rTree.end(); )
244         {
245             VarTree::Iterator next = it;
246             IT_DISP_LOOP_END( next );
247             if( key == KEY_UP )
248             {
249                 //Scroll up one item
250                 if( ( it->parent()
251                       && it != it->parent()->begin() )
252                     || &*it != m_pLastSelected )
253                 {
254                     bool nextWasSelected = ( &*next == m_pLastSelected );
255                     it->m_selected = nextWasSelected;
256                     if( nextWasSelected )
257                     {
258                         m_pLastSelected = &*it;
259                     }
260                 }
261             }
262             else if( key == KEY_DOWN )
263             {
264                 // Scroll down one item
265                 if( ( it->parent()
266                       && next != it->parent()->end() )
267                     || &*it != m_pLastSelected )
268                 {
269                     (*it).m_selected = previousWasSelected;
270                 }
271                 if( previousWasSelected )
272                 {
273                     m_pLastSelected = &*it;
274                     previousWasSelected = false;
275                 }
276                 else
277                 {
278                     previousWasSelected = ( &*it == m_pLastSelected );
279                 }
280             }
281             else if( key == KEY_RIGHT )
282             {
283                 // Go down one level (and expand node)
284                 if( &*it == m_pLastSelected )
285                 {
286                     if( it->m_expanded )
287                     {
288                         if( it->size() )
289                         {
290                             it->m_selected = false;
291                             it->begin()->m_selected = true;
292                             m_pLastSelected = &*(it->begin());
293                         }
294                         else
295                         {
296                             m_rTree.action( &*it );
297                         }
298                     }
299                     else
300                     {
301                         it->m_expanded = true;
302                     }
303                 }
304             }
305             else if( key == KEY_LEFT )
306             {
307                 // Go up one level (and close node)
308                 if( &*it == m_pLastSelected )
309                 {
310                     if( it->m_expanded && it->size() )
311                     {
312                         it->m_expanded = false;
313                     }
314                     else
315                     {
316                         if( it->parent() && it->parent() != &m_rTree)
317                         {
318                             it->m_selected = false;
319                             m_pLastSelected = it->parent();
320                             m_pLastSelected->m_selected = true;
321                         }
322                     }
323                 }
324             }
325             it = next;
326         }
327
328         // Redraw the control
329         makeImage();
330         notifyLayout();
331     }
332
333     else if( rEvent.getAsString().find( "mouse:left" ) != string::npos )
334     {
335         EvtMouse &rEvtMouse = (EvtMouse&)rEvent;
336         const Position *pos = getPosition();
337         int yPos = ( rEvtMouse.getYPos() - pos->getTop() ) / itemHeight();
338         int xPos = rEvtMouse.getXPos() - pos->getLeft();
339         VarTree::Iterator it;
340         int index = 0;
341
342         if( rEvent.getAsString().find( "mouse:left:down:ctrl,shift" ) !=
343             string::npos )
344         {
345             // Flag to know if the currend item must be selected
346             bool select = false;
347             index = -1;
348             for( it = m_rTree.begin(); it != m_rTree.end(); )
349             {
350                 bool nextSelect = select;
351                 if( it == m_lastPos ) index = 0;
352                 if( index == yPos || &*it == m_pLastSelected )
353                 {
354                     if( select )
355                     {
356                         nextSelect = false;
357                     }
358                     else
359                     {
360                         select = true;
361                         nextSelect = true;
362                     }
363                 }
364                 it->m_selected = (*it).m_selected || select;
365                 select = nextSelect;
366                 if( index != -1 )
367                     index++;
368                 IT_DISP_LOOP_END( it );
369             }
370         }
371         else if( rEvent.getAsString().find( "mouse:left:down:ctrl" ) !=
372                  string::npos )
373         {
374             for( it = m_lastPos; it != m_rTree.end(); )
375             {
376                 if( index == yPos )
377                 {
378                     it->m_selected = !it->m_selected;
379                     m_pLastSelected = &*it;
380                     break;
381                 }
382                 index++;
383                 IT_DISP_LOOP_END( it );
384             }
385         }
386         else if( rEvent.getAsString().find( "mouse:left:down:shift" ) !=
387                  string::npos )
388         {
389             // Flag to know if the currend item must be selected
390             bool select = false;
391             index = -1;
392             for( it = m_rTree.begin(); it != m_rTree.end(); )
393             {
394                 bool nextSelect = select;
395                 if( it == m_lastPos ) index = 0;
396                 if( index == yPos || &*it == m_pLastSelected )
397                 {
398                     if( select )
399                     {
400                         nextSelect = false;
401                     }
402                     else
403                     {
404                         select = true;
405                         nextSelect = true;
406                     }
407                 }
408                 it->m_selected = select;
409                 select = nextSelect;
410                 if( index != -1 )
411                     index++;
412                 IT_DISP_LOOP_END( it );
413             }
414         }
415         else if( rEvent.getAsString().find( "mouse:left:down" ) !=
416                  string::npos )
417         {
418             for( it = m_lastPos; it != m_rTree.end(); )
419             {
420                 if( index == yPos )
421                 {
422                     it->m_selected = true;
423                     m_pLastSelected = &*it;
424                 }
425                 else
426                 {
427                     it->m_selected = false;
428                 }
429                 index ++;
430                 IT_DISP_LOOP_END( it );
431             }
432         }
433
434         else if( rEvent.getAsString().find( "mouse:left:dblclick" ) !=
435                  string::npos )
436         {
437             for( it = m_lastPos; it != m_rTree.end(); )
438             {
439                 if( index == yPos )
440                 {
441                     if( it->size() && xPos < it->depth() * itemImageWidth() )
442                     {
443                         it->m_expanded = !it->m_expanded;
444                     }
445                     else
446                     {
447                         it->m_selected = true;
448                         m_pLastSelected = &*it;
449                         // Execute the action associated to this item
450                         m_rTree.action( &*it );
451                     }
452                 }
453                 else
454                 {
455                     it->m_selected = false;
456                 }
457                 index ++;
458                 IT_DISP_LOOP_END( it );
459             }
460         }
461
462         // Redraw the control
463         makeImage();
464         notifyLayout();
465     }
466
467     else if( rEvent.getAsString().find( "scroll" ) != string::npos )
468     {
469         int direction = ((EvtScroll&)rEvent).getDirection();
470
471         double percentage = m_rTree.getPositionVar().get();
472         double step = 2.0 / (double)m_rTree.visibleItems();
473         if( direction == EvtScroll::kUp )
474         {
475             percentage += step;
476         }
477         else
478         {
479             percentage -= step;
480         }
481         m_rTree.getPositionVar().set( percentage );
482     }
483 }
484
485 bool CtrlTree::mouseOver( int x, int y ) const
486 {
487     const Position *pPos = getPosition();
488     return ( pPos
489        ? x >= 0 && x <= pPos->getWidth() && y >= 0 && y <= pPos->getHeight()
490        : false);
491 }
492
493 void CtrlTree::draw( OSGraphics &rImage, int xDest, int yDest )
494 {
495     if( m_pImage )
496     {
497         rImage.drawGraphics( *m_pImage, 0, 0, xDest, yDest );
498     }
499 }
500
501 void CtrlTree::autoScroll()
502 {
503     // Find the current playing stream
504     int playIndex = 0;
505     VarTree::Iterator it;
506     for( it = m_rTree.begin(); it != m_rTree.end(); )
507     {
508         if( it->m_playing ) break;
509         playIndex++;
510         IT_DISP_LOOP_END( it );
511     }
512
513     if( it == m_rTree.end() ) return;
514
515     // Find  m_lastPos
516     int lastPosIndex = 0;
517     for( it = m_rTree.begin(); it != m_rTree.end(); )
518     {
519         if( it == m_lastPos ) break;
520         lastPosIndex++;
521         IT_DISP_LOOP_END( it );
522     }
523
524     if( it == m_rTree.end() ) return;
525
526
527     if( it != m_rTree.end()
528         && ( playIndex < lastPosIndex
529            || playIndex > lastPosIndex + maxItems() ) )
530     {
531         // Scroll to have the playing stream visible
532         VarPercent &rVarPos = m_rTree.getPositionVar();
533         rVarPos.set( 1.0 - (double)playIndex / (double)m_rTree.visibleItems() );
534     }
535     else
536     {
537         makeImage();
538         notifyLayout();
539     }
540 }
541
542 void CtrlTree::makeImage()
543 {
544     if( m_pImage )
545     {
546         delete m_pImage;
547     }
548
549     // Get the size of the control
550     const Position *pPos = getPosition();
551     if( !pPos )
552     {
553         return;
554     }
555     int width = pPos->getWidth();
556     int height = pPos->getHeight();
557
558     int i_itemHeight = itemHeight();
559
560     // Create an image
561     OSFactory *pOsFactory = OSFactory::instance( getIntf() );
562     m_pImage = pOsFactory->createOSGraphics( width, height );
563
564     VarTree::Iterator it = m_lastPos;
565
566     if( m_pBgBitmap )
567     {
568         // Draw the background bitmap
569         ScaledBitmap bmp( getIntf(), *m_pBgBitmap, width, height );
570         m_pImage->drawBitmap( bmp, 0, 0 );
571
572         for( int yPos = 0; yPos < height; yPos += i_itemHeight )
573         {
574             int rectHeight = __MIN( i_itemHeight, height - yPos );
575             if( it != m_rTree.end() )
576             {
577                 if( (*it).m_selected )
578                 {
579                     m_pImage->fillRect( 0, yPos, width, rectHeight,
580                                         m_selColor );
581                 }
582                 IT_DISP_LOOP_END( it );
583             }
584         }
585     }
586     else
587     {
588         // FIXME (TRYME)
589         // Fill background with background color
590         uint32_t bgColor = m_bgColor1;
591         m_pImage->fillRect( 0, 0, width, height, bgColor );
592         for( int yPos = 0; yPos < height; yPos += i_itemHeight )
593         {
594             int rectHeight = __MIN( i_itemHeight, height - yPos );
595             if( it != m_rTree.end() )
596             {
597                 uint32_t color = ( it->m_selected ? m_selColor : bgColor );
598                 m_pImage->fillRect( 0, yPos, width, rectHeight, color );
599                 IT_DISP_LOOP_END( it );
600             }
601             else
602             {
603                 m_pImage->fillRect( 0, yPos, width, rectHeight, bgColor );
604             }
605             bgColor = ( bgColor == m_bgColor1 ? m_bgColor2 : m_bgColor1 );
606         }
607     }
608 //    fprintf( stderr, "done\n");
609
610     int bitmapWidth = itemImageWidth();
611
612     int yPos = 0;
613     it = m_lastPos;
614     while( it != m_rTree.end() && yPos < height )
615     {
616         const GenericBitmap *m_pCurBitmap;
617         UString *pStr = (UString*)(it->m_cString.get());
618         uint32_t color = ( it->m_playing ? m_playColor : m_fgColor );
619         // Draw the text
620         if( pStr != NULL ){
621             int depth = it->depth();
622             GenericBitmap *pText = m_rFont.drawString( *pStr, color, width - bitmapWidth * depth );
623             if( !pText )
624             {
625                 return;
626             }
627             m_pCurBitmap = it->size() ? ( it->m_expanded ? m_pOpenBitmap : m_pClosedBitmap ) : m_pItemBitmap ;
628             if( m_pCurBitmap )
629             {
630                 int yPos2 = yPos+(i_itemHeight-m_pCurBitmap->getHeight()+1)/2;
631                 m_pImage->drawBitmap( *m_pCurBitmap, 0, 0, bitmapWidth * (depth - 1 ), yPos2, m_pCurBitmap->getWidth(), __MIN( m_pCurBitmap->getHeight(), height -  yPos2), true );
632             }
633             else
634             {
635                 /* it would be nice to draw something */
636             }
637             yPos += i_itemHeight - pText->getHeight();
638             int ySrc = 0;
639             if( yPos < 0 )
640             {
641                 ySrc = - yPos;
642                 yPos = 0;
643             }
644             int lineHeight =  __MIN( pText->getHeight() - ySrc, height - yPos );
645             m_pImage->drawBitmap( *pText, 0, ySrc, bitmapWidth * depth, yPos,
646                                   pText->getWidth(),
647                                   lineHeight, true );
648             yPos += (pText->getHeight() - ySrc );
649             delete pText;
650         }
651         IT_DISP_LOOP_END( it );
652     }
653 }