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