]> git.sesse.net Git - vlc/blob - modules/gui/skins2/controls/ctrl_tree.cpp
17bc5f539409997b4dd5090e271f0d7a8a13fb7c
[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     // TODO TODO FIXME TODO TODO
239     if( rEvent.getAsString().find( "key:down" ) != string::npos )
240     {
241         int key = ((EvtKey&)rEvent).getKey();
242         VarTree::Iterator it = m_rTree.begin();
243         bool previousWasSelected = false;
244         while( it != m_rTree.end() )
245         {
246             VarTree::Iterator next = it;
247             IT_DISP_LOOP_END( next );
248             if( key == KEY_UP )
249             {
250                 //Scroll up one item
251                 if( ( it->parent()
252                       && it != it->parent()->begin() )
253                     || &*it != m_pLastSelected )
254                 {
255                     bool nextWasSelected = ( &*next == m_pLastSelected );
256                     it->m_selected = nextWasSelected;
257                     if( nextWasSelected )
258                     {
259                         m_pLastSelected = &*it;
260                     }
261                 }
262             }
263             else if( key == KEY_DOWN )
264             {
265                 // Scroll down one item
266                 if( ( it->parent()
267                       && next != it->parent()->end() )
268                     || &*it != m_pLastSelected )
269                 {
270                     (*it).m_selected = previousWasSelected;
271                 }
272                 if( previousWasSelected )
273                 {
274                     m_pLastSelected = &*it;
275                     previousWasSelected = false;
276                 }
277                 else
278                 {
279                     previousWasSelected = ( &*it == m_pLastSelected );
280                 }
281             }
282             else if( key == KEY_RIGHT )
283             {
284                 // Go down one level
285                 if( it->m_expanded )
286                 {
287                     if( it->size() )
288                     {
289                         /* FIXME : finir */
290                         m_pLastSelected = &*(it->begin());
291                     }
292                 }
293                 else
294                 {
295                     it->m_expanded = true;
296                 }
297             }
298             else if( key == KEY_LEFT )
299             {
300                 // Go up one level (and close node)
301                 // TODO
302                 it->m_expanded = false;
303             }
304             it = next;
305         }
306
307         // Redraw the control
308         makeImage();
309         notifyLayout();
310     }
311
312     else if( rEvent.getAsString().find( "mouse:left" ) != string::npos )
313     {
314         EvtMouse &rEvtMouse = (EvtMouse&)rEvent;
315         const Position *pos = getPosition();
316         int yPos = ( rEvtMouse.getYPos() - pos->getTop() ) / itemHeight();
317         int xPos = rEvtMouse.getXPos() - pos->getLeft();
318         VarTree::Iterator it;
319         int index = 0;
320
321         if( rEvent.getAsString().find( "mouse:left:down:ctrl,shift" ) !=
322             string::npos )
323         {
324             // Flag to know if the currend item must be selected
325             bool select = false;
326             index = -1;
327             for( it = m_rTree.begin(); it != m_rTree.end(); )
328             {
329                 bool nextSelect = select;
330                 if( it == m_lastPos ) index = 0;
331                 if( index == yPos || &*it == m_pLastSelected )
332                 {
333                     if( select )
334                     {
335                         nextSelect = false;
336                     }
337                     else
338                     {
339                         select = true;
340                         nextSelect = true;
341                     }
342                 }
343                 it->m_selected = (*it).m_selected || select;
344                 select = nextSelect;
345                 if( index != -1 )
346                     index++;
347                 IT_DISP_LOOP_END( it );
348             }
349         }
350         else if( rEvent.getAsString().find( "mouse:left:down:ctrl" ) !=
351                  string::npos )
352         {
353             for( it = m_lastPos; it != m_rTree.end(); )
354             {
355                 if( index == yPos )
356                 {
357                     it->m_selected = !it->m_selected;
358                     m_pLastSelected = &*it;
359                     break;
360                 }
361                 index++;
362                 IT_DISP_LOOP_END( it );
363             }
364         }
365         else if( rEvent.getAsString().find( "mouse:left:down:shift" ) !=
366                  string::npos )
367         {
368             // Flag to know if the currend item must be selected
369             bool select = false;
370             index = -1;
371             for( it = m_rTree.begin(); it != m_rTree.end(); )
372             {
373                 bool nextSelect = select;
374                 if( it == m_lastPos ) index = 0;
375                 if( index == yPos || &*it == m_pLastSelected )
376                 {
377                     if( select )
378                     {
379                         nextSelect = false;
380                     }
381                     else
382                     {
383                         select = true;
384                         nextSelect = true;
385                     }
386                 }
387                 it->m_selected = select;
388                 select = nextSelect;
389                 if( index != -1 )
390                     index++;
391                 IT_DISP_LOOP_END( it );
392             }
393         }
394         else if( rEvent.getAsString().find( "mouse:left:down" ) !=
395                  string::npos )
396         {
397             for( it = m_lastPos; it != m_rTree.end(); )
398             {
399                 if( index == yPos )
400                 {
401                     it->m_selected = true;
402                     m_pLastSelected = &*it;
403                 }
404                 else
405                 {
406                     it->m_selected = false;
407                 }
408                 index ++;
409                 IT_DISP_LOOP_END( it );
410             }
411         }
412
413         else if( rEvent.getAsString().find( "mouse:left:dblclick" ) !=
414                  string::npos )
415         {
416             for( it = m_lastPos; it != m_rTree.end(); )
417             {
418                 if( index == yPos )
419                 {
420                     if( it->size() && xPos < it->depth() * itemImageWidth() )
421                     {
422                         it->m_expanded = !it->m_expanded;
423                     }
424                     else
425                     {
426                         it->m_selected = true;
427                         m_pLastSelected = &*it;
428                         // Execute the action associated to this item
429                         m_rTree.action( &*it );
430                     }
431                 }
432                 else
433                 {
434                     it->m_selected = false;
435                 }
436                 index ++;
437                 IT_DISP_LOOP_END( it );
438             }
439         }
440
441         // Redraw the control
442         makeImage();
443         notifyLayout();
444     }
445
446     else if( rEvent.getAsString().find( "scroll" ) != string::npos )
447     {
448         int direction = ((EvtScroll&)rEvent).getDirection();
449
450         double percentage = m_rTree.getPositionVar().get();
451         double step = 2.0 / (double)m_rTree.visibleItems();
452         if( direction == EvtScroll::kUp )
453         {
454             percentage += step;
455         }
456         else
457         {
458             percentage -= step;
459         }
460         m_rTree.getPositionVar().set( percentage );
461     }
462 }
463
464 bool CtrlTree::mouseOver( int x, int y ) const
465 {
466     const Position *pPos = getPosition();
467     return ( pPos
468        ? x >= 0 && x <= pPos->getWidth() && y >= 0 && y <= pPos->getHeight()
469        : false);
470 }
471
472 void CtrlTree::draw( OSGraphics &rImage, int xDest, int yDest )
473 {
474     if( m_pImage )
475     {
476         rImage.drawGraphics( *m_pImage, 0, 0, xDest, yDest );
477     }
478 }
479
480 void CtrlTree::autoScroll()
481 {
482     // Find the current playing stream
483     int playIndex = 0;
484     VarTree::Iterator it;
485     for( it = m_rTree.begin(); it != m_rTree.end(); )
486     {
487         if( it->m_playing ) break;
488         playIndex++;
489         IT_DISP_LOOP_END( it );
490     }
491
492     if( it == m_rTree.end() ) return;
493
494     // Find  m_lastPos
495     int lastPosIndex = 0;
496     for( it = m_rTree.begin(); it != m_rTree.end(); )
497     {
498         if( it == m_lastPos ) break;
499         lastPosIndex++;
500         IT_DISP_LOOP_END( it );
501     }
502
503     if( it == m_rTree.end() ) return;
504
505
506     if( it != m_rTree.end()
507         && ( playIndex < lastPosIndex
508            || playIndex > lastPosIndex + maxItems() ) )
509     {
510         // Scroll to have the playing stream visible
511         VarPercent &rVarPos = m_rTree.getPositionVar();
512         rVarPos.set( 1.0 - (double)playIndex / (double)m_rTree.visibleItems() );
513     }
514     else
515     {
516         makeImage();
517         notifyLayout();
518     }
519 }
520
521 void CtrlTree::makeImage()
522 {
523     fprintf( stderr, "CtrlTree::makeImage()\n");
524     if( m_pImage )
525     {
526         delete m_pImage;
527     }
528
529     // Get the size of the control
530     const Position *pPos = getPosition();
531     if( !pPos )
532     {
533         return;
534     }
535     int width = pPos->getWidth();
536     int height = pPos->getHeight();
537
538     int i_itemHeight = itemHeight();
539
540     // Create an image
541     OSFactory *pOsFactory = OSFactory::instance( getIntf() );
542     m_pImage = pOsFactory->createOSGraphics( width, height );
543
544     VarTree::Iterator it = m_lastPos;
545
546     if( m_pBgBitmap )
547     {
548         // Draw the background bitmap
549         ScaledBitmap bmp( getIntf(), *m_pBgBitmap, width, height );
550         m_pImage->drawBitmap( bmp, 0, 0 );
551
552         for( int yPos = 0; yPos < height; yPos += i_itemHeight )
553         {
554             int rectHeight = __MIN( i_itemHeight, height - yPos );
555             if( it != m_rTree.end() )
556             {
557                 if( (*it).m_selected )
558                 {
559                     m_pImage->fillRect( 0, yPos, width, rectHeight,
560                                         m_selColor );
561                 }
562                 IT_DISP_LOOP_END( it );
563             }
564         }
565     }
566     else
567     {
568         // FIXME (TRYME)
569         // Fill background with background color
570         uint32_t bgColor = m_bgColor1;
571         m_pImage->fillRect( 0, 0, width, height, bgColor );
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                 uint32_t color = ( it->m_selected ? m_selColor : bgColor );
578                 m_pImage->fillRect( 0, yPos, width, rectHeight, color );
579                 IT_DISP_LOOP_END( it );
580             }
581             else
582             {
583                 m_pImage->fillRect( 0, yPos, width, rectHeight, bgColor );
584             }
585             bgColor = ( bgColor == m_bgColor1 ? m_bgColor2 : m_bgColor1 );
586         }
587     }
588 //    fprintf( stderr, "done\n");
589
590     int bitmapWidth = itemImageWidth();
591
592     int yPos = 0;
593     it = m_lastPos;
594     while( it != m_rTree.end() && yPos < height )
595     {
596         const GenericBitmap *m_pCurBitmap;
597         UString *pStr = (UString*)(it->m_cString.get());
598         uint32_t color = ( it->m_playing ? m_playColor : m_fgColor );
599         // Draw the text
600         if( pStr != NULL ){
601             int depth = it->depth();
602             GenericBitmap *pText = m_rFont.drawString( *pStr, color, width - bitmapWidth * depth );
603             if( !pText )
604             {
605                 return;
606             }
607             m_pCurBitmap = it->size() ? ( it->m_expanded ? m_pOpenBitmap : m_pClosedBitmap ) : m_pItemBitmap ;
608             if( m_pCurBitmap )
609             {
610                 int yPos2 = yPos+(i_itemHeight-m_pCurBitmap->getHeight()+1)/2;
611                 m_pImage->drawBitmap( *m_pCurBitmap, 0, 0, bitmapWidth * (depth - 1 ), yPos2, m_pCurBitmap->getWidth(), __MIN( m_pCurBitmap->getHeight(), height -  yPos2), true );
612             }
613             else
614             {
615                 /* it would be nice to draw something */
616             }
617             yPos += i_itemHeight - pText->getHeight();
618             int ySrc = 0;
619             if( yPos < 0 )
620             {
621                 ySrc = - yPos;
622                 yPos = 0;
623             }
624             int lineHeight =  __MIN( pText->getHeight() - ySrc, height - yPos );
625             m_pImage->drawBitmap( *pText, 0, ySrc, bitmapWidth * depth, yPos,
626                                   pText->getWidth(),
627                                   lineHeight, true );
628             yPos += (pText->getHeight() - ySrc );
629             delete pText;
630         }
631         IT_DISP_LOOP_END( it );
632     }
633 }