]> git.sesse.net Git - vlc/blob - modules/gui/skins2/controls/ctrl_tree.cpp
add KEY_ENTER and KEY_SPACE to launch action()
[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             else if( key == KEY_ENTER || key == KEY_SPACE )
326             {
327                 // Go up one level (and close node)
328                 if( &*it == m_pLastSelected )
329                 {
330                     m_rTree.action( &*it );
331                 }
332             }
333             it = next;
334         }
335
336         // Redraw the control
337         makeImage();
338         notifyLayout();
339     }
340
341     else if( rEvent.getAsString().find( "mouse:left" ) != string::npos )
342     {
343         EvtMouse &rEvtMouse = (EvtMouse&)rEvent;
344         const Position *pos = getPosition();
345         int yPos = ( rEvtMouse.getYPos() - pos->getTop() ) / itemHeight();
346         int xPos = rEvtMouse.getXPos() - pos->getLeft();
347         VarTree::Iterator it;
348         int index = 0;
349
350         if( rEvent.getAsString().find( "mouse:left:down:ctrl,shift" ) !=
351             string::npos )
352         {
353             // Flag to know if the currend item must be selected
354             bool select = false;
355             index = -1;
356             for( it = m_rTree.begin(); it != m_rTree.end(); )
357             {
358                 bool nextSelect = select;
359                 if( it == m_lastPos ) index = 0;
360                 if( index == yPos || &*it == m_pLastSelected )
361                 {
362                     if( select )
363                     {
364                         nextSelect = false;
365                     }
366                     else
367                     {
368                         select = true;
369                         nextSelect = true;
370                     }
371                 }
372                 it->m_selected = (*it).m_selected || select;
373                 select = nextSelect;
374                 if( index != -1 )
375                     index++;
376                 IT_DISP_LOOP_END( it );
377             }
378         }
379         else if( rEvent.getAsString().find( "mouse:left:down:ctrl" ) !=
380                  string::npos )
381         {
382             for( it = m_lastPos; it != m_rTree.end(); )
383             {
384                 if( index == yPos )
385                 {
386                     it->m_selected = !it->m_selected;
387                     m_pLastSelected = &*it;
388                     break;
389                 }
390                 index++;
391                 IT_DISP_LOOP_END( it );
392             }
393         }
394         else if( rEvent.getAsString().find( "mouse:left:down:shift" ) !=
395                  string::npos )
396         {
397             // Flag to know if the currend item must be selected
398             bool select = false;
399             index = -1;
400             for( it = m_rTree.begin(); it != m_rTree.end(); )
401             {
402                 bool nextSelect = select;
403                 if( it == m_lastPos ) index = 0;
404                 if( index == yPos || &*it == m_pLastSelected )
405                 {
406                     if( select )
407                     {
408                         nextSelect = false;
409                     }
410                     else
411                     {
412                         select = true;
413                         nextSelect = true;
414                     }
415                 }
416                 it->m_selected = select;
417                 select = nextSelect;
418                 if( index != -1 )
419                     index++;
420                 IT_DISP_LOOP_END( it );
421             }
422         }
423         else if( rEvent.getAsString().find( "mouse:left:down" ) !=
424                  string::npos )
425         {
426             for( it = m_lastPos; it != m_rTree.end(); )
427             {
428                 if( index == yPos )
429                 {
430                     it->m_selected = true;
431                     m_pLastSelected = &*it;
432                 }
433                 else
434                 {
435                     it->m_selected = false;
436                 }
437                 index ++;
438                 IT_DISP_LOOP_END( it );
439             }
440         }
441
442         else if( rEvent.getAsString().find( "mouse:left:dblclick" ) !=
443                  string::npos )
444         {
445             for( it = m_lastPos; it != m_rTree.end(); )
446             {
447                 if( index == yPos )
448                 {
449                     if( it->size() && xPos < it->depth() * itemImageWidth() )
450                     {
451                         it->m_expanded = !it->m_expanded;
452                     }
453                     else
454                     {
455                         it->m_selected = true;
456                         m_pLastSelected = &*it;
457                         // Execute the action associated to this item
458                         m_rTree.action( &*it );
459                     }
460                 }
461                 else
462                 {
463                     it->m_selected = false;
464                 }
465                 index ++;
466                 IT_DISP_LOOP_END( it );
467             }
468         }
469
470         // Redraw the control
471         makeImage();
472         notifyLayout();
473     }
474
475     else if( rEvent.getAsString().find( "scroll" ) != string::npos )
476     {
477         int direction = ((EvtScroll&)rEvent).getDirection();
478
479         double percentage = m_rTree.getPositionVar().get();
480         double step = 2.0 / (double)m_rTree.visibleItems();
481         if( direction == EvtScroll::kUp )
482         {
483             percentage += step;
484         }
485         else
486         {
487             percentage -= step;
488         }
489         m_rTree.getPositionVar().set( percentage );
490     }
491 }
492
493 bool CtrlTree::mouseOver( int x, int y ) const
494 {
495     const Position *pPos = getPosition();
496     return ( pPos
497        ? x >= 0 && x <= pPos->getWidth() && y >= 0 && y <= pPos->getHeight()
498        : false);
499 }
500
501 void CtrlTree::draw( OSGraphics &rImage, int xDest, int yDest )
502 {
503     if( m_pImage )
504     {
505         rImage.drawGraphics( *m_pImage, 0, 0, xDest, yDest );
506     }
507 }
508
509 void CtrlTree::autoScroll()
510 {
511     // Find the current playing stream
512     int playIndex = 0;
513     VarTree::Iterator it;
514     for( it = m_rTree.begin(); it != m_rTree.end(); )
515     {
516         if( it->m_playing ) break;
517         playIndex++;
518         IT_DISP_LOOP_END( it );
519     }
520
521     if( it == m_rTree.end() ) return;
522
523     // Find  m_lastPos
524     int lastPosIndex = 0;
525     for( it = m_rTree.begin(); it != m_rTree.end(); )
526     {
527         if( it == m_lastPos ) break;
528         lastPosIndex++;
529         IT_DISP_LOOP_END( it );
530     }
531
532     if( it == m_rTree.end() ) return;
533
534
535     if( it != m_rTree.end()
536         && ( playIndex < lastPosIndex
537            || playIndex > lastPosIndex + maxItems() ) )
538     {
539         // Scroll to have the playing stream visible
540         VarPercent &rVarPos = m_rTree.getPositionVar();
541         rVarPos.set( 1.0 - (double)playIndex / (double)m_rTree.visibleItems() );
542     }
543     else
544     {
545         makeImage();
546         notifyLayout();
547     }
548 }
549
550 void CtrlTree::makeImage()
551 {
552     if( m_pImage )
553     {
554         delete m_pImage;
555     }
556
557     // Get the size of the control
558     const Position *pPos = getPosition();
559     if( !pPos )
560     {
561         return;
562     }
563     int width = pPos->getWidth();
564     int height = pPos->getHeight();
565
566     int i_itemHeight = itemHeight();
567
568     // Create an image
569     OSFactory *pOsFactory = OSFactory::instance( getIntf() );
570     m_pImage = pOsFactory->createOSGraphics( width, height );
571
572     VarTree::Iterator it = m_lastPos;
573
574     if( m_pBgBitmap )
575     {
576         // Draw the background bitmap
577         ScaledBitmap bmp( getIntf(), *m_pBgBitmap, width, height );
578         m_pImage->drawBitmap( bmp, 0, 0 );
579
580         for( int yPos = 0; yPos < height; yPos += i_itemHeight )
581         {
582             int rectHeight = __MIN( i_itemHeight, height - yPos );
583             if( it != m_rTree.end() )
584             {
585                 if( (*it).m_selected )
586                 {
587                     m_pImage->fillRect( 0, yPos, width, rectHeight,
588                                         m_selColor );
589                 }
590                 IT_DISP_LOOP_END( it );
591             }
592         }
593     }
594     else
595     {
596         // FIXME (TRYME)
597         // Fill background with background color
598         uint32_t bgColor = m_bgColor1;
599         m_pImage->fillRect( 0, 0, width, height, bgColor );
600         for( int yPos = 0; yPos < height; yPos += i_itemHeight )
601         {
602             int rectHeight = __MIN( i_itemHeight, height - yPos );
603             if( it != m_rTree.end() )
604             {
605                 uint32_t color = ( it->m_selected ? m_selColor : bgColor );
606                 m_pImage->fillRect( 0, yPos, width, rectHeight, color );
607                 IT_DISP_LOOP_END( it );
608             }
609             else
610             {
611                 m_pImage->fillRect( 0, yPos, width, rectHeight, bgColor );
612             }
613             bgColor = ( bgColor == m_bgColor1 ? m_bgColor2 : m_bgColor1 );
614         }
615     }
616 //    fprintf( stderr, "done\n");
617
618     int bitmapWidth = itemImageWidth();
619
620     int yPos = 0;
621     it = m_lastPos;
622     while( it != m_rTree.end() && yPos < height )
623     {
624         const GenericBitmap *m_pCurBitmap;
625         UString *pStr = (UString*)(it->m_cString.get());
626         uint32_t color = ( it->m_playing ? m_playColor : m_fgColor );
627         // Draw the text
628         if( pStr != NULL ){
629             int depth = it->depth();
630             GenericBitmap *pText = m_rFont.drawString( *pStr, color, width - bitmapWidth * depth );
631             if( !pText )
632             {
633                 return;
634             }
635             m_pCurBitmap = it->size() ? ( it->m_expanded ? m_pOpenBitmap : m_pClosedBitmap ) : m_pItemBitmap ;
636             if( m_pCurBitmap )
637             {
638                 int yPos2 = yPos+(i_itemHeight-m_pCurBitmap->getHeight()+1)/2;
639                 m_pImage->drawBitmap( *m_pCurBitmap, 0, 0, bitmapWidth * (depth - 1 ), yPos2, m_pCurBitmap->getWidth(), __MIN( m_pCurBitmap->getHeight(), height -  yPos2), true );
640             }
641             else
642             {
643                 /* it would be nice to draw something */
644             }
645             yPos += i_itemHeight - pText->getHeight();
646             int ySrc = 0;
647             if( yPos < 0 )
648             {
649                 ySrc = - yPos;
650                 yPos = 0;
651             }
652             int lineHeight =  __MIN( pText->getHeight() - ySrc, height - yPos );
653             m_pImage->drawBitmap( *pText, 0, ySrc, bitmapWidth * depth, yPos,
654                                   pText->getWidth(),
655                                   lineHeight, true );
656             yPos += (pText->getHeight() - ySrc );
657             delete pText;
658         }
659         IT_DISP_LOOP_END( it );
660     }
661 }