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