]> git.sesse.net Git - vlc/blob - modules/gui/skins2/controls/ctrl_list.cpp
Use Brackets for global headers.
[vlc] / modules / gui / skins2 / controls / ctrl_list.cpp
1 /*****************************************************************************
2  * ctrl_list.cpp
3  *****************************************************************************
4  * Copyright (C) 2003 the VideoLAN team
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., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, 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.05f
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     delete m_pImage;
69 }
70
71
72 void CtrlList::onUpdate( Subject<VarList> &rList, void *arg  )
73 {
74     autoScroll();
75     m_pLastSelected = NULL;
76 }
77
78
79 void CtrlList::onUpdate( Subject<VarPercent> &rPercent, void *arg  )
80 {
81     // Get the size of the control
82     const Position *pPos = getPosition();
83     if( !pPos )
84     {
85         return;
86     }
87     int height = pPos->getHeight();
88
89     // How many lines can be displayed ?
90     int itemHeight = m_rFont.getSize() + LINE_INTERVAL;
91     int maxItems = height / itemHeight;
92
93     // Determine what is the first item to display
94     VarPercent &rVarPos = m_rList.getPositionVar();
95     int firstItem = 0;
96     int excessItems = m_rList.size() - maxItems;
97     if( excessItems > 0 )
98     {
99         // a simple (int)(...) causes rounding errors !
100 #ifdef _MSC_VER
101 #   define lrint (int)
102 #endif
103         firstItem = lrint( (1.0 - rVarPos.get()) * (double)excessItems );
104     }
105     if( m_lastPos != firstItem )
106     {
107         // Redraw the control if the position has changed
108         m_lastPos = firstItem;
109         makeImage();
110         notifyLayout();
111     }
112 }
113
114
115 void CtrlList::onResize()
116 {
117     // Get the size of the control
118     const Position *pPos = getPosition();
119     if( !pPos )
120     {
121         return;
122     }
123     int height = pPos->getHeight();
124
125     // How many lines can be displayed ?
126     int itemHeight = m_rFont.getSize() + LINE_INTERVAL;
127     int maxItems = height / itemHeight;
128
129     // Update the position variable
130     VarPercent &rVarPos = m_rList.getPositionVar();
131     int excessItems = m_rList.size() - maxItems;
132     if( excessItems > 0 )
133     {
134         double newVal = 1.0 - (double)m_lastPos / excessItems;
135         if( newVal >= 0 )
136         {
137             // Change the position to keep the same first displayed item
138             rVarPos.set( 1.0 - (double)m_lastPos / excessItems );
139         }
140         else
141         {
142             // We cannot keep the current first item
143             m_lastPos = excessItems;
144         }
145     }
146
147     makeImage();
148     notifyLayout();
149 }
150
151
152 void CtrlList::onPositionChange()
153 {
154     makeImage();
155     notifyLayout();
156 }
157
158
159 void CtrlList::handleEvent( EvtGeneric &rEvent )
160 {
161     if( rEvent.getAsString().find( "key:down" ) != string::npos )
162     {
163         int key = ((EvtKey&)rEvent).getKey();
164         VarList::Iterator it = m_rList.begin();
165         bool previousWasSelected = false;
166         while( it != m_rList.end() )
167         {
168             VarList::Iterator next = it;
169             ++next;
170             if( key == KEY_UP )
171             {
172                 // Scroll up one item
173                 if( it != m_rList.begin() || &*it != m_pLastSelected )
174                 {
175                     bool nextWasSelected = ( &*next == m_pLastSelected );
176                     (*it).m_selected = nextWasSelected;
177                     if( nextWasSelected )
178                     {
179                         m_pLastSelected = &*it;
180                     }
181                 }
182             }
183             else if( key == KEY_DOWN )
184             {
185                 // Scroll down one item
186                 if( next != m_rList.end() || &*it != m_pLastSelected )
187                 {
188                     (*it).m_selected = previousWasSelected;
189                 }
190                 if( previousWasSelected )
191                 {
192                     m_pLastSelected = &*it;
193                     previousWasSelected = false;
194                 }
195                 else
196                 {
197                     previousWasSelected = ( &*it == m_pLastSelected );
198                 }
199             }
200             it = next;
201         }
202
203         // Redraw the control
204         makeImage();
205         notifyLayout();
206     }
207
208     else if( rEvent.getAsString().find( "mouse:left" ) != string::npos )
209     {
210         EvtMouse &rEvtMouse = (EvtMouse&)rEvent;
211         const Position *pos = getPosition();
212         int yPos = m_lastPos + ( rEvtMouse.getYPos() - pos->getTop() ) /
213             (m_rFont.getSize() + LINE_INTERVAL);
214         VarList::Iterator it;
215         int index = 0;
216
217         if( rEvent.getAsString().find( "mouse:left:down:ctrl,shift" ) !=
218                  string::npos )
219         {
220             // Flag to know if the current item must be selected
221             bool select = false;
222             for( it = m_rList.begin(); it != m_rList.end(); it++ )
223             {
224                 bool nextSelect = select;
225                 if( index == yPos || &*it == m_pLastSelected )
226                 {
227                     if( select )
228                     {
229                         nextSelect = false;
230                     }
231                     else
232                     {
233                         select = true;
234                         nextSelect = true;
235                     }
236                 }
237                 (*it).m_selected = (*it).m_selected || select;
238                 select = nextSelect;
239                 index++;
240             }
241         }
242
243         else if( rEvent.getAsString().find( "mouse:left:down:ctrl" ) !=
244                  string::npos )
245         {
246             for( it = m_rList.begin(); it != m_rList.end(); it++ )
247             {
248                 if( index == yPos )
249                 {
250                     (*it).m_selected = ! (*it).m_selected;
251                     m_pLastSelected = &*it;
252                     break;
253                 }
254                 index++;
255             }
256         }
257
258         else if( rEvent.getAsString().find( "mouse:left:down:shift" ) !=
259                  string::npos )
260         {
261             // Flag to know if the current item must be selected
262             bool select = false;
263             for( it = m_rList.begin(); it != m_rList.end(); it++ )
264             {
265                 bool nextSelect = select;
266                 if( index == yPos ||  &*it == m_pLastSelected )
267                 {
268                     if( select )
269                     {
270                         nextSelect = false;
271                     }
272                     else
273                     {
274                         select = true;
275                         nextSelect = true;
276                     }
277                 }
278                 (*it).m_selected = select;
279                 select = nextSelect;
280                 index++;
281             }
282         }
283
284         else if( rEvent.getAsString().find( "mouse:left:down" ) !=
285                  string::npos )
286         {
287             for( it = m_rList.begin(); it != m_rList.end(); it++ )
288             {
289                 if( index == yPos )
290                 {
291                     (*it).m_selected = true;
292                     m_pLastSelected = &*it;
293                 }
294                 else
295                 {
296                     (*it).m_selected = false;
297                 }
298                 index++;
299             }
300         }
301
302         else if( rEvent.getAsString().find( "mouse:left:dblclick" ) !=
303                  string::npos )
304         {
305             for( it = m_rList.begin(); it != m_rList.end(); it++ )
306             {
307                 if( index == yPos )
308                 {
309                     (*it).m_selected = true;
310                     m_pLastSelected = &*it;
311                     // Execute the action associated to this item
312                     m_rList.action( &*it );
313                 }
314                 else
315                 {
316                     (*it).m_selected = false;
317                 }
318                 index++;
319             }
320         }
321
322         // Redraw the control
323         makeImage();
324         notifyLayout();
325     }
326
327     else if( rEvent.getAsString().find( "scroll" ) != string::npos )
328     {
329         int direction = ((EvtScroll&)rEvent).getDirection();
330
331         double percentage = m_rList.getPositionVar().get();
332         double step = 2.0 / (double)m_rList.size();
333         if( direction == EvtScroll::kUp )
334         {
335             percentage += step;
336         }
337         else
338         {
339             percentage -= step;
340         }
341         m_rList.getPositionVar().set( percentage );
342     }
343 }
344
345
346 bool CtrlList::mouseOver( int x, int y ) const
347 {
348     const Position *pPos = getPosition();
349     if( pPos )
350     {
351         int width = pPos->getWidth();
352         int height = pPos->getHeight();
353         return ( x >= 0 && x <= width && y >= 0 && y <= height );
354     }
355     return false;
356 }
357
358
359 void CtrlList::draw( OSGraphics &rImage, int xDest, int yDest )
360 {
361     if( m_pImage )
362     {
363         rImage.drawGraphics( *m_pImage, 0, 0, xDest, yDest );
364     }
365 }
366
367
368 void CtrlList::autoScroll()
369 {
370     // Get the size of the control
371     const Position *pPos = getPosition();
372     if( !pPos )
373     {
374         return;
375     }
376     int height = pPos->getHeight();
377
378     // How many lines can be displayed ?
379     int itemHeight = m_rFont.getSize() + LINE_INTERVAL;
380     int maxItems = height / itemHeight;
381
382     // Find the current playing stream
383     int playIndex = 0;
384     VarList::ConstIterator it;
385     for( it = m_rList.begin(); it != m_rList.end(); it++ )
386     {
387         if( (*it).m_playing )
388         {
389             break;
390         }
391         playIndex++;
392     }
393     if( it != m_rList.end() &&
394         ( playIndex < m_lastPos || playIndex >= m_lastPos + maxItems ) )
395     {
396         // Scroll the list to have the playing stream visible
397         VarPercent &rVarPos = m_rList.getPositionVar();
398         rVarPos.set( 1.0 - (float)playIndex / (float)m_rList.size() );
399         // The image will be changed by onUpdate(VarPercent&)
400     }
401     else
402     {
403         makeImage();
404         notifyLayout();
405     }
406 }
407
408
409 void CtrlList::makeImage()
410 {
411     delete m_pImage;
412
413     // Get the size of the control
414     const Position *pPos = getPosition();
415     if( !pPos )
416     {
417         return;
418     }
419     int width = pPos->getWidth();
420     int height = pPos->getHeight();
421     int itemHeight = m_rFont.getSize() + LINE_INTERVAL;
422
423     // Create an image
424     OSFactory *pOsFactory = OSFactory::instance( getIntf() );
425     m_pImage = pOsFactory->createOSGraphics( width, height );
426
427     VarList::ConstIterator it = m_rList[m_lastPos];
428
429     // Draw the background
430     if( m_pBitmap )
431     {
432         // A background bitmap is given, so we scale it, ignoring the
433         // background colors
434         ScaledBitmap bmp( getIntf(), *m_pBitmap, width, height );
435         m_pImage->drawBitmap( bmp, 0, 0 );
436
437         // Take care of the selection color
438         for( int yPos = 0; yPos < height; yPos += itemHeight )
439         {
440             int rectHeight = __MIN( itemHeight, height - yPos );
441             if( it != m_rList.end() )
442             {
443                 if( (*it).m_selected )
444                 {
445                     m_pImage->fillRect( 0, yPos, width, rectHeight,
446                                         m_selColor );
447                 }
448                 it++;
449             }
450         }
451     }
452     else
453     {
454         // No background bitmap, so use the 2 background colors
455         // Current background color
456         uint32_t bgColor = m_bgColor1;
457         for( int yPos = 0; yPos < height; yPos += itemHeight )
458         {
459             int rectHeight = __MIN( itemHeight, height - yPos );
460             if( it != m_rList.end() )
461             {
462                 uint32_t color = ( (*it).m_selected ? m_selColor : bgColor );
463                 m_pImage->fillRect( 0, yPos, width, rectHeight, color );
464                 it++;
465             }
466             else
467             {
468                 m_pImage->fillRect( 0, yPos, width, rectHeight, bgColor );
469             }
470             // Flip the background color
471             bgColor = ( bgColor == m_bgColor1 ? m_bgColor2 : m_bgColor1 );
472         }
473     }
474
475     // Draw the items
476     int yPos = 0;
477     for( it = m_rList[m_lastPos]; it != m_rList.end() && yPos < height; it++ )
478     {
479         UString *pStr = (UString*)(it->m_cString.get());
480         uint32_t color = ( it->m_playing ? m_playColor : m_fgColor );
481
482         // Draw the text
483         GenericBitmap *pText = m_rFont.drawString( *pStr, color, width );
484         if( !pText )
485         {
486             return;
487         }
488         yPos += itemHeight - pText->getHeight();
489         int ySrc = 0;
490         if( yPos < 0 )
491         {
492             ySrc = - yPos;
493             yPos = 0;
494         }
495         int lineHeight = __MIN( pText->getHeight() - ySrc, height - yPos );
496         m_pImage->drawBitmap( *pText, 0, ySrc, 0, yPos, pText->getWidth(),
497                               lineHeight, true );
498         yPos += (pText->getHeight() - ySrc );
499         delete pText;
500
501     }
502 }
503