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