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