]> git.sesse.net Git - vlc/blob - modules/gui/skins2/controls/ctrl_tree.cpp
* Playtree start. Basic functionalities work. Still needs a lot of
[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::maxItems()
104 {
105     const Position *pPos = getPosition();
106     if( !pPos )
107     {
108         return -1;
109     }
110     return pPos->getHeight() / itemHeight();
111 }
112
113
114 void CtrlTree::onUpdate( Subject<VarTree> &rTree )
115 {
116     autoScroll();
117     m_pLastSelected = NULL;
118 }
119
120 void CtrlTree::onUpdate( Subject<VarPercent> &rPercent )
121 {
122     // Determine what is the first item to display
123     VarTree::Iterator it = m_rTree.begin();
124
125     int excessItems = m_rTree.visibleItems() - maxItems();
126
127     if( excessItems > 0)
128     {
129         VarPercent &rVarPos = m_rTree.getPositionVar();
130         // a simple (int)(...) causes rounding errors !
131 #ifdef _MSC_VER
132 #   define lrint (int)
133 #endif
134         it = m_rTree.visibleItem(lrint( (1.0 - rVarPos.get()) * (double)excessItems ) + 1); /* FIXME : shouldn't need this +1 */
135     }
136     if( m_lastPos != it )
137     {
138         // Redraw the control if the position has changed
139         m_lastPos = it;
140         makeImage();
141         notifyLayout();
142     }
143 }
144
145 void CtrlTree::onResize()
146 {
147 // FIXME : shouldn't be the same as the onUpdate function ... but i'm lazy
148     // Determine what is the first item to display
149     VarTree::Iterator it = m_rTree.begin();
150
151     int excessItems = m_rTree.visibleItems() - maxItems();
152
153     if( excessItems > 0)
154     {
155         VarPercent &rVarPos = m_rTree.getPositionVar();
156         // a simple (int)(...) causes rounding errors !
157 #ifdef _MSC_VER
158 #   define lrint (int)
159 #endif
160         it = m_rTree.visibleItem(lrint( (1.0 - rVarPos.get()) * (double)excessItems ) + 1); /* FIXME : shouldn't need this +1 */
161     }
162     // Redraw the control if the position has changed
163     m_lastPos = it;
164     makeImage();
165     notifyLayout();
166 #if 0
167     // Determine what is the first item to display
168     VarTree::Iterator it = m_rTree.begin();
169
170     int excessItems = m_rTree.visibleItems() - maxItems();
171
172     if( excessItems > 0)
173     {
174         /* FIXME VarPercent &rVarPos = m_rTree.getPositionVar();
175         double newVal = 1.0 - (double)m_lastPos / excessItems;
176         if( newVal >= 0 )
177         {
178             // Change the position to keep the same first displayed item
179             rVarPos.set( 1.0 - (double)m_lastPos / excessItems );
180         }
181         else
182         {
183             // We cannot keep the current first item
184             m_lastPos = excessItems;
185         }*/
186         it = m_rTree.visibleItem( excessItems );
187     }
188     makeImage();
189     notifyLayout();
190 #endif
191 }
192
193 void CtrlTree::onPositionChange()
194 {
195     makeImage();
196     notifyLayout();
197 }
198
199 #define IT_DISP_LOOP_END( a )  \
200                 if( a ->m_expanded && a ->size() ) \
201                 { \
202                     a = a ->begin(); \
203                 } \
204                 else \
205                 { \
206                     VarTree::Iterator it_old = a; \
207                     a ++; \
208                     if( it_old->parent() && it_old->parent()->end() == a ) \
209                     { \
210                         a = it_old->uncle(); \
211                     } \
212                 }
213 void CtrlTree::handleEvent( EvtGeneric &rEvent )
214 {
215     // TODO TODO FIXME TODO TODO
216     if( rEvent.getAsString().find( "key:down" ) != string::npos )
217     {
218         int key = ((EvtKey&)rEvent).getKey();
219         VarTree::Iterator it = m_rTree.begin();
220         bool previousWasSelected = false;
221         while( it != m_rTree.end() )
222         {
223             VarTree::Iterator next = it;
224             IT_DISP_LOOP_END( next );
225             if( key == KEY_UP )
226             {
227                 //Scroll up one item
228                 if( ( it->parent()
229                       && it != it->parent()->begin() )
230                     || &*it != m_pLastSelected )
231                 {
232                     bool nextWasSelected = ( &*next == m_pLastSelected );
233                     it->m_selected = nextWasSelected;
234                     if( nextWasSelected )
235                     {
236                         m_pLastSelected = &*it;
237                     }
238                 }
239             }
240             else if( key == KEY_DOWN )
241             {
242                 // Scroll down one item
243                 if( ( it->parent()
244                       && next != it->parent()->end() )
245                     || &*it != m_pLastSelected )
246                 {
247                     (*it).m_selected = previousWasSelected;
248                 }
249                 if( previousWasSelected )
250                 {
251                     m_pLastSelected = &*it;
252                     previousWasSelected = false;
253                 }
254                 else
255                 {
256                     previousWasSelected = ( &*it == m_pLastSelected );
257                 }
258             }
259             else if( key == KEY_RIGHT )
260             {
261                 // Go down one level
262                 if( it->m_expanded )
263                 {
264                     if( it->size() )
265                     {
266                         /* FIXME : finir */
267                         m_pLastSelected = &*(it->begin());
268                     }
269                 }
270                 else
271                 {
272                     it->m_expanded = true;
273                 }
274             }
275             else if( key == KEY_LEFT )
276             {
277                 // Go up one level (and close node)
278                 // TODO
279                 it->m_expanded = false;
280             }
281             it = next;
282         }
283
284         // Redraw the control
285         makeImage();
286         notifyLayout();
287     }
288
289     else if( rEvent.getAsString().find( "mouse:left" ) != string::npos )
290     {
291         EvtMouse &rEvtMouse = (EvtMouse&)rEvent;
292         const Position *pos = getPosition();
293         int yPos = ( rEvtMouse.getYPos() - pos->getTop() ) / itemHeight();
294         VarTree::Iterator it;
295         int index = 0;
296
297         // TODO : add all other mouse controls
298         /**/ if( rEvent.getAsString().find( "mouse:left:down" ) !=
299                  string::npos )
300         {
301             for( it = m_lastPos; it != m_rTree.end(); )
302             {
303                 if( index == yPos )
304                 {
305                     it->m_selected = true;
306                     m_pLastSelected = &*it;
307                 }
308                 else
309                 {
310                     it->m_selected = false;
311                 }
312                 index ++;
313                 IT_DISP_LOOP_END( it );
314             }
315         }
316
317         else if( rEvent.getAsString().find( "mouse:left:dblclick" ) !=
318                  string::npos )
319         {
320             for( it = m_lastPos; it != m_rTree.end(); )
321             {
322                 if( index == yPos )
323                 {
324                     it->m_selected = true;
325                     m_pLastSelected = &*it;
326                     // Execute the action associated to this item
327                     m_rTree.action( &*it );
328                 }
329                 else
330                 {
331                     it->m_selected = false;
332                 }
333                 index ++;
334                 IT_DISP_LOOP_END( it );
335             }
336         }
337
338         // Redraw the control
339         makeImage();
340         notifyLayout();
341     }
342
343     else if( rEvent.getAsString().find( "scroll" ) != string::npos )
344     {
345         int direction = ((EvtScroll&)rEvent).getDirection();
346
347         double percentage = m_rTree.getPositionVar().get();
348         if( direction == EvtScroll::kUp )
349         {
350             percentage += SCROLL_STEP;
351         }
352         else
353         {
354             percentage -= SCROLL_STEP;
355         }
356         m_rTree.getPositionVar().set( percentage );
357     }
358 }
359
360 bool CtrlTree::mouseOver( int x, int y ) const
361 {
362     const Position *pPos = getPosition();
363     return ( pPos
364        ? x >= 0 && x <= pPos->getWidth() && y >= 0 && y <= pPos->getHeight()
365        : false);
366 }
367
368 void CtrlTree::draw( OSGraphics &rImage, int xDest, int yDest )
369 {
370     if( m_pImage )
371     {
372         rImage.drawGraphics( *m_pImage, 0, 0, xDest, yDest );
373     }
374 }
375
376 void CtrlTree::autoScroll()
377 {
378     // TODO FIXME TODO
379     makeImage();
380     notifyLayout();
381 }
382
383 void CtrlTree::makeImage()
384 {
385     if( m_pImage )
386     {
387         delete m_pImage;
388     }
389
390     // Get the size of the control
391     const Position *pPos = getPosition();
392     if( !pPos )
393     {
394         return;
395     }
396     int width = pPos->getWidth();
397     int height = pPos->getHeight();
398
399     int i_itemHeight = itemHeight();
400
401     // Create an image
402     OSFactory *pOsFactory = OSFactory::instance( getIntf() );
403     m_pImage = pOsFactory->createOSGraphics( width, height );
404
405     VarTree::Iterator it = m_lastPos;
406
407     if( m_pBgBitmap )
408     {
409         // Draw the background bitmap
410         ScaledBitmap bmp( getIntf(), *m_pBgBitmap, width, height );
411         m_pImage->drawBitmap( bmp, 0, 0 );
412
413         // FIXME : Take care of the selection color
414         for( int yPos = 0; yPos < height; yPos += i_itemHeight )
415         {
416             int rectHeight = __MIN( i_itemHeight, height - yPos );
417             if( it != m_rTree.end() )
418             {
419                 if( (*it).m_selected )
420                 {
421                     m_pImage->fillRect( 0, yPos, width, rectHeight,
422                                         m_selColor );
423                 }
424                 IT_DISP_LOOP_END( it );
425             }
426         }
427     }
428     else
429     {
430         // FIXME (TRYME)
431         // Fill background with background color
432         uint32_t bgColor = m_bgColor1;
433         m_pImage->fillRect( 0, 0, width, height, bgColor );
434         for( int yPos = 0; yPos < height; yPos += i_itemHeight )
435         {
436             int rectHeight = __MIN( i_itemHeight, height - yPos );
437             if( it != m_rTree.end() )
438             {
439                 uint32_t color = ( it->m_selected ? m_selColor : bgColor );
440                 m_pImage->fillRect( 0, yPos, width, rectHeight, color );
441                 IT_DISP_LOOP_END( it );
442             }
443             else
444             {
445                 m_pImage->fillRect( 0, yPos, width, rectHeight, bgColor );
446             }
447             bgColor = ( bgColor == m_bgColor1 ? m_bgColor2 : m_bgColor1 );
448         }
449     }
450 //    fprintf( stderr, "done\n");
451
452     int bitmapWidth = 5;
453     if( m_pClosedBitmap )
454     {
455         bitmapWidth = __MAX( m_pClosedBitmap->getWidth(), bitmapWidth );
456     }
457     if( m_pOpenBitmap )
458     {
459         bitmapWidth = __MAX( m_pOpenBitmap->getWidth(), bitmapWidth );
460     }
461     if( m_pItemBitmap )
462     {
463         bitmapWidth = __MAX( m_pItemBitmap->getWidth(), bitmapWidth );
464     }
465     bitmapWidth += 2;
466
467     // FIXME : Draw the items
468     int yPos = 0;
469     it = m_lastPos;
470     while( it != m_rTree.end() && yPos < height )
471     {
472         const GenericBitmap *m_pCurBitmap;
473         UString *pStr = (UString*)(it->m_cString.get());
474         uint32_t color = ( it->m_playing ? m_playColor : m_fgColor );
475         // Draw the text
476         if( pStr != NULL ){
477             int depth = it->depth();
478             GenericBitmap *pText = m_rFont.drawString( *pStr, color, width - bitmapWidth * depth );
479             if( !pText )
480             {
481                 return;
482             }
483             m_pCurBitmap = it->size() ? ( it->m_expanded ? m_pOpenBitmap : m_pClosedBitmap ) : m_pItemBitmap ;
484             if( m_pCurBitmap )
485             {
486                 int yPos2 = yPos+(i_itemHeight-m_pCurBitmap->getHeight()+1)/2;
487                 m_pImage->drawBitmap( *m_pCurBitmap, 0, 0, bitmapWidth * (depth - 1 ), yPos2, m_pCurBitmap->getWidth(), __MIN( m_pCurBitmap->getHeight(), height -  yPos2), true );
488             }
489             else
490             {
491                 /* it would be nice to draw something */
492             }
493             yPos += i_itemHeight - pText->getHeight();
494             int ySrc = 0;
495             if( yPos < 0 )
496             {
497                 ySrc = - yPos;
498                 yPos = 0;
499             }
500             int lineHeight =  __MIN( pText->getHeight() - ySrc, height - yPos );
501             m_pImage->drawBitmap( *pText, 0, ySrc, bitmapWidth * depth, yPos,
502                                   pText->getWidth(),
503                                   lineHeight, true );
504             yPos += (pText->getHeight() - ySrc );
505             delete pText;
506         }
507         IT_DISP_LOOP_END( it );
508     }
509 }