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