]> git.sesse.net Git - vlc/blob - modules/gui/skins2/controls/ctrl_list.cpp
ef2e17515a1abbfd678631c92db563be7e4eba76
[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     if( m_pImage )
69     {
70         delete m_pImage;
71     }
72 }
73
74
75 void CtrlList::onUpdate( Subject<VarList, void*> &rList, void *arg  )
76 {
77     autoScroll();
78     m_pLastSelected = NULL;
79 }
80
81
82 void CtrlList::onUpdate( Subject<VarPercent, void*> &rPercent, void *arg  )
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         double step = 2.0 / (double)m_rList.size();
336         if( direction == EvtScroll::kUp )
337         {
338             percentage += step;
339         }
340         else
341         {
342             percentage -= step;
343         }
344         m_rList.getPositionVar().set( percentage );
345     }
346 }
347
348
349 bool CtrlList::mouseOver( int x, int y ) const
350 {
351     const Position *pPos = getPosition();
352     if( pPos )
353     {
354         int width = pPos->getWidth();
355         int height = pPos->getHeight();
356         return ( x >= 0 && x <= width && y >= 0 && y <= height );
357     }
358     return false;
359 }
360
361
362 void CtrlList::draw( OSGraphics &rImage, int xDest, int yDest )
363 {
364     if( m_pImage )
365     {
366         rImage.drawGraphics( *m_pImage, 0, 0, xDest, yDest );
367     }
368 }
369
370
371 void CtrlList::autoScroll()
372 {
373     // Get the size of the control
374     const Position *pPos = getPosition();
375     if( !pPos )
376     {
377         return;
378     }
379     int height = pPos->getHeight();
380
381     // How many lines can be displayed ?
382     int itemHeight = m_rFont.getSize() + LINE_INTERVAL;
383     int maxItems = height / itemHeight;
384
385     // Find the current playing stream
386     int playIndex = 0;
387     VarList::ConstIterator it;
388     for( it = m_rList.begin(); it != m_rList.end(); it++ )
389     {
390         if( (*it).m_playing )
391         {
392             break;
393         }
394         playIndex++;
395     }
396     if( it != m_rList.end() &&
397         ( playIndex < m_lastPos || playIndex >= m_lastPos + maxItems ) )
398     {
399         // Scroll the list to have the playing stream visible
400         VarPercent &rVarPos = m_rList.getPositionVar();
401         rVarPos.set( 1.0 - (float)playIndex / (float)m_rList.size() );
402         // The image will be changed by onUpdate(VarPercent&)
403     }
404     else
405     {
406         makeImage();
407         notifyLayout();
408     }
409 }
410
411
412 void CtrlList::makeImage()
413 {
414     if( m_pImage )
415     {
416         delete m_pImage;
417     }
418
419     // Get the size of the control
420     const Position *pPos = getPosition();
421     if( !pPos )
422     {
423         return;
424     }
425     int width = pPos->getWidth();
426     int height = pPos->getHeight();
427     int itemHeight = m_rFont.getSize() + LINE_INTERVAL;
428
429     // Create an image
430     OSFactory *pOsFactory = OSFactory::instance( getIntf() );
431     m_pImage = pOsFactory->createOSGraphics( width, height );
432
433     VarList::ConstIterator it = m_rList[m_lastPos];
434
435     // Draw the background
436     if( m_pBitmap )
437     {
438         // A background bitmap is given, so we scale it, ignoring the
439         // background colors
440         ScaledBitmap bmp( getIntf(), *m_pBitmap, width, height );
441         m_pImage->drawBitmap( bmp, 0, 0 );
442
443         // Take care of the selection color
444         for( int yPos = 0; yPos < height; yPos += itemHeight )
445         {
446             int rectHeight = __MIN( itemHeight, height - yPos );
447             if( it != m_rList.end() )
448             {
449                 if( (*it).m_selected )
450                 {
451                     m_pImage->fillRect( 0, yPos, width, rectHeight,
452                                         m_selColor );
453                 }
454                 it++;
455             }
456         }
457     }
458     else
459     {
460         // No background bitmap, so use the 2 background colors
461         // Current background color
462         uint32_t bgColor = m_bgColor1;
463         for( int yPos = 0; yPos < height; yPos += itemHeight )
464         {
465             int rectHeight = __MIN( itemHeight, height - yPos );
466             if( it != m_rList.end() )
467             {
468                 uint32_t color = ( (*it).m_selected ? m_selColor : bgColor );
469                 m_pImage->fillRect( 0, yPos, width, rectHeight, color );
470                 it++;
471             }
472             else
473             {
474                 m_pImage->fillRect( 0, yPos, width, rectHeight, bgColor );
475             }
476             // Flip the background color
477             bgColor = ( bgColor == m_bgColor1 ? m_bgColor2 : m_bgColor1 );
478         }
479     }
480
481     // Draw the items
482     int yPos = 0;
483     for( it = m_rList[m_lastPos]; it != m_rList.end() && yPos < height; it++ )
484     {
485         UString *pStr = (UString*)(it->m_cString.get());
486         uint32_t color = ( it->m_playing ? m_playColor : m_fgColor );
487
488         // Draw the text
489         GenericBitmap *pText = m_rFont.drawString( *pStr, color, width );
490         if( !pText )
491         {
492             return;
493         }
494         yPos += itemHeight - pText->getHeight();
495         int ySrc = 0;
496         if( yPos < 0 )
497         {
498             ySrc = - yPos;
499             yPos = 0;
500         }
501         int lineHeight = __MIN( pText->getHeight() - ySrc, height - yPos );
502         m_pImage->drawBitmap( *pText, 0, ySrc, 0, yPos, pText->getWidth(),
503                               lineHeight, true );
504         yPos += (pText->getHeight() - ySrc );
505         delete pText;
506
507     }
508 }
509