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