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