1 /***************************************************************************
2 scrollinglist.cpp - Scrollable list widget
3 -------------------
4 begin : Thu Aug 28 2003
5 copyright : (C) 2003 by Gabor Torok
6 email : cctorok@yahoo.com
7 ***************************************************************************/
8
9 /***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17 #include "../common/constants.h"
18 #include "scrollinglist.h"
19 #include "../util.h"
20 #include "../render/texture.h"
21
22 using namespace std;
23
24 #define LIST_TEXT_Y_OFFSET 4
25
26 /**
27 *@author Gabor Torok
28 */
ScrollingList(int x,int y,int w,int h,Texture highlight,DragAndDropHandler * dragAndDropHandler,int lineHeight)29 ScrollingList::ScrollingList( int x, int y, int w, int h, Texture highlight, DragAndDropHandler *dragAndDropHandler, int lineHeight )
30 : Widget( x, y, w, h ) {
31 value = 0;
32 scrollerWidth = 15;
33 listHeight = 0;
34 alpha = 0.5f;
35 alphaInc = 0.05f;
36 lastTick = 0;
37 inside = false;
38 scrollerY = 0;
39 this->dragging = false;
40 this->dragX = this->dragY = 0;
41 selectedLine = NULL;
42 selectedLineCount = 0;
43 scrollerHeight = h;
44 this->dragAndDropHandler = dragAndDropHandler;
45 this->lineHeight = lineHeight;
46 this->innerDrag = false;
47 this->colors = NULL;
48 this->icons = NULL;
49 this->highlight = highlight;
50 this->linewrap = false;
51 this->iconBorder = false;
52 highlightBorders = false;
53 debug = false;
54 canGetFocusVar = Widget::canGetFocus();
55 allowMultipleSelection = false;
56 tooltipLine = -1;
57 }
58
~ScrollingList()59 ScrollingList::~ScrollingList() {
60 delete [] selectedLine;
61 }
62
63
setLines(int count,string const s[],const Color * colors,Texture * icons)64 void ScrollingList::setLines( int count, string const s[], const Color *colors, Texture* icons ) {
65 textWidthCache.clear();
66 list.clear();
67 for ( int i = 0; i < count; i++ )
68 list.push_back( s[i] );
69 this->colors = colors;
70 this->icons = icons;
71 setupHeight();
72 }
73
setLines(const vector<string>::iterator begin,const vector<string>::iterator end,const Color * colors,Texture * icons)74 void ScrollingList::setLines( const vector<string>::iterator begin, const vector<string>::iterator end, const Color *colors, Texture* icons ) {
75 textWidthCache.clear();
76 list.clear();
77 list.insert( list.end(), begin, end );
78 this->colors = colors;
79 this->icons = icons;
80 setupHeight();
81 }
82
setLine(const string & toPush)83 void ScrollingList::setLine( const string& toPush ) {
84 list.push_back( toPush );
85 setupHeight();
86 }
87
setLine(size_t pos,const string & toPush)88 void ScrollingList::setLine( size_t pos, const string& toPush ) {
89 if ( pos >= 0 && pos < list.size() ) {
90 list[pos] = toPush;
91 setupHeight();
92 }
93 }
94
setupHeight()95 void ScrollingList::setupHeight() {
96 listHeight = list.size() * lineHeight + 5;
97 scrollerHeight = ( listHeight <= getHeight() ? getHeight() : ( getHeight() * getHeight() ) / listHeight );
98 // set a min. height for scrollerHeight
99 if ( scrollerHeight < 20 ) scrollerHeight = 20;
100 // reset the scroller
101 value = scrollerY = 0;
102 selectedLineCount = 0;
103 delete [] selectedLine;
104 selectedLine = NULL;
105
106 if ( !list.empty() ) {
107 selectedLine = new int[ list.size() ];
108 if ( !allowMultipleSelection ) {
109 selectedLine[ 0 ] = 0;
110 selectedLineCount = 1;
111 }
112 }
113 }
114
drawWidget(Widget * parent)115 void ScrollingList::drawWidget( Widget *parent ) {
116 GuiTheme *theme = ( ( Window* )parent )->getTheme();
117
118 // draw the text
119 if ( debug ) {
120 cerr << "**********************************************" << endl;
121 cerr << "SCROLLING LIST: count=" << list.size() << endl;
122 for ( vector<string>::iterator i = list.begin(); i != list.end(); i++ ) {
123 cerr << "i=" << distance( list.begin(), i ) << " " << *i << endl;
124 }
125 cerr << "**********************************************" << endl;
126 }
127 int textPos = -static_cast<int>( ( ( listHeight - getHeight() ) / 100.0f ) * static_cast<float>( value ) );
128 if ( !( ( Window* )parent )->isOpening() ) {
129 glScissor( ( ( Window* )parent )->getX() + x,
130 ( ( Window* )parent )->getScourgeGui()->getScreenHeight() -
131 ( ( ( Window* )parent )->getY() + ( ( Window* )parent )->getGutter() + y + getHeight() ),
132 w, getHeight() );
133 glEnable( GL_SCISSOR_TEST );
134
135 // highlight the selected line
136 //if(selectedLine > -1) {
137 if ( selectedLine != NULL ) {
138 if ( theme->getSelectionBackground() ) {
139 if ( theme->getSelectionBackground()->color.a < 1 ) {
140 glEnable( GL_BLEND );
141 glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
142 }
143 glColor4f( theme->getSelectionBackground()->color.r,
144 theme->getSelectionBackground()->color.g,
145 theme->getSelectionBackground()->color.b,
146 theme->getSelectionBackground()->color.a );
147 } else {
148 applySelectionColor();
149 }
150 for ( int i = 0; i < selectedLineCount; i++ ) {
151 glBegin( GL_TRIANGLE_STRIP );
152 glVertex2d( w, textPos + ( selectedLine[i] * lineHeight ) + 5 );
153 glVertex2d( scrollerWidth, textPos + ( selectedLine[i] * lineHeight ) + 5 );
154 glVertex2d( w, textPos + ( ( selectedLine[i] + 1 ) * lineHeight + 5 ) );
155 glVertex2d( scrollerWidth, textPos + ( ( selectedLine[i] + 1 ) * lineHeight + 5 ) );
156 glEnd();
157 }
158 glDisable( GL_BLEND );
159 }
160
161 // draw the contents
162 if ( !colors ) {
163
164 if ( theme->getWindowText() ) {
165 glColor4f( theme->getWindowText()->r,
166 theme->getWindowText()->g,
167 theme->getWindowText()->b,
168 theme->getWindowText()->a );
169 } else {
170 applyColor();
171 }
172 }
173 int ypos;
174 for ( size_t i = 0; i < list.size(); i++ ) {
175 ypos = textPos + ( i + 1 ) * lineHeight;
176 // writing text is expensive, only print what's visible
177 if ( ypos >= 0 && ypos < getHeight() + lineHeight ) {
178 if ( icons ) drawIcon( scrollerWidth + 5, ypos - ( lineHeight - 5 ), icons[i], parent );
179 if ( colors ) glColor4f( ( colors + i )->r, ( colors + i )->g, ( colors + i )->b, 1 );
180 else if ( isSelected( i ) && theme->getSelectionText() ) {
181 glColor4f( theme->getSelectionText()->r,
182 theme->getSelectionText()->g,
183 theme->getSelectionText()->b,
184 theme->getSelectionText()->a );
185 } else {
186 if ( theme->getWindowText() ) {
187 glColor4f( theme->getWindowText()->r,
188 theme->getWindowText()->g,
189 theme->getWindowText()->b,
190 theme->getWindowText()->a );
191 } else {
192 applyColor();
193 }
194 }
195
196 int startYPos = ypos - ( lineHeight > 15 ? ( lineHeight - 15 ) : 0 );
197 int startXPos = scrollerWidth + ( icons ? ( lineHeight + 5 ) : 5 );
198 printLine( parent, startXPos, startYPos, list[i] );
199 }
200 }
201
202 if ( selectedLine != NULL ) {
203 if ( theme->getButtonBorder() ) {
204 glColor4f( theme->getButtonBorder()->color.r,
205 theme->getButtonBorder()->color.g,
206 theme->getButtonBorder()->color.b,
207 theme->getButtonBorder()->color.a );
208 } else {
209 applyBorderColor();
210 }
211 for ( int i = 0; i < selectedLineCount; i++ ) {
212 glBegin( GL_LINES );
213 glVertex2d( scrollerWidth, textPos + ( selectedLine[i] * lineHeight ) + 5 );
214 glVertex2d( w, textPos + ( selectedLine[i] * lineHeight ) + 5 );
215 glVertex2d( scrollerWidth, textPos + ( ( selectedLine[i] + 1 ) * lineHeight + 5 ) );
216 glVertex2d( w, textPos + ( ( selectedLine[i] + 1 ) * lineHeight + 5 ) );
217 glEnd();
218 }
219 }
220
221 glDisable( GL_SCISSOR_TEST );
222 }
223
224 glDisable( GL_TEXTURE_2D );
225 glEnable( GL_BLEND );
226 glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
227 glColor4f( 0, 0, 0, 0.4f );
228 glBegin( GL_TRIANGLE_STRIP );
229 glVertex2d( 0, 0 );
230 glVertex2d( scrollerWidth, 0 );
231 glVertex2d( 0, h );
232 glVertex2d( scrollerWidth, h );
233 glEnd();
234 glDisable( GL_BLEND );
235 glEnable( GL_TEXTURE_2D );
236
237 drawButton( parent, 0, scrollerY, scrollerWidth, scrollerY + scrollerHeight,
238 false, false, false, false, inside );
239
240 // draw the outline
241 glDisable( GL_TEXTURE_2D );
242 if ( highlightBorders ) {
243 glLineWidth( 3.0f );
244 }
245 if ( theme->getButtonBorder() ) {
246 glColor4f( theme->getButtonBorder()->color.r,
247 theme->getButtonBorder()->color.g,
248 theme->getButtonBorder()->color.b,
249 theme->getButtonBorder()->color.a );
250 } else {
251 applyBorderColor();
252 }
253
254 glBegin( GL_LINES );
255 glVertex2d( 0, 0 );
256 glVertex2d( 0, h );
257 glVertex2d( w, 0 );
258 glVertex2d( w, h );
259 glVertex2d( 0, 0 );
260 glVertex2d( w, 0 );
261 glVertex2d( 0, h );
262 glVertex2d( w, h );
263 glVertex2d( scrollerWidth, 0 );
264 glVertex2d( scrollerWidth, h );
265 glVertex2d( 0, scrollerY );
266 glVertex2d( scrollerWidth, scrollerY );
267 glVertex2d( 0, scrollerY + scrollerHeight );
268 glVertex2d( scrollerWidth, scrollerY + scrollerHeight );
269 glEnd();
270 glLineWidth( 1.0f );
271 }
272
printLine(Widget * parent,int x,int y,const std::string & s)273 void ScrollingList::printLine( Widget *parent, int x, int y, const std::string& s ) {
274 if ( !linewrap ) {
275 ( ( Window* )parent )->getScourgeGui()->texPrint( x, y + LIST_TEXT_Y_OFFSET, s.c_str() );
276 return;
277 }
278
279 int currentX = x, currentY = y;
280 vector<string> parts = Util::Tokenize<vector<string> >( s, " |" );
281 int space = getTextWidth( parent, " " );
282 GuiTheme *theme = ( ( Window* )parent )->getTheme();
283
284 for ( vector<string>::iterator i = parts.begin(); i != parts.end(); i++ ) {
285 int wordWidth = getTextWidth( parent, i->c_str() );
286 if ( currentX + wordWidth > getWidth() ) {
287 currentY += 15;
288 currentX = x;
289 }
290
291 if ( theme->getWindowText() ) {
292 glColor4f( theme->getWindowText()->r,
293 theme->getWindowText()->g,
294 theme->getWindowText()->b,
295 theme->getWindowText()->a );
296 } else {
297 applyColor();
298 }
299 ( ( Window* )parent )->getScourgeGui()->texPrint( currentX, currentY + LIST_TEXT_Y_OFFSET, i->c_str() );
300
301 currentX += space + wordWidth;
302 }
303 }
304
drawIcon(int x,int y,Texture icon,Widget * parent)305 void ScrollingList::drawIcon( int x, int y, Texture icon, Widget *parent ) {
306 float n = lineHeight - 3;
307
308 glEnable( GL_BLEND );
309 glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
310 glEnable( GL_TEXTURE_2D );
311
312 glPushMatrix();
313 glTranslatef( x, y, 0 );
314 if ( icon.isSpecified() ) icon.glBind();
315 glColor4f( 1, 1, 1, 1 );
316
317
318 glBegin( GL_TRIANGLE_STRIP );
319 if ( icon.isSpecified() ) glTexCoord2f( 0, 0 );
320 glVertex3f( 0, 0, 0 );
321 if ( icon.isSpecified() ) glTexCoord2f( 1, 0 );
322 glVertex3f( n, 0, 0 );
323 if ( icon.isSpecified() ) glTexCoord2f( 0, 1 );
324 glVertex3f( 0, n, 0 );
325 if ( icon.isSpecified() ) glTexCoord2f( 1, 1 );
326 glVertex3f( n, n, 0 );
327 glEnd();
328
329 glDisable( GL_TEXTURE_2D );
330
331 if ( iconBorder ) {
332 GuiTheme *theme = ( ( Window* )parent )->getTheme();
333 if ( theme->getButtonBorder() ) {
334 glColor4f( theme->getButtonBorder()->color.r,
335 theme->getButtonBorder()->color.g,
336 theme->getButtonBorder()->color.b,
337 theme->getButtonBorder()->color.a );
338 } else {
339 applyBorderColor();
340 }
341 glBegin( GL_LINE_LOOP );
342 glVertex2f( 0, 0 );
343 glVertex2f( 0, n );
344 glVertex2f( n, n );
345 glVertex2f( n, 0 );
346 glEnd();
347 }
348 glPopMatrix();
349
350 glDisable( GL_BLEND );
351 }
352
getLineAtPoint(int x,int y)353 int ScrollingList::getLineAtPoint( int x, int y ) {
354 int textPos = -static_cast<int>( ( ( listHeight - getHeight() ) / 100.0f ) * static_cast<float>( value ) );
355 int n = static_cast<int>( static_cast<float>( y - ( getY() + textPos ) ) / static_cast<float>( lineHeight ) );
356 if ( !list.empty() && n >= 0 && n < static_cast<int>( list.size() ) ) {
357 return n;
358 } else {
359 return -1;
360 }
361 }
362
selectLine(int x,int y,bool addToSelection,bool mouseDown)363 void ScrollingList::selectLine( int x, int y, bool addToSelection, bool mouseDown ) {
364 int n = getLineAtPoint( x, y );
365 if ( n > -1 ) {
366 if ( addToSelection && allowMultipleSelection && selectedLineCount > 0 ) {
367 // is it already selected?
368 for ( int i = 0; i < selectedLineCount; i++ ) {
369 if ( selectedLine[ i ] == n ) {
370
371 if ( mouseDown ) {
372 for ( int t = i; t < selectedLineCount - 1; t++ ) {
373 selectedLine[ t ] = selectedLine[ t + 1 ];
374 }
375 selectedLineCount--;
376 }
377
378 return;
379 }
380 }
381 // add to selection
382 selectedLine[ selectedLineCount++ ] = n;
383 } else {
384 // set as selection
385 selectedLineCount = 0;
386 selectedLine[ selectedLineCount++ ] = n;
387 }
388 }
389 }
390
handleEvent(Widget * parent,SDL_Event * event,int x,int y)391 bool ScrollingList::handleEvent( Widget *parent, SDL_Event *event, int x, int y ) {
392 eventType = EVENT_ACTION;
393 inside = ( x >= getX() && x < getX() + scrollerWidth &&
394 y >= getY() + scrollerY && y < getY() + scrollerY + scrollerHeight );
395 switch ( event->type ) {
396 case SDL_KEYDOWN:
397 if ( hasFocus() ) {
398 if ( event->key.keysym.sym == SDLK_UP ||
399 event->key.keysym.sym == SDLK_DOWN ) {
400 return true;
401 }
402 }
403 break;
404 case SDL_KEYUP:
405 if ( hasFocus() ) {
406 if ( event->key.keysym.sym == SDLK_UP ) {
407 moveSelectionUp();
408 return true;
409 } else if ( event->key.keysym.sym == SDLK_DOWN ) {
410 moveSelectionDown();
411 return true;
412 }
413 }
414 break;
415 case SDL_MOUSEMOTION:
416 if ( innerDrag &&
417 ( abs( innerDragX - x ) > DragAndDropHandler::DRAG_START_DISTANCE ||
418 abs( innerDragY - y ) > DragAndDropHandler::DRAG_START_DISTANCE ) &&
419 dragAndDropHandler ) {
420 innerDrag = false;
421 dragAndDropHandler->startDrag( this );
422 }
423 highlightBorders = ( isInside( x, y ) && dragAndDropHandler );
424
425 tooltipLine = getLineAtPoint( x, y );
426 if ( tooltipLine > -1 &&
427 !linewrap &&
428 ( ( Window* )parent )->getScourgeGui()->textWidth( list[ tooltipLine ].c_str() ) > getWidth() - scrollerWidth ) {
429 setTooltip( list[tooltipLine].c_str() );
430 } else {
431 setTooltip( "" );
432 }
433 break;
434 case SDL_MOUSEBUTTONUP:
435 if ( !dragging && isInside( x, y ) ) {
436 if ( dragAndDropHandler ) dragAndDropHandler->receive( this );
437 }
438 eventType = ( dragging ? EVENT_DRAG : EVENT_ACTION );
439 innerDrag = false;
440 dragging = false;
441 //((Window*)parent)->getScourgeGui()->unlockMouse();
442 return isInside( x, y );
443 case SDL_MOUSEBUTTONDOWN:
444 if ( event->button.button ) {
445 if ( event->button.button == SDL_BUTTON_WHEELUP ) {
446 eventType = EVENT_DRAG;
447 if ( isInside( x, y ) ) {
448 if ( listHeight > getHeight() ) {
449 int scrollDelta = static_cast<int>( 100.0f / static_cast<float>( list.size() ) );
450 if ( scrollDelta == 0 ) scrollDelta = 1;
451 value -= scrollDelta;
452 if ( value < 0 ) value = 0;
453 scrollerY = static_cast<int>( ( static_cast<float>( getHeight() - scrollerHeight ) / 100.0f ) * static_cast<float>( value ) );
454 }
455 return true;
456 }
457 }
458 if ( event->button.button == SDL_BUTTON_WHEELDOWN ) {
459 eventType = EVENT_DRAG;
460 if ( isInside( x, y ) ) {
461 if ( listHeight > getHeight() ) {
462 int scrollDelta = static_cast<int>( 100.0f / static_cast<float>( list.size() ) );
463 if ( scrollDelta == 0 ) scrollDelta = 1;
464 value += scrollDelta;
465 if ( value > 100 ) value = 100;
466 scrollerY = static_cast<int>( ( static_cast<float>( getHeight() - scrollerHeight ) / 100.0f ) * static_cast<float>( value ) );
467 }
468 return true;
469 }
470 } else if ( event->button.button == SDL_BUTTON_LEFT ) {
471 if ( scrollerHeight < getHeight() && x - getX() < scrollerWidth ) {
472 innerDrag = false;
473 dragging = inside;
474 dragX = x - getX();
475 dragY = y - ( scrollerY + getY() );
476 //((Window*)parent)->getScourgeGui()->lockMouse( this );
477
478 if ( ( y - getY() ) < scrollerY ) { // we clicked above the scroller
479 moveSelectionUp();
480 } else if ( ( y - getY() ) > ( scrollerY + scrollerHeight ) ) { // we clicked below the scroller
481 moveSelectionDown();
482 }
483 } else if ( isInside( x, y ) ) {
484 dragging = false;
485 selectLine( x, y,
486 ( ( SDL_GetModState() & KMOD_SHIFT ) ||
487 ( SDL_GetModState() & KMOD_CTRL ) ),
488 true );
489 innerDrag = ( selectedLine != NULL );
490 innerDragX = x;
491 innerDragY = y;
492 }
493 }
494 }
495 break;
496 }
497 if ( dragging ) {
498 value = static_cast<int>( static_cast<float>( ( y - dragY ) - getY() ) / ( static_cast<float>( getHeight() - scrollerHeight ) / 100.0f ) );
499 if ( value < 0 ) value = 0;
500 if ( value > 100 ) value = 100;
501 scrollerY = static_cast<int>( ( static_cast<float>( getHeight() - scrollerHeight ) / 100.0f ) * static_cast<float>( value ) );
502 }
503 return false;
504 }
505
removeEffects(Widget * parent)506 void ScrollingList::removeEffects( Widget *parent ) {
507 highlightBorders = false;
508 inside = false;
509 }
510
unselectAllLines()511 void ScrollingList::unselectAllLines() {
512 if ( allowMultipleSelection ) {
513 selectedLineCount = 0;
514 } else {
515 setSelectedLine( 0 );
516 }
517 }
518
setSelectedLine(size_t line)519 void ScrollingList::setSelectedLine( size_t line ) {
520
521 if ( selectedLine == NULL ) return;
522
523 selectedLine[ 0 ] = ( line < list.size() ? line : list.size() - 1 );
524 selectedLineCount = 1;
525
526 // fixme: should check if line is already visible
527 if ( listHeight > getHeight() ) {
528 value = static_cast<int>( ( static_cast<float>( selectedLine[0] + 1 ) / static_cast<float>( list.size() ) ) * 100.0f );
529 if ( value < 0 ) value = 0;
530 if ( value > 100 ) value = 100;
531 scrollerY = static_cast<int>( ( static_cast<float>( getHeight() - scrollerHeight ) / 100.0f ) * static_cast<float>( value ) );
532 // on 0, align to the top of the control
533 if ( selectedLine[ 0 ] == 0 ) {
534 scrollerY = value = 0;
535 }
536 }
537 }
538
moveSelectionUp()539 void ScrollingList::moveSelectionUp() {
540 if ( selectedLine == NULL )
541 setSelectedLine( 0 );
542 else if ( selectedLine[0] > 0 )
543 setSelectedLine( selectedLine[0] - 1 );
544 }
545
moveSelectionDown()546 void ScrollingList::moveSelectionDown() {
547 if ( selectedLine == NULL )
548 setSelectedLine( 0 );
549 else if ( selectedLine[0] < static_cast<int>( list.size() ) - 1 )
550 setSelectedLine( selectedLine[0] + 1 );
551 }
552