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