PageRenderTime 61ms CodeModel.GetById 15ms app.highlight 41ms RepoModel.GetById 1ms app.codeStats 0ms

/indra/llui/llmultislider.cpp

https://bitbucket.org/lindenlab/viewer-beta/
C++ | 621 lines | 431 code | 108 blank | 82 comment | 80 complexity | 7dabc0087a57ff7f6d22835ca12a35e8 MD5 | raw file
  1/** 
  2 * @file llmultisldr.cpp
  3 * @brief LLMultiSlider base class
  4 *
  5 * $LicenseInfo:firstyear=2007&license=viewerlgpl$
  6 * Second Life Viewer Source Code
  7 * Copyright (C) 2010, Linden Research, Inc.
  8 * 
  9 * This library is free software; you can redistribute it and/or
 10 * modify it under the terms of the GNU Lesser General Public
 11 * License as published by the Free Software Foundation;
 12 * version 2.1 of the License only.
 13 * 
 14 * This library is distributed in the hope that it will be useful,
 15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 17 * Lesser General Public License for more details.
 18 * 
 19 * You should have received a copy of the GNU Lesser General Public
 20 * License along with this library; if not, write to the Free Software
 21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 22 * 
 23 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 24 * $/LicenseInfo$
 25 */
 26
 27#include "linden_common.h"
 28
 29#include "llmultislider.h"
 30#include "llui.h"
 31
 32#include "llgl.h"
 33#include "llwindow.h"
 34#include "llfocusmgr.h"
 35#include "llkeyboard.h"			// for the MASK constants
 36#include "llcontrol.h"
 37#include "lluictrlfactory.h"
 38#include "lluiimage.h"
 39
 40#include <sstream>
 41
 42static LLDefaultChildRegistry::Register<LLMultiSlider> r("multi_slider_bar");
 43
 44const F32 FLOAT_THRESHOLD = 0.00001f;
 45
 46S32 LLMultiSlider::mNameCounter = 0;
 47
 48LLMultiSlider::SliderParams::SliderParams()
 49:	name("name"),
 50	value("value", 0.f)
 51{
 52}
 53
 54LLMultiSlider::Params::Params()
 55:	max_sliders("max_sliders", 1),
 56	allow_overlap("allow_overlap", false),
 57	draw_track("draw_track", true),
 58	use_triangle("use_triangle", false),
 59	track_color("track_color"),
 60	thumb_disabled_color("thumb_disabled_color"),
 61	thumb_outline_color("thumb_outline_color"),
 62	thumb_center_color("thumb_center_color"),
 63	thumb_center_selected_color("thumb_center_selected_color"),
 64	triangle_color("triangle_color"),
 65	mouse_down_callback("mouse_down_callback"),
 66	mouse_up_callback("mouse_up_callback"),
 67	thumb_width("thumb_width"),
 68	sliders("slider")
 69{}
 70
 71LLMultiSlider::LLMultiSlider(const LLMultiSlider::Params& p)
 72:	LLF32UICtrl(p),
 73	mMouseOffset( 0 ),
 74	mDragStartThumbRect( 0, getRect().getHeight(), p.thumb_width, 0 ),
 75	mMaxNumSliders(p.max_sliders),
 76	mAllowOverlap(p.allow_overlap),
 77	mDrawTrack(p.draw_track),
 78	mUseTriangle(p.use_triangle),
 79	mTrackColor(p.track_color()),
 80	mThumbOutlineColor(p.thumb_outline_color()),
 81	mThumbCenterColor(p.thumb_center_color()),
 82	mThumbCenterSelectedColor(p.thumb_center_selected_color()),
 83	mDisabledThumbColor(p.thumb_disabled_color()),
 84	mTriangleColor(p.triangle_color()),
 85	mThumbWidth(p.thumb_width),
 86	mMouseDownSignal(NULL),
 87	mMouseUpSignal(NULL)
 88{
 89	mValue.emptyMap();
 90	mCurSlider = LLStringUtil::null;
 91	
 92	if (p.mouse_down_callback.isProvided())
 93	{
 94		setMouseDownCallback(initCommitCallback(p.mouse_down_callback));
 95	}
 96	if (p.mouse_up_callback.isProvided())
 97	{
 98		setMouseUpCallback(initCommitCallback(p.mouse_up_callback));
 99	}
100
101	for (LLInitParam::ParamIterator<SliderParams>::const_iterator it = p.sliders.begin();
102		it != p.sliders.end();
103		++it)
104	{
105		if (it->name.isProvided())
106		{
107			addSlider(it->value, it->name);
108		}
109		else
110		{
111			addSlider(it->value);
112		}
113	}
114}
115
116LLMultiSlider::~LLMultiSlider()
117{
118	delete mMouseDownSignal;
119	delete mMouseUpSignal;
120}
121
122
123void LLMultiSlider::setSliderValue(const std::string& name, F32 value, BOOL from_event)
124{
125	// exit if not there
126	if(!mValue.has(name)) {
127		return;
128	}
129
130	value = llclamp( value, mMinValue, mMaxValue );
131
132	// Round to nearest increment (bias towards rounding down)
133	value -= mMinValue;
134	value += mIncrement/2.0001f;
135	value -= fmod(value, mIncrement);
136	F32 newValue = mMinValue + value;
137
138	// now, make sure no overlap
139	// if we want that
140	if(!mAllowOverlap) {
141		bool hit = false;
142
143		// look at the current spot
144		// and see if anything is there
145		LLSD::map_iterator mIt = mValue.beginMap();
146		for(;mIt != mValue.endMap(); mIt++) {
147			
148			F32 testVal = (F32)mIt->second.asReal() - newValue;
149			if(testVal > -FLOAT_THRESHOLD && testVal < FLOAT_THRESHOLD &&
150				mIt->first != name) {
151				hit = true;
152				break;
153			}
154		}
155
156		// if none found, stop
157		if(hit) {
158			return;
159		}
160	}
161	
162
163	// now set it in the map
164	mValue[name] = newValue;
165
166	// set the control if it's the current slider and not from an event
167	if (!from_event && name == mCurSlider)
168	{
169		setControlValue(mValue);
170	}
171	
172	F32 t = (newValue - mMinValue) / (mMaxValue - mMinValue);
173
174	S32 left_edge = mThumbWidth/2;
175	S32 right_edge = getRect().getWidth() - (mThumbWidth/2);
176
177	S32 x = left_edge + S32( t * (right_edge - left_edge) );
178	mThumbRects[name].mLeft = x - (mThumbWidth/2);
179	mThumbRects[name].mRight = x + (mThumbWidth/2);
180}
181
182void LLMultiSlider::setValue(const LLSD& value)
183{
184	// only do if it's a map
185	if(value.isMap()) {
186		
187		// add each value... the first in the map becomes the current
188		LLSD::map_const_iterator mIt = value.beginMap();
189		mCurSlider = mIt->first;
190
191		for(; mIt != value.endMap(); mIt++) {
192			setSliderValue(mIt->first, (F32)mIt->second.asReal(), TRUE);
193		}
194	}
195}
196
197F32 LLMultiSlider::getSliderValue(const std::string& name) const
198{
199	return (F32)mValue[name].asReal();
200}
201
202void LLMultiSlider::setCurSlider(const std::string& name)
203{
204	if(mValue.has(name)) {
205		mCurSlider = name;
206	}
207}
208
209const std::string& LLMultiSlider::addSlider()
210{
211	return addSlider(mInitialValue);
212}
213
214const std::string& LLMultiSlider::addSlider(F32 val)
215{
216	std::stringstream newName;
217	F32 initVal = val;
218
219	if(mValue.size() >= mMaxNumSliders) {
220		return LLStringUtil::null;
221	}
222
223	// create a new name
224	newName << "sldr" << mNameCounter;
225	mNameCounter++;
226
227	bool foundOne = findUnusedValue(initVal);
228	if(!foundOne) {
229		return LLStringUtil::null;
230	}
231
232	// add a new thumb rect
233	mThumbRects[newName.str()] = LLRect( 0, getRect().getHeight(), mThumbWidth, 0 );
234
235	// add the value and set the current slider to this one
236	mValue.insert(newName.str(), initVal);
237	mCurSlider = newName.str();
238
239	// move the slider
240	setSliderValue(mCurSlider, initVal, TRUE);
241
242	return mCurSlider;
243}
244
245void LLMultiSlider::addSlider(F32 val, const std::string& name)
246{
247	F32 initVal = val;
248
249	if(mValue.size() >= mMaxNumSliders) {
250		return;
251	}
252
253	bool foundOne = findUnusedValue(initVal);
254	if(!foundOne) {
255		return;
256	}
257
258	// add a new thumb rect
259	mThumbRects[name] = LLRect( 0, getRect().getHeight(), mThumbWidth, 0 );
260
261	// add the value and set the current slider to this one
262	mValue.insert(name, initVal);
263	mCurSlider = name;
264
265	// move the slider
266	setSliderValue(mCurSlider, initVal, TRUE);
267}
268
269bool LLMultiSlider::findUnusedValue(F32& initVal)
270{
271	bool firstTry = true;
272
273	// find the first open slot starting with
274	// the initial value
275	while(true) {
276		
277		bool hit = false;
278
279		// look at the current spot
280		// and see if anything is there
281		LLSD::map_iterator mIt = mValue.beginMap();
282		for(;mIt != mValue.endMap(); mIt++) {
283			
284			F32 testVal = (F32)mIt->second.asReal() - initVal;
285			if(testVal > -FLOAT_THRESHOLD && testVal < FLOAT_THRESHOLD) {
286				hit = true;
287				break;
288			}
289		}
290
291		// if we found one
292		if(!hit) {
293			break;
294		}
295
296		// increment and wrap if need be
297		initVal += mIncrement;
298		if(initVal > mMaxValue) {
299			initVal = mMinValue;
300		}
301
302		// stop if it's filled
303		if(initVal == mInitialValue && !firstTry) {
304			llwarns << "Whoa! Too many multi slider elements to add one to" << llendl;
305			return false;
306		}
307
308		firstTry = false;
309		continue;
310	}
311
312	return true;
313}
314
315
316void LLMultiSlider::deleteSlider(const std::string& name)
317{
318	// can't delete last slider
319	if(mValue.size() <= 0) {
320		return;
321	}
322
323	// get rid of value from mValue and its thumb rect
324	mValue.erase(name);
325	mThumbRects.erase(name);
326
327	// set to the last created
328	if(mValue.size() > 0) {
329		std::map<std::string, LLRect>::iterator mIt = mThumbRects.end();
330		mIt--;
331		mCurSlider = mIt->first;
332	}
333}
334
335void LLMultiSlider::clear()
336{
337	while(mThumbRects.size() > 0) {
338		deleteCurSlider();
339	}
340
341	LLF32UICtrl::clear();
342}
343
344BOOL LLMultiSlider::handleHover(S32 x, S32 y, MASK mask)
345{
346	if( gFocusMgr.getMouseCapture() == this )
347	{
348		S32 left_edge = mThumbWidth/2;
349		S32 right_edge = getRect().getWidth() - (mThumbWidth/2);
350
351		x += mMouseOffset;
352		x = llclamp( x, left_edge, right_edge );
353
354		F32 t = F32(x - left_edge) / (right_edge - left_edge);
355		setCurSliderValue(t * (mMaxValue - mMinValue) + mMinValue );
356		onCommit();
357
358		getWindow()->setCursor(UI_CURSOR_ARROW);
359		lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (active)" << llendl;		
360	}
361	else
362	{
363		getWindow()->setCursor(UI_CURSOR_ARROW);
364		lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (inactive)" << llendl;		
365	}
366	return TRUE;
367}
368
369BOOL LLMultiSlider::handleMouseUp(S32 x, S32 y, MASK mask)
370{
371	BOOL handled = FALSE;
372
373	if( gFocusMgr.getMouseCapture() == this )
374	{
375		gFocusMgr.setMouseCapture( NULL );
376
377		if (mMouseUpSignal)
378			(*mMouseUpSignal)( this, LLSD() );
379
380		handled = TRUE;
381		make_ui_sound("UISndClickRelease");
382	}
383	else
384	{
385		handled = TRUE;
386	}
387
388	return handled;
389}
390
391BOOL LLMultiSlider::handleMouseDown(S32 x, S32 y, MASK mask)
392{
393	// only do sticky-focus on non-chrome widgets
394	if (!getIsChrome())
395	{
396		setFocus(TRUE);
397	}
398	if (mMouseDownSignal)
399		(*mMouseDownSignal)( this, LLSD() );
400
401	if (MASK_CONTROL & mask) // if CTRL is modifying
402	{
403		setCurSliderValue(mInitialValue);
404		onCommit();
405	}
406	else
407	{
408		// scroll through thumbs to see if we have a new one selected and select that one
409		std::map<std::string, LLRect>::iterator mIt = mThumbRects.begin();
410		for(; mIt != mThumbRects.end(); mIt++) {
411			
412			// check if inside.  If so, set current slider and continue
413			if(mIt->second.pointInRect(x,y)) {
414				mCurSlider = mIt->first;
415				break;
416			}
417		}
418
419		// Find the offset of the actual mouse location from the center of the thumb.
420		if (mThumbRects[mCurSlider].pointInRect(x,y))
421		{
422			mMouseOffset = (mThumbRects[mCurSlider].mLeft + mThumbWidth/2) - x;
423		}
424		else
425		{
426			mMouseOffset = 0;
427		}
428
429		// Start dragging the thumb
430		// No handler needed for focus lost since this class has no state that depends on it.
431		gFocusMgr.setMouseCapture( this );
432		mDragStartThumbRect = mThumbRects[mCurSlider];				
433	}
434	make_ui_sound("UISndClick");
435
436	return TRUE;
437}
438
439BOOL	LLMultiSlider::handleKeyHere(KEY key, MASK mask)
440{
441	BOOL handled = FALSE;
442	switch(key)
443	{
444	case KEY_UP:
445	case KEY_DOWN:
446		// eat up and down keys to be consistent
447		handled = TRUE;
448		break;
449	case KEY_LEFT:
450		setCurSliderValue(getCurSliderValue() - getIncrement());
451		onCommit();
452		handled = TRUE;
453		break;
454	case KEY_RIGHT:
455		setCurSliderValue(getCurSliderValue() + getIncrement());
456		onCommit();
457		handled = TRUE;
458		break;
459	default:
460		break;
461	}
462	return handled;
463}
464
465void LLMultiSlider::draw()
466{
467	static LLUICachedControl<S32> extra_triangle_height ("UIExtraTriangleHeight", 0);
468	static LLUICachedControl<S32> extra_triangle_width ("UIExtraTriangleWidth", 0);
469	LLColor4 curThumbColor;
470
471	std::map<std::string, LLRect>::iterator mIt;
472	std::map<std::string, LLRect>::iterator curSldrIt;
473
474	// Draw background and thumb.
475
476	// drawing solids requires texturing be disabled
477	gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
478
479	LLRect rect(mDragStartThumbRect);
480
481	F32 opacity = getEnabled() ? 1.f : 0.3f;
482
483	// Track
484	LLUIImagePtr thumb_imagep = LLUI::getUIImage("Rounded_Square");
485
486	static LLUICachedControl<S32> multi_track_height ("UIMultiTrackHeight", 0);
487	S32 height_offset = (getRect().getHeight() - multi_track_height) / 2;
488	LLRect track_rect(0, getRect().getHeight() - height_offset, getRect().getWidth(), height_offset );
489
490
491	if(mDrawTrack)
492	{
493		track_rect.stretch(-1);
494		thumb_imagep->draw(track_rect, mTrackColor.get() % opacity);
495	}
496
497	// if we're supposed to use a drawn triangle
498	// simple gl call for the triangle
499	if(mUseTriangle) {
500
501		for(mIt = mThumbRects.begin(); mIt != mThumbRects.end(); mIt++) {
502
503			gl_triangle_2d(
504				mIt->second.mLeft - extra_triangle_width, 
505				mIt->second.mTop + extra_triangle_height,
506				mIt->second.mRight + extra_triangle_width, 
507				mIt->second.mTop + extra_triangle_height,
508				mIt->second.mLeft + mIt->second.getWidth() / 2, 
509				mIt->second.mBottom - extra_triangle_height,
510				mTriangleColor.get() % opacity, TRUE);
511		}
512	}
513	else if (!thumb_imagep)
514	{
515		// draw all the thumbs
516		curSldrIt = mThumbRects.end();
517		for(mIt = mThumbRects.begin(); mIt != mThumbRects.end(); mIt++) {
518			
519			// choose the color
520			curThumbColor = mThumbCenterColor.get();
521			if(mIt->first == mCurSlider) {
522				
523				curSldrIt = mIt;
524				continue;
525				//curThumbColor = mThumbCenterSelectedColor;
526			}
527
528			// the draw command
529			gl_rect_2d(mIt->second, curThumbColor, TRUE);
530		}
531
532		// now draw the current slider
533		if(curSldrIt != mThumbRects.end()) {
534			gl_rect_2d(curSldrIt->second, mThumbCenterSelectedColor.get(), TRUE);
535		}
536
537		// and draw the drag start
538		if (gFocusMgr.getMouseCapture() == this)
539		{
540			gl_rect_2d(mDragStartThumbRect, mThumbCenterColor.get() % opacity, FALSE);
541		}
542	}
543	else if( gFocusMgr.getMouseCapture() == this )
544	{
545		// draw drag start
546		thumb_imagep->drawSolid(mDragStartThumbRect, mThumbCenterColor.get() % 0.3f);
547
548		// draw the highlight
549		if (hasFocus())
550		{
551			thumb_imagep->drawBorder(mThumbRects[mCurSlider], gFocusMgr.getFocusColor(), gFocusMgr.getFocusFlashWidth());
552		}
553
554		// draw the thumbs
555		curSldrIt = mThumbRects.end();
556		for(mIt = mThumbRects.begin(); mIt != mThumbRects.end(); mIt++) 
557		{
558			// choose the color
559			curThumbColor = mThumbCenterColor.get();
560			if(mIt->first == mCurSlider) 
561			{
562				// don't draw now, draw last
563				curSldrIt = mIt;
564				continue;				
565			}
566			
567			// the draw command
568			thumb_imagep->drawSolid(mIt->second, curThumbColor);
569		}
570		
571		// draw cur slider last
572		if(curSldrIt != mThumbRects.end()) 
573		{
574			thumb_imagep->drawSolid(curSldrIt->second, mThumbCenterSelectedColor.get());
575		}
576		
577	}
578	else
579	{ 
580		// draw highlight
581		if (hasFocus())
582		{
583			thumb_imagep->drawBorder(mThumbRects[mCurSlider], gFocusMgr.getFocusColor(), gFocusMgr.getFocusFlashWidth());
584		}
585
586		// draw thumbs
587		curSldrIt = mThumbRects.end();
588		for(mIt = mThumbRects.begin(); mIt != mThumbRects.end(); mIt++) 
589		{
590			
591			// choose the color
592			curThumbColor = mThumbCenterColor.get();
593			if(mIt->first == mCurSlider) 
594			{
595				curSldrIt = mIt;
596				continue;
597				//curThumbColor = mThumbCenterSelectedColor;
598			}				
599			
600			thumb_imagep->drawSolid(mIt->second, curThumbColor % opacity);
601		}
602
603		if(curSldrIt != mThumbRects.end()) 
604		{
605			thumb_imagep->drawSolid(curSldrIt->second, mThumbCenterSelectedColor.get() % opacity);
606		}
607	}
608
609	LLF32UICtrl::draw();
610}
611boost::signals2::connection LLMultiSlider::setMouseDownCallback( const commit_signal_t::slot_type& cb ) 
612{ 
613	if (!mMouseDownSignal) mMouseDownSignal = new commit_signal_t();
614	return mMouseDownSignal->connect(cb); 
615}
616
617boost::signals2::connection LLMultiSlider::setMouseUpCallback(	const commit_signal_t::slot_type& cb )   
618{ 
619	if (!mMouseUpSignal) mMouseUpSignal = new commit_signal_t();
620	return mMouseUpSignal->connect(cb); 
621}