]> git.sesse.net Git - vlc/blob - modules/gui/skins2/controls/ctrl_list.cpp
* skins2: New Playlist.bgimage attribute, to specify a background image for
[vlc] / modules / gui / skins2 / controls / ctrl_list.cpp
1 /*****************************************************************************
2  * ctrl_list.cpp
3  *****************************************************************************
4  * Copyright (C) 2003 VideoLAN
5  * $Id$
6  *
7  * Authors: Cyril Deguet     <asmax@via.ecp.fr>
8  *          Olivier Teulière <ipkiss@via.ecp.fr>
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
23  *****************************************************************************/
24
25 #include <math.h>
26 #include "ctrl_list.hpp"
27 #include "../src/os_factory.hpp"
28 #include "../src/os_graphics.hpp"
29 #include "../src/generic_bitmap.hpp"
30 #include "../src/generic_font.hpp"
31 #include "../src/scaled_bitmap.hpp"
32 #include "../utils/position.hpp"
33 #include "../utils/ustring.hpp"
34 #include "../events/evt_key.hpp"
35 #include "../events/evt_mouse.hpp"
36 #include "../events/evt_scroll.hpp"
37 #include "vlc_keys.h"
38 #ifdef sun
39 #   include "solaris_specific.h" // for lrint
40 #endif
41
42 #define SCROLL_STEP 0.05
43 #define LINE_INTERVAL 1  // Number of pixels inserted between 2 lines
44
45
46 CtrlList::CtrlList( intf_thread_t *pIntf, VarList &rList,
47                     const GenericFont &rFont, const GenericBitmap *pBitmap,
48                     uint32_t fgColor, uint32_t playColor, uint32_t bgColor1,
49                     uint32_t bgColor2, uint32_t selColor,
50                     const UString &rHelp, VarBool *pVisible ):
51     CtrlGeneric( pIntf, rHelp, pVisible ), m_rList( rList ), m_rFont( rFont ),
52     m_pBitmap( pBitmap ), m_fgColor( fgColor ), m_playColor( playColor ),
53     m_bgColor1( bgColor1 ), m_bgColor2( bgColor2 ), m_selColor( selColor ),
54     m_pLastSelected( NULL ), m_pImage( NULL ), m_lastPos( 0 )
55 {
56     // Observe the list and position variables
57     m_rList.addObserver( this );
58     m_rList.getPositionVar().addObserver( this );
59
60     makeImage();
61 }
62
63
64 CtrlList::~CtrlList()
65 {
66     m_rList.getPositionVar().delObserver( this );
67     m_rList.delObserver( this );
68     if( m_pImage )
69     {
70         delete m_pImage;
71     }
72 }
73
74
75 void CtrlList::onUpdate( Subject<VarList> &rList )
76 {
77     autoScroll();
78     m_pLastSelected = NULL;
79 }
80
81
82 void CtrlList::onUpdate( Subject<VarPercent> &rPercent )
83 {
84     // Get the size of the control
85     const Position *pPos = getPosition();
86     if( !pPos )
87     {
88         return;
89     }
90     int height = pPos->getHeight();
91
92     // How many lines can be displayed ?
93     int itemHeight = m_rFont.getSize() + LINE_INTERVAL;
94     int maxItems = height / itemHeight;
95
96     // Determine what is the first item to display
97     VarPercent &rVarPos = m_rList.getPositionVar();
98     int firstItem = 0;
99     int excessItems = m_rList.size() - maxItems;
100     if( excessItems > 0 )
101     {
102         // a simple (int)(...) causes rounding errors !
103 #ifdef _MSC_VER
104 #   define lrint (int)
105 #endif
106         firstItem = lrint( (1.0 - rVarPos.get()) * (double)excessItems );
107     }
108     if( m_lastPos != firstItem )
109     {
110         // Redraw the control if the position has changed
111         m_lastPos = firstItem;
112         makeImage();
113         notifyLayout();
114     }
115 }
116
117
118 void CtrlList::onResize()
119 {
120     // Get the size of the control
121     const Position *pPos = getPosition();
122     if( !pPos )
123     {
124         return;
125     }
126     int height = pPos->getHeight();
127
128     // How many lines can be displayed ?
129     int itemHeight = m_rFont.getSize() + LINE_INTERVAL;
130     int maxItems = height / itemHeight;
131
132     // Update the position variable
133     VarPercent &rVarPos = m_rList.getPositionVar();
134     int excessItems = m_rList.size() - maxItems;
135     if( excessItems > 0 )
136     {
137         double newVal = 1.0 - (double)m_lastPos / excessItems;
138         if( newVal >= 0 )
139         {
140             // Change the position to keep the same first displayed item
141             rVarPos.set( 1.0 - (double)m_lastPos / excessItems );
142         }
143         else
144         {
145             // We cannot keep the current first item
146             m_lastPos = excessItems;
147         }
148     }
149
150     makeImage();
151     notifyLayout();
152 }
153
154
155 void CtrlList::onPositionChange()
156 {
157     makeImage();
158     notifyLayout();
159 }
160
161
162 void CtrlList::handleEvent( EvtGeneric &rEvent )
163 {
164     if( rEvent.getAsString().find( "key:down" ) != string::npos )
165     {
166         int key = ((EvtKey&)rEvent).getKey();
167         VarList::Iterator it = m_rList.begin();
168         bool previousWasSelected = false;
169         while( it != m_rList.end() )
170         {
171             VarList::Iterator next = it;
172             ++next;
173             if( key == KEY_UP )
174             {
175                 // Scroll up one item
176                 if( it != m_rList.begin() || &*it != m_pLastSelected )
177                 {
178                     bool nextWasSelected = ( &*next == m_pLastSelected );
179                     (*it).m_selected = nextWasSelected;
180                     if( nextWasSelected )
181                     {
182                         m_pLastSelected = &*it;
183                     }
184                 }
185             }
186             else if( key == KEY_DOWN )
187             {
188                 // Scroll down one item
189                 if( next != m_rList.end() || &*it != m_pLastSelected )
190                 {
191                     (*it).m_selected = previousWasSelected;
192                 }
193                 if( previousWasSelected )
194                 {
195                     m_pLastSelected = &*it;
196                     previousWasSelected = false;
197                 }
198                 else
199                 {
200                     previousWasSelected = ( &*it == m_pLastSelected );
201                 }
202             }
203             it = next;
204         }
205
206         // Redraw the control
207         makeImage();
208         notifyLayout();
209     }
210
211     else if( rEvent.getAsString().find( "mouse:left" ) != string::npos )
212     {
213         EvtMouse &rEvtMouse = (EvtMouse&)rEvent;
214         const Position *pos = getPosition();
215         int yPos = m_lastPos + ( rEvtMouse.getYPos() - pos->getTop() ) /
216             (m_rFont.getSize() + LINE_INTERVAL);
217         VarList::Iterator it;
218         int index = 0;
219
220         if( rEvent.getAsString().find( "mouse:left:down:ctrl,shift" ) !=
221                  string::npos )
222         {
223             // Flag to know if the current item must be selected
224             bool select = false;
225             for( it = m_rList.begin(); it != m_rList.end(); it++ )
226             {
227                 bool nextSelect = select;
228                 if( index == yPos || &*it == m_pLastSelected )
229                 {
230                     if( select )
231                     {
232                         nextSelect = false;
233                     }
234                     else
235                     {
236                         select = true;
237                         nextSelect = true;
238                     }
239                 }
240                 (*it).m_selected = (*it).m_selected || select;
241                 select = nextSelect;
242                 index++;
243             }
244         }
245
246         else if( rEvent.getAsString().find( "mouse:left:down:ctrl" ) !=
247                  string::npos )
248         {
249             for( it = m_rList.begin(); it != m_rList.end(); it++ )
250             {
251                 if( index == yPos )
252                 {
253                     (*it).m_selected = ! (*it).m_selected;
254                     m_pLastSelected = &*it;
255                     break;
256                 }
257                 index++;
258             }
259         }
260
261         else if( rEvent.getAsString().find( "mouse:left:down:shift" ) !=
262                  string::npos )
263         {
264             // Flag to know if the current item must be selected
265             bool select = false;
266             for( it = m_rList.begin(); it != m_rList.end(); it++ )
267             {
268                 bool nextSelect = select;
269                 if( index == yPos ||  &*it == m_pLastSelected )
270                 {
271                     if( select )
272                     {
273                         nextSelect = false;
274                     }
275                     else
276                     {
277                         select = true;
278                         nextSelect = true;
279                     }
280                 }
281                 (*it).m_selected = select;
282                 select = nextSelect;
283                 index++;
284             }
285         }
286
287         else if( rEvent.getAsString().find( "mouse:left:down" ) !=
288                  string::npos )
289         {
290             for( it = m_rList.begin(); it != m_rList.end(); it++ )
291             {
292                 if( index == yPos )
293                 {
294                     (*it).m_selected = true;
295                     m_pLastSelected = &*it;
296                 }
297                 else
298                 {
299                     (*it).m_selected = false;
300                 }
301                 index++;
302             }
303         }
304
305         else if( rEvent.getAsString().find( "mouse:left:dblclick" ) !=
306                  string::npos )
307         {
308             for( it = m_rList.begin(); it != m_rList.end(); it++ )
309             {
310                 if( index == yPos )
311                 {
312                     (*it).m_selected = true;
313                     m_pLastSelected = &*it;
314                     // Execute the action associated to this item
315                     m_rList.action( &*it );
316                 }
317                 else
318                 {
319                     (*it).m_selected = false;
320                 }
321                 index++;
322             }
323         }
324
325         // Redraw the control
326         makeImage();
327         notifyLayout();
328     }
329
330     else if( rEvent.getAsString().find( "scroll" ) != string::npos )
331     {
332         int direction = ((EvtScroll&)rEvent).getDirection();
333
334         double percentage = m_rList.getPositionVar().get();
335         if( direction == EvtScroll::kUp )
336         {
337             percentage += SCROLL_STEP;
338         }
339         else
340         {
341             percentage -= SCROLL_STEP;
342         }
343         m_rList.getPositionVar().set( percentage );
344     }
345 }
346
347
348 bool CtrlList::mouseOver( int x, int y ) const
349 {
350     const Position *pPos = getPosition();
351     if( pPos )
352     {
353         int width = pPos->getWidth();
354         int height = pPos->getHeight();
355         return ( x >= 0 && x <= width && y >= 0 && y <= height );
356     }
357     return false;
358 }
359
360
361 void CtrlList::draw( OSGraphics &rImage, int xDest, int yDest )
362 {
363     if( m_pImage )
364     {
365         rImage.drawGraphics( *m_pImage, 0, 0, xDest, yDest );
366     }
367 }
368
369
370 void CtrlList::autoScroll()
371 {
372     // Get the size of the control
373     const Position *pPos = getPosition();
374     if( !pPos )
375     {
376         return;
377     }
378     int height = pPos->getHeight();
379
380     // How many lines can be displayed ?
381     int itemHeight = m_rFont.getSize() + LINE_INTERVAL;
382     int maxItems = height / itemHeight;
383
384     // Find the current playing stream
385     int playIndex = 0;
386     VarList::ConstIterator it;
387     for( it = m_rList.begin(); it != m_rList.end(); it++ )
388     {
389         if( (*it).m_playing )
390         {
391             break;
392         }
393         playIndex++;
394     }
395     if( it != m_rList.end() &&
396         ( playIndex < m_lastPos || playIndex >= m_lastPos + maxItems ) )
397     {
398         // Scroll the list to have the playing stream visible
399         VarPercent &rVarPos = m_rList.getPositionVar();
400         rVarPos.set( 1.0 - (float)playIndex / (float)m_rList.size() );
401         // The image will be changed by onUpdate(VarPercent&)
402     }
403     else
404     {
405         makeImage();
406         notifyLayout();
407     }
408 }
409
410
411 void CtrlList::makeImage()
412 {
413     if( m_pImage )
414     {
415         delete m_pImage;
416     }
417
418     // Get the size of the control
419     const Position *pPos = getPosition();
420     if( !pPos )
421     {
422         return;
423     }
424     int width = pPos->getWidth();
425     int height = pPos->getHeight();
426     int itemHeight = m_rFont.getSize() + LINE_INTERVAL;
427
428     // Create an image
429     OSFactory *pOsFactory = OSFactory::instance( getIntf() );
430     m_pImage = pOsFactory->createOSGraphics( width, height );
431
432     VarList::ConstIterator it = m_rList[m_lastPos];
433
434     // Draw the background
435     if( m_pBitmap )
436     {
437         // A background bitmap is given, so we scale it, ignoring the
438         // background colors
439         ScaledBitmap bmp( getIntf(), *m_pBitmap, width, height );
440         m_pImage->drawBitmap( bmp, 0, 0 );
441
442         // Take care of the selection color
443         for( int yPos = 0; yPos < height; yPos += itemHeight )
444         {
445             int rectHeight = __MIN( itemHeight, height - yPos );
446             if( it != m_rList.end() )
447             {
448                 if( (*it).m_selected )
449                 {
450                     m_pImage->fillRect( 0, yPos, width, rectHeight,
451                                         m_selColor );
452                 }
453                 it++;
454             }
455         }
456     }
457     else
458     {
459         // No background bitmap, so use the 2 background colors
460         // Current background color
461         uint32_t bgColor = m_bgColor1;
462         for( int yPos = 0; yPos < height; yPos += itemHeight )
463         {
464             int rectHeight = __MIN( itemHeight, height - yPos );
465             if( it != m_rList.end() )
466             {
467                 uint32_t color = ( (*it).m_selected ? m_selColor : bgColor );
468                 m_pImage->fillRect( 0, yPos, width, rectHeight, color );
469                 it++;
470             }
471             else
472             {
473                 m_pImage->fillRect( 0, yPos, width, rectHeight, bgColor );
474             }
475             // Flip the background color
476             bgColor = ( bgColor == m_bgColor1 ? m_bgColor2 : m_bgColor1 );
477         }
478     }
479
480     // Draw the items
481     int yPos = 0;
482     for( it = m_rList[m_lastPos]; it != m_rList.end() && yPos < height; it++ )
483     {
484         UString *pStr = (UString*)((*it).m_cString.get());
485         uint32_t color = ( (*it).m_playing ? m_playColor : m_fgColor );
486
487         // Draw the text
488         GenericBitmap *pText = m_rFont.drawString( *pStr, color, width );
489         if( !pText )
490         {
491             return;
492         }
493         yPos += 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, 0, yPos, pText->getWidth(),
502                               lineHeight, true );
503         yPos += (pText->getHeight() - ySrc );
504         delete pText;
505
506     }
507 }
508