1/*
2 * Copyright 2001-2009, Haiku.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 *		Marc Flerackers (mflerackers@androme.be)
7 *		Stephan Aßmus <superstippi@gmx.de>
8 *		Axel Dörfler, axeld@pinc-software.de
9 */
10
11
12#include <Slider.h>
13
14#include <stdio.h>
15#include <stdlib.h>
16#include <string.h>
17
18#include <Bitmap.h>
19#include <ControlLook.h>
20#include <Errors.h>
21#include <LayoutUtils.h>
22#include <Message.h>
23#include <Region.h>
24#include <String.h>
25#include <Window.h>
26
27#include <binary_compatibility/Interface.h>
28
29
30#define USE_OFF_SCREEN_VIEW 0
31
32
33BSlider::BSlider(BRect frame, const char* name, const char* label,
34			BMessage* message, int32 minValue, int32 maxValue,
35			thumb_style thumbType, uint32 resizingMode, uint32 flags)
36	: BControl(frame, name, label, message, resizingMode, flags),
37	fModificationMessage(NULL),
38	fSnoozeAmount(20000),
39
40	fMinLimitLabel(NULL),
41	fMaxLimitLabel(NULL),
42
43	fMinValue(minValue),
44	fMaxValue(maxValue),
45	fKeyIncrementValue(1),
46
47	fHashMarkCount(0),
48	fHashMarks(B_HASH_MARKS_NONE),
49
50	fStyle(thumbType),
51
52	fOrientation(B_HORIZONTAL),
53	fBarThickness(6.0)
54{
55	_InitBarColor();
56
57	_InitObject();
58	SetValue(0);
59}
60
61
62BSlider::BSlider(BRect frame, const char *name, const char *label,
63			BMessage *message, int32 minValue, int32 maxValue,
64			orientation posture, thumb_style thumbType, uint32 resizingMode,
65			uint32 flags)
66	: BControl(frame, name, label, message, resizingMode, flags),
67	fModificationMessage(NULL),
68	fSnoozeAmount(20000),
69
70	fMinLimitLabel(NULL),
71	fMaxLimitLabel(NULL),
72
73	fMinValue(minValue),
74	fMaxValue(maxValue),
75	fKeyIncrementValue(1),
76
77	fHashMarkCount(0),
78	fHashMarks(B_HASH_MARKS_NONE),
79
80	fStyle(thumbType),
81
82	fOrientation(posture),
83	fBarThickness(6.0)
84{
85	_InitBarColor();
86
87	_InitObject();
88	SetValue(0);
89}
90
91
92BSlider::BSlider(const char *name, const char *label, BMessage *message,
93			int32 minValue, int32 maxValue, orientation posture,
94			thumb_style thumbType, uint32 flags)
95	: BControl(name, label, message, flags),
96	fModificationMessage(NULL),
97	fSnoozeAmount(20000),
98
99	fMinLimitLabel(NULL),
100	fMaxLimitLabel(NULL),
101
102	fMinValue(minValue),
103	fMaxValue(maxValue),
104	fKeyIncrementValue(1),
105
106	fHashMarkCount(0),
107	fHashMarks(B_HASH_MARKS_NONE),
108
109	fStyle(thumbType),
110
111	fOrientation(posture),
112	fBarThickness(6.0)
113{
114	_InitBarColor();
115
116	_InitObject();
117	SetValue(0);
118}
119
120
121BSlider::BSlider(BMessage *archive)
122	: BControl(archive)
123{
124	fModificationMessage = NULL;
125
126	if (archive->HasMessage("_mod_msg")) {
127		BMessage* message = new BMessage;
128
129		archive->FindMessage("_mod_msg", message);
130
131		SetModificationMessage(message);
132	}
133
134	if (archive->FindInt32("_sdelay", &fSnoozeAmount) != B_OK)
135		SetSnoozeAmount(20000);
136
137	rgb_color color;
138	if (archive->FindInt32("_fcolor", (int32 *)&color) == B_OK)
139		UseFillColor(true, &color);
140	else
141		UseFillColor(false);
142
143	int32 orient;
144	if (archive->FindInt32("_orient", &orient) == B_OK)
145		fOrientation = (orientation)orient;
146	else
147		fOrientation = B_HORIZONTAL;
148
149	fMinLimitLabel = NULL;
150	fMaxLimitLabel = NULL;
151
152	const char* minlbl = NULL;
153	const char* maxlbl = NULL;
154
155	archive->FindString("_minlbl", &minlbl);
156	archive->FindString("_maxlbl", &maxlbl);
157
158	SetLimitLabels(minlbl, maxlbl);
159
160	if (archive->FindInt32("_min", &fMinValue) != B_OK)
161		fMinValue = 0;
162
163	if (archive->FindInt32("_max", &fMaxValue) != B_OK)
164		fMaxValue = 100;
165
166	if (archive->FindInt32("_incrementvalue", &fKeyIncrementValue) != B_OK)
167		fKeyIncrementValue = 1;
168
169	if (archive->FindInt32("_hashcount", &fHashMarkCount) != B_OK)
170		fHashMarkCount = 11;
171
172	int16 hashloc;
173	if (archive->FindInt16("_hashloc", &hashloc) == B_OK)
174		fHashMarks = (hash_mark_location)hashloc;
175	else
176		fHashMarks = B_HASH_MARKS_NONE;
177
178	int16 sstyle;
179	if (archive->FindInt16("_sstyle", &sstyle) == B_OK)
180		fStyle = (thumb_style)sstyle;
181	else
182		fStyle = B_BLOCK_THUMB;
183
184	if (archive->FindInt32("_bcolor", (int32 *)&color) != B_OK)
185		color = tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), B_DARKEN_4_TINT);
186	SetBarColor(color);
187
188	float bthickness;
189	if (archive->FindFloat("_bthickness", &bthickness) == B_OK)
190		fBarThickness = bthickness;
191	else
192		fBarThickness = 6.0f;
193
194	_InitObject();
195}
196
197
198BSlider::~BSlider()
199{
200#if USE_OFF_SCREEN_VIEW
201	delete fOffScreenBits;
202#endif
203
204	delete fModificationMessage;
205	free(fMinLimitLabel);
206	free(fMaxLimitLabel);
207}
208
209
210void
211BSlider::_InitBarColor()
212{
213	if (be_control_look != NULL) {
214		SetBarColor(be_control_look->SliderBarColor(
215			ui_color(B_PANEL_BACKGROUND_COLOR)));
216	} else {
217		SetBarColor(tint_color(ui_color(B_PANEL_BACKGROUND_COLOR),
218			B_DARKEN_4_TINT));
219	}
220
221	UseFillColor(false, NULL);
222}
223
224
225void
226BSlider::_InitObject()
227{
228	fLocation.x = 0;
229	fLocation.y = 0;
230	fInitialLocation.x = 0;
231	fInitialLocation.y = 0;
232
233#if USE_OFF_SCREEN_VIEW
234	fOffScreenBits = NULL;
235	fOffScreenView = NULL;
236#endif
237
238	fUpdateText = NULL;
239	fMinSize.Set(-1, -1);
240	fMaxUpdateTextWidth = -1.0;
241}
242
243
244BArchivable*
245BSlider::Instantiate(BMessage *archive)
246{
247	if (validate_instantiation(archive, "BSlider"))
248		return new BSlider(archive);
249
250	return NULL;
251}
252
253
254status_t
255BSlider::Archive(BMessage *archive, bool deep) const
256{
257	status_t ret = BControl::Archive(archive, deep);
258
259	if (ModificationMessage() && ret == B_OK)
260		ret = archive->AddMessage("_mod_msg", ModificationMessage());
261
262	if (ret == B_OK)
263		ret = archive->AddInt32("_sdelay", fSnoozeAmount);
264	if (ret == B_OK)
265		ret = archive->AddInt32("_bcolor", (const uint32 &)fBarColor);
266
267	if (FillColor(NULL) && ret == B_OK)
268		ret = archive->AddInt32("_fcolor", (const uint32 &)fFillColor);
269
270	if (ret == B_OK && fMinLimitLabel)
271		ret = archive->AddString("_minlbl", fMinLimitLabel);
272
273	if (ret == B_OK && fMaxLimitLabel)
274		ret = archive->AddString("_maxlbl", fMaxLimitLabel);
275
276	if (ret == B_OK)
277		ret = archive->AddInt32("_min", fMinValue);
278	if (ret == B_OK)
279		ret = archive->AddInt32("_max", fMaxValue);
280
281	if (ret == B_OK)
282		ret = archive->AddInt32("_incrementvalue", fKeyIncrementValue);
283	if (ret == B_OK)
284		ret = archive->AddInt32("_hashcount", fHashMarkCount);
285	if (ret == B_OK)
286		ret = archive->AddInt16("_hashloc", fHashMarks);
287	if (ret == B_OK)
288		ret = archive->AddInt16("_sstyle", fStyle);
289	if (ret == B_OK)
290		ret = archive->AddInt32("_orient", fOrientation);
291	if (ret == B_OK)
292		ret = archive->AddFloat("_bthickness", fBarThickness);
293
294	return ret;
295}
296
297
298status_t
299BSlider::Perform(perform_code code, void* _data)
300{
301	switch (code) {
302		case PERFORM_CODE_MIN_SIZE:
303			((perform_data_min_size*)_data)->return_value
304				= BSlider::MinSize();
305			return B_OK;
306		case PERFORM_CODE_MAX_SIZE:
307			((perform_data_max_size*)_data)->return_value
308				= BSlider::MaxSize();
309			return B_OK;
310		case PERFORM_CODE_PREFERRED_SIZE:
311			((perform_data_preferred_size*)_data)->return_value
312				= BSlider::PreferredSize();
313			return B_OK;
314		case PERFORM_CODE_LAYOUT_ALIGNMENT:
315			((perform_data_layout_alignment*)_data)->return_value
316				= BSlider::LayoutAlignment();
317			return B_OK;
318		case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH:
319			((perform_data_has_height_for_width*)_data)->return_value
320				= BSlider::HasHeightForWidth();
321			return B_OK;
322		case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH:
323		{
324			perform_data_get_height_for_width* data
325				= (perform_data_get_height_for_width*)_data;
326			BSlider::GetHeightForWidth(data->width, &data->min, &data->max,
327				&data->preferred);
328			return B_OK;
329		}
330		case PERFORM_CODE_SET_LAYOUT:
331		{
332			perform_data_set_layout* data = (perform_data_set_layout*)_data;
333			BSlider::SetLayout(data->layout);
334			return B_OK;
335		}
336		case PERFORM_CODE_LAYOUT_INVALIDATED:
337		{
338			perform_data_layout_invalidated* data
339				= (perform_data_layout_invalidated*)_data;
340			BSlider::LayoutInvalidated(data->descendants);
341			return B_OK;
342		}
343		case PERFORM_CODE_DO_LAYOUT:
344		{
345			BSlider::DoLayout();
346			return B_OK;
347		}
348	}
349
350	return BControl::Perform(code, _data);
351}
352
353
354void
355BSlider::WindowActivated(bool state)
356{
357	BControl::WindowActivated(state);
358}
359
360
361void
362BSlider::AttachedToWindow()
363{
364	ResizeToPreferred();
365
366#if USE_OFF_SCREEN_VIEW
367	BRect bounds(Bounds());
368
369	if (!fOffScreenView) {
370		fOffScreenView = new BView(bounds, "", B_FOLLOW_ALL, B_WILL_DRAW);
371
372		BFont font;
373		GetFont(&font);
374		fOffScreenView->SetFont(&font);
375	}
376
377	if (!fOffScreenBits) {
378		fOffScreenBits = new BBitmap(bounds, B_RGBA32, true, false);
379
380		if (fOffScreenBits && fOffScreenView)
381			fOffScreenBits->AddChild(fOffScreenView);
382
383	} else if (fOffScreenView)
384		fOffScreenBits->AddChild(fOffScreenView);
385#endif // USE_OFF_SCREEN_VIEW
386
387	BControl::AttachedToWindow();
388
389	BView* view = OffscreenView();
390	if (view && view->LockLooper()) {
391		view->SetViewColor(B_TRANSPARENT_COLOR);
392		view->SetLowColor(LowColor());
393		view->UnlockLooper();
394	}
395
396	int32 value = Value();
397	SetValue(value);
398		// makes sure the value is within valid bounds
399	_SetLocationForValue(Value());
400		// makes sure the location is correct
401	UpdateTextChanged();
402}
403
404
405void
406BSlider::AllAttached()
407{
408	BControl::AllAttached();
409}
410
411
412void
413BSlider::AllDetached()
414{
415	BControl::AllDetached();
416}
417
418
419void
420BSlider::DetachedFromWindow()
421{
422	BControl::DetachedFromWindow();
423
424#if USE_OFF_SCREEN_VIEW
425	if (fOffScreenBits) {
426		delete fOffScreenBits;
427		fOffScreenBits = NULL;
428		fOffScreenView = NULL;
429	}
430#endif
431}
432
433
434void
435BSlider::MessageReceived(BMessage *msg)
436{
437	BControl::MessageReceived(msg);
438}
439
440
441void
442BSlider::FrameMoved(BPoint new_position)
443{
444	BControl::FrameMoved(new_position);
445}
446
447
448void
449BSlider::FrameResized(float w,float h)
450{
451	BControl::FrameResized(w, h);
452
453	BRect bounds(Bounds());
454
455	if (bounds.right <= 0.0f || bounds.bottom <= 0.0f)
456		return;
457
458#if USE_OFF_SCREEN_VIEW
459	if (fOffScreenBits) {
460		fOffScreenBits->RemoveChild(fOffScreenView);
461		delete fOffScreenBits;
462
463		fOffScreenView->ResizeTo(bounds.Width(), bounds.Height());
464
465		fOffScreenBits = new BBitmap(Bounds(), B_RGBA32, true, false);
466		fOffScreenBits->AddChild(fOffScreenView);
467	}
468#endif
469
470	Invalidate();
471}
472
473
474void
475BSlider::KeyDown(const char *bytes, int32 numBytes)
476{
477	if (!IsEnabled() || IsHidden())
478		return;
479
480	int32 newValue = Value();
481
482	switch (bytes[0]) {
483		case B_LEFT_ARROW:
484		case B_DOWN_ARROW:
485			newValue -= KeyIncrementValue();
486			break;
487
488		case B_RIGHT_ARROW:
489		case B_UP_ARROW:
490			newValue += KeyIncrementValue();
491			break;
492
493		case B_HOME:
494			newValue = fMinValue;
495			break;
496		case B_END:
497			newValue = fMaxValue;
498			break;
499
500		default:
501			BControl::KeyDown(bytes, numBytes);
502			return;
503	}
504
505	if (newValue < fMinValue)
506		newValue = fMinValue;
507	if (newValue > fMaxValue)
508		newValue = fMaxValue;
509
510	if (newValue != Value()) {
511		fInitialLocation = _Location();
512		SetValue(newValue);
513		InvokeNotify(ModificationMessage(), B_CONTROL_MODIFIED);
514	}
515}
516
517void
518BSlider::KeyUp(const char *bytes, int32 numBytes)
519{
520	if (fInitialLocation != _Location()) {
521		// The last KeyDown event triggered the modification message or no
522		// notification at all, we may also have sent the modification message
523		// continually while the user kept pressing the key. In either case,
524		// finish with the final message to make the behavior consistent with
525		// changing the value by mouse.
526		Invoke();
527	}
528}
529
530
531/*!
532	Makes sure the \a point is within valid bounds.
533	Returns \c true if the relevant coordinate (depending on the orientation
534	of the slider) differs from \a comparePoint.
535*/
536bool
537BSlider::_ConstrainPoint(BPoint& point, BPoint comparePoint) const
538{
539	if (fOrientation == B_HORIZONTAL) {
540		if (point.x != comparePoint.x) {
541			if (point.x < _MinPosition())
542				point.x = _MinPosition();
543			else if (point.x > _MaxPosition())
544				point.x = _MaxPosition();
545
546			return true;
547		}
548	} else {
549		if (point.y != comparePoint.y) {
550			if (point.y > _MinPosition())
551				point.y = _MinPosition();
552			else if (point.y < _MaxPosition())
553				point.y = _MaxPosition();
554
555			return true;
556		}
557	}
558
559	return false;
560}
561
562
563void
564BSlider::MouseDown(BPoint point)
565{
566	if (!IsEnabled())
567		return;
568
569	if (BarFrame().Contains(point) || ThumbFrame().Contains(point))
570		fInitialLocation = _Location();
571
572	uint32 buttons;
573	GetMouse(&point, &buttons, true);
574
575	_ConstrainPoint(point, fInitialLocation);
576	SetValue(ValueForPoint(point));
577
578	if (_Location() != fInitialLocation)
579		InvokeNotify(ModificationMessage(), B_CONTROL_MODIFIED);
580
581	if (Window()->Flags() & B_ASYNCHRONOUS_CONTROLS) {
582		SetTracking(true);
583		SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS | B_NO_POINTER_HISTORY);
584	} else {
585		// synchronous mouse tracking
586		BPoint prevPoint;
587
588		while (buttons) {
589			prevPoint = point;
590
591			snooze(SnoozeAmount());
592			GetMouse(&point, &buttons, true);
593
594			if (_ConstrainPoint(point, prevPoint)) {
595				int32 value = ValueForPoint(point);
596				if (value != Value()) {
597					SetValue(value);
598					InvokeNotify(ModificationMessage(), B_CONTROL_MODIFIED);
599				}
600			}
601		}
602		if (_Location() != fInitialLocation)
603			Invoke();
604	}
605}
606
607
608void
609BSlider::MouseUp(BPoint point)
610{
611	if (IsTracking()) {
612		if (_Location() != fInitialLocation)
613			Invoke();
614
615		SetTracking(false);
616	} else
617		BControl::MouseUp(point);
618}
619
620
621void
622BSlider::MouseMoved(BPoint point, uint32 transit, const BMessage *message)
623{
624	if (IsTracking()) {
625		if (_ConstrainPoint(point, _Location())) {
626			int32 value = ValueForPoint(point);
627			if (value != Value()) {
628				SetValue(value);
629				InvokeNotify(ModificationMessage(), B_CONTROL_MODIFIED);
630			}
631		}
632	} else
633		BControl::MouseMoved(point, transit, message);
634}
635
636
637void
638BSlider::Pulse()
639{
640	BControl::Pulse();
641}
642
643
644void
645BSlider::SetLabel(const char *label)
646{
647	BControl::SetLabel(label);
648}
649
650
651void
652BSlider::SetLimitLabels(const char *minLabel, const char *maxLabel)
653{
654	free(fMinLimitLabel);
655	fMinLimitLabel = minLabel ? strdup(minLabel) : NULL;
656
657	free(fMaxLimitLabel);
658	fMaxLimitLabel = maxLabel ? strdup(maxLabel) : NULL;
659
660	InvalidateLayout();
661
662	// TODO: This is for backwards compatibility and should
663	// probably be removed when breaking binary compatiblity.
664	// Applications like our own Mouse rely on this behavior.
665	if ((Flags() & B_SUPPORTS_LAYOUT) == 0)
666		ResizeToPreferred();
667
668	Invalidate();
669}
670
671
672const char*
673BSlider::MinLimitLabel() const
674{
675	return fMinLimitLabel;
676}
677
678
679const char*
680BSlider::MaxLimitLabel() const
681{
682	return fMaxLimitLabel;
683}
684
685
686void
687BSlider::SetValue(int32 value)
688{
689	if (value < fMinValue)
690		value = fMinValue;
691	if (value > fMaxValue)
692		value = fMaxValue;
693
694	if (value == Value())
695		return;
696
697	_SetLocationForValue(value);
698
699	BRect oldThumbFrame = ThumbFrame();
700
701	// While it would be enough to do this dependent on fUseFillColor,
702	// that doesn't work out if DrawBar() has been overridden by a sub class
703	if (fOrientation == B_HORIZONTAL)
704		oldThumbFrame.top = BarFrame().top;
705	else
706		oldThumbFrame.left = BarFrame().left;
707
708	BControl::SetValueNoUpdate(value);
709	BRect invalid = oldThumbFrame | ThumbFrame();
710
711	if (Style() == B_TRIANGLE_THUMB) {
712		// 1) We need to take care of pixels touched because of anti-aliasing.
713		// 2) We need to update the region with the focus mark as well. (A
714		// method BSlider::FocusMarkFrame() would be nice as well.)
715		if (fOrientation == B_HORIZONTAL) {
716			if (IsFocus())
717				invalid.bottom += 2;
718			invalid.InsetBy(-1, 0);
719		} else {
720			if (IsFocus())
721				invalid.left -= 2;
722			invalid.InsetBy(0, -1);
723		}
724	}
725
726	Invalidate(invalid);
727
728	UpdateTextChanged();
729}
730
731
732int32
733BSlider::ValueForPoint(BPoint location) const
734{
735	float min;
736	float max;
737	float position;
738	if (fOrientation == B_HORIZONTAL) {
739		min = _MinPosition();
740		max = _MaxPosition();
741		position = location.x;
742	} else {
743		max = _MinPosition();
744		min = _MaxPosition();
745		position = min + (max - location.y);
746	}
747
748	if (position < min)
749		position = min;
750	if (position > max)
751		position = max;
752
753	return (int32)roundf(((position - min) * (fMaxValue - fMinValue) / (max - min)) + fMinValue);
754}
755
756
757void
758BSlider::SetPosition(float position)
759{
760	if (position <= 0.0f)
761		BControl::SetValue(fMinValue);
762	else if (position >= 1.0f)
763		BControl::SetValue(fMaxValue);
764	else
765		BControl::SetValue((int32)(position * (fMaxValue - fMinValue) + fMinValue));
766}
767
768
769float
770BSlider::Position() const
771{
772	float range = (float)(fMaxValue - fMinValue);
773	if (range == 0.0f)
774		range = 1.0f;
775
776	return (float)(Value() - fMinValue) / range;
777}
778
779
780void
781BSlider::SetEnabled(bool on)
782{
783	BControl::SetEnabled(on);
784}
785
786
787void
788BSlider::GetLimits(int32 *minimum, int32 *maximum) const
789{
790	if (minimum != NULL)
791		*minimum = fMinValue;
792	if (maximum != NULL)
793		*maximum = fMaxValue;
794}
795
796
797// #pragma mark - drawing
798
799
800void
801BSlider::Draw(BRect updateRect)
802{
803	// clear out background
804	BRegion background(updateRect);
805	background.Exclude(BarFrame());
806	bool drawBackground = true;
807	if (Parent() && (Parent()->Flags() & B_DRAW_ON_CHILDREN) != 0) {
808		// This view is embedded somewhere, most likely the Tracker Desktop
809		// shelf.
810		drawBackground = false;
811	}
812
813#if USE_OFF_SCREEN_VIEW
814	if (!fOffScreenBits)
815		return;
816
817	if (fOffScreenBits->Lock()) {
818		fOffScreenView->SetViewColor(ViewColor());
819		fOffScreenView->SetLowColor(LowColor());
820#endif
821
822		if (drawBackground && background.Frame().IsValid())
823			OffscreenView()->FillRegion(&background, B_SOLID_LOW);
824
825#if USE_OFF_SCREEN_VIEW
826		fOffScreenView->Sync();
827		fOffScreenBits->Unlock();
828	}
829#endif
830
831	DrawSlider();
832}
833
834
835void
836BSlider::DrawSlider()
837{
838	if (LockLooper()) {
839#if USE_OFF_SCREEN_VIEW
840		if (!fOffScreenBits)
841			return;
842		if (fOffScreenBits->Lock()) {
843#endif
844			DrawBar();
845			DrawHashMarks();
846			DrawThumb();
847			DrawFocusMark();
848			DrawText();
849
850#if USE_OFF_SCREEN_VIEW
851			fOffScreenView->Sync();
852			fOffScreenBits->Unlock();
853
854			DrawBitmap(fOffScreenBits, B_ORIGIN);
855		}
856#endif
857		UnlockLooper();
858	}
859}
860
861
862void
863BSlider::DrawBar()
864{
865	BRect frame = BarFrame();
866	BView *view = OffscreenView();
867
868	if (be_control_look != NULL) {
869		uint32 flags = be_control_look->Flags(this);
870		rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
871		rgb_color rightFillColor = fBarColor;
872		rgb_color leftFillColor = fUseFillColor ? fFillColor : fBarColor;
873		be_control_look->DrawSliderBar(view, frame, frame, base, leftFillColor,
874			rightFillColor, Position(), flags, fOrientation);
875		return;
876	}
877
878	rgb_color no_tint = ui_color(B_PANEL_BACKGROUND_COLOR);
879	rgb_color lightenmax;
880	rgb_color darken1;
881	rgb_color darken2;
882	rgb_color darkenmax;
883
884	rgb_color barColor;
885	rgb_color fillColor;
886
887	if (IsEnabled()) {
888		lightenmax	= tint_color(no_tint, B_LIGHTEN_MAX_TINT);
889		darken1		= tint_color(no_tint, B_DARKEN_1_TINT);
890		darken2		= tint_color(no_tint, B_DARKEN_2_TINT);
891		darkenmax	= tint_color(no_tint, B_DARKEN_MAX_TINT);
892		barColor	= fBarColor;
893		fillColor	= fFillColor;
894	} else {
895		lightenmax	= tint_color(no_tint, B_LIGHTEN_MAX_TINT);
896		darken1		= no_tint;
897		darken2		= tint_color(no_tint, B_DARKEN_1_TINT);
898		darkenmax	= tint_color(no_tint, B_DARKEN_3_TINT);
899
900		barColor.red	= (fBarColor.red + no_tint.red) / 2;
901		barColor.green	= (fBarColor.green + no_tint.green) / 2;
902		barColor.blue	= (fBarColor.blue + no_tint.blue) / 2;
903		barColor.alpha	= 255;
904
905		fillColor.red	= (fFillColor.red + no_tint.red) / 2;
906		fillColor.green	= (fFillColor.green + no_tint.green) / 2;
907		fillColor.blue	= (fFillColor.blue + no_tint.blue) / 2;
908		fillColor.alpha	= 255;
909	}
910
911	// exclude the block thumb from the bar filling
912
913	BRect lowerFrame = frame.InsetByCopy(1, 1);
914	lowerFrame.top++;
915	lowerFrame.left++;
916	BRect upperFrame = lowerFrame;
917	BRect thumbFrame;
918
919	if (Style() == B_BLOCK_THUMB) {
920		thumbFrame = ThumbFrame();
921
922		if (fOrientation == B_HORIZONTAL) {
923			lowerFrame.right = thumbFrame.left;
924			upperFrame.left = thumbFrame.right;
925		} else {
926			lowerFrame.top = thumbFrame.bottom;
927			upperFrame.bottom = thumbFrame.top;
928		}
929	} else if (fUseFillColor) {
930		if (fOrientation == B_HORIZONTAL) {
931			lowerFrame.right = floor(lowerFrame.left - 1 + Position()
932				* (lowerFrame.Width() + 1));
933			upperFrame.left = lowerFrame.right;
934		} else {
935			lowerFrame.top = floor(lowerFrame.bottom + 1 - Position()
936				* (lowerFrame.Height() + 1));
937			upperFrame.bottom = lowerFrame.top;
938		}
939	}
940
941	view->SetHighColor(barColor);
942	view->FillRect(upperFrame);
943
944	if (Style() == B_BLOCK_THUMB || fUseFillColor) {
945		if (fUseFillColor)
946			view->SetHighColor(fillColor);
947		view->FillRect(lowerFrame);
948	}
949
950	if (Style() == B_BLOCK_THUMB) {
951		// We don't want to stroke the lines over the thumb
952
953		PushState();
954
955		BRegion region;
956		GetClippingRegion(&region);
957		region.Exclude(thumbFrame);
958		ConstrainClippingRegion(&region);
959	}
960
961	view->SetHighColor(darken1);
962	view->StrokeLine(BPoint(frame.left, frame.top),
963					 BPoint(frame.left + 1.0f, frame.top));
964	view->StrokeLine(BPoint(frame.left, frame.bottom),
965					 BPoint(frame.left + 1.0f, frame.bottom));
966	view->StrokeLine(BPoint(frame.right - 1.0f, frame.top),
967					 BPoint(frame.right, frame.top));
968
969	view->SetHighColor(darken2);
970	view->StrokeLine(BPoint(frame.left + 1.0f, frame.top),
971					 BPoint(frame.right - 1.0f, frame.top));
972	view->StrokeLine(BPoint(frame.left, frame.bottom - 1.0f),
973					 BPoint(frame.left, frame.top + 1.0f));
974
975	view->SetHighColor(lightenmax);
976	view->StrokeLine(BPoint(frame.left + 1.0f, frame.bottom),
977					 BPoint(frame.right, frame.bottom));
978	view->StrokeLine(BPoint(frame.right, frame.bottom - 1.0f),
979					 BPoint(frame.right, frame.top + 1.0f));
980
981	frame.InsetBy(1.0f, 1.0f);
982
983	view->SetHighColor(darkenmax);
984	view->StrokeLine(BPoint(frame.left, frame.bottom),
985					 BPoint(frame.left, frame.top));
986	view->StrokeLine(BPoint(frame.left + 1.0f, frame.top),
987					 BPoint(frame.right, frame.top));
988
989	if (Style() == B_BLOCK_THUMB)
990		PopState();
991}
992
993
994void
995BSlider::DrawHashMarks()
996{
997	if (fHashMarks == B_HASH_MARKS_NONE)
998		return;
999
1000	BRect frame = HashMarksFrame();
1001	BView* view = OffscreenView();
1002
1003	if (be_control_look != NULL) {
1004		rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
1005		uint32 flags = be_control_look->Flags(this);
1006		be_control_look->DrawSliderHashMarks(view, frame, frame, base,
1007			fHashMarkCount, fHashMarks, flags, fOrientation);
1008		return;
1009	}
1010
1011	rgb_color no_tint = ui_color(B_PANEL_BACKGROUND_COLOR);
1012	rgb_color lightenmax;
1013	rgb_color darken2;
1014
1015	if (IsEnabled()) {
1016		lightenmax = tint_color(no_tint, B_LIGHTEN_MAX_TINT);
1017		darken2 = tint_color(no_tint, B_DARKEN_2_TINT);
1018	} else {
1019		lightenmax = tint_color(no_tint, B_LIGHTEN_2_TINT);
1020		darken2 = tint_color(no_tint, B_DARKEN_1_TINT);
1021	}
1022
1023	float pos = _MinPosition();
1024	int32 hashMarkCount = max_c(fHashMarkCount, 2);
1025		// draw at least two hashmarks at min/max if
1026		// fHashMarks != B_HASH_MARKS_NONE
1027	float factor = (_MaxPosition() - pos) / (hashMarkCount - 1);
1028
1029	if (fHashMarks & B_HASH_MARKS_TOP) {
1030
1031		view->BeginLineArray(hashMarkCount * 2);
1032
1033		if (fOrientation == B_HORIZONTAL) {
1034			for (int32 i = 0; i < hashMarkCount; i++) {
1035				view->AddLine(BPoint(pos, frame.top),
1036							  BPoint(pos, frame.top + 5), darken2);
1037				view->AddLine(BPoint(pos + 1, frame.top),
1038							  BPoint(pos + 1, frame.top + 5), lightenmax);
1039
1040				pos += factor;
1041			}
1042		} else {
1043			for (int32 i = 0; i < hashMarkCount; i++) {
1044				view->AddLine(BPoint(frame.left, pos),
1045							  BPoint(frame.left + 5, pos), darken2);
1046				view->AddLine(BPoint(frame.left, pos + 1),
1047							  BPoint(frame.left + 5, pos + 1), lightenmax);
1048
1049				pos += factor;
1050			}
1051		}
1052
1053		view->EndLineArray();
1054	}
1055
1056	pos = _MinPosition();
1057
1058	if (fHashMarks & B_HASH_MARKS_BOTTOM) {
1059
1060		view->BeginLineArray(hashMarkCount * 2);
1061
1062		if (fOrientation == B_HORIZONTAL) {
1063			for (int32 i = 0; i < hashMarkCount; i++) {
1064				view->AddLine(BPoint(pos, frame.bottom - 5),
1065							  BPoint(pos, frame.bottom), darken2);
1066				view->AddLine(BPoint(pos + 1, frame.bottom - 5),
1067							  BPoint(pos + 1, frame.bottom), lightenmax);
1068
1069				pos += factor;
1070			}
1071		} else {
1072			for (int32 i = 0; i < hashMarkCount; i++) {
1073				view->AddLine(BPoint(frame.right - 5, pos),
1074							  BPoint(frame.right, pos), darken2);
1075				view->AddLine(BPoint(frame.right - 5, pos + 1),
1076							  BPoint(frame.right, pos + 1), lightenmax);
1077
1078				pos += factor;
1079			}
1080		}
1081
1082		view->EndLineArray();
1083	}
1084}
1085
1086
1087void
1088BSlider::DrawThumb()
1089{
1090	if (Style() == B_BLOCK_THUMB)
1091		_DrawBlockThumb();
1092	else
1093		_DrawTriangleThumb();
1094}
1095
1096
1097void
1098BSlider::DrawFocusMark()
1099{
1100	if (!IsFocus())
1101		return;
1102
1103	OffscreenView()->SetHighColor(ui_color(B_KEYBOARD_NAVIGATION_COLOR));
1104
1105	BRect frame = ThumbFrame();
1106
1107	if (fStyle == B_BLOCK_THUMB) {
1108		frame.left += 2.0f;
1109		frame.top += 2.0f;
1110		frame.right -= 3.0f;
1111		frame.bottom -= 3.0f;
1112		OffscreenView()->StrokeRect(frame);
1113	} else {
1114		if (fOrientation == B_HORIZONTAL) {
1115			OffscreenView()->StrokeLine(BPoint(frame.left, frame.bottom + 2.0f),
1116				BPoint(frame.right, frame.bottom + 2.0f));
1117		} else {
1118			OffscreenView()->StrokeLine(BPoint(frame.left - 2.0f, frame.top),
1119				BPoint(frame.left - 2.0f, frame.bottom));
1120		}
1121	}
1122}
1123
1124
1125void
1126BSlider::DrawText()
1127{
1128	BRect bounds(Bounds());
1129	BView *view = OffscreenView();
1130
1131	rgb_color base = LowColor();
1132	uint32 flags = 0;
1133	if (be_control_look == NULL) {
1134		if (IsEnabled()) {
1135			view->SetHighColor(0, 0, 0);
1136		} else {
1137			view->SetHighColor(tint_color(LowColor(), B_DISABLED_LABEL_TINT));
1138		}
1139	} else
1140 		flags = be_control_look->Flags(this);
1141
1142	font_height fontHeight;
1143	GetFontHeight(&fontHeight);
1144	if (Orientation() == B_HORIZONTAL) {
1145		if (Label()) {
1146			if (be_control_look == NULL) {
1147				view->DrawString(Label(),
1148					BPoint(0.0, ceilf(fontHeight.ascent)));
1149			} else {
1150				be_control_look->DrawLabel(view, Label(), base, flags,
1151					BPoint(0.0, ceilf(fontHeight.ascent)));
1152			}
1153		}
1154
1155		// the update text is updated in SetValue() only
1156		if (fUpdateText != NULL) {
1157			if (be_control_look == NULL) {
1158				view->DrawString(fUpdateText, BPoint(bounds.right
1159					- StringWidth(fUpdateText), ceilf(fontHeight.ascent)));
1160			} else {
1161				be_control_look->DrawLabel(view, fUpdateText, base, flags,
1162					BPoint(bounds.right - StringWidth(fUpdateText),
1163						ceilf(fontHeight.ascent)));
1164			}
1165		}
1166
1167		if (fMinLimitLabel) {
1168			if (be_control_look == NULL) {
1169				view->DrawString(fMinLimitLabel, BPoint(0.0, bounds.bottom
1170					- fontHeight.descent));
1171			} else {
1172				be_control_look->DrawLabel(view, fMinLimitLabel, base, flags,
1173					BPoint(0.0, bounds.bottom - fontHeight.descent));
1174			}
1175		}
1176
1177		if (fMaxLimitLabel) {
1178			if (be_control_look == NULL) {
1179				view->DrawString(fMaxLimitLabel, BPoint(bounds.right
1180					- StringWidth(fMaxLimitLabel), bounds.bottom
1181					- fontHeight.descent));
1182			} else {
1183				be_control_look->DrawLabel(view, fMaxLimitLabel, base, flags,
1184					BPoint(bounds.right - StringWidth(fMaxLimitLabel),
1185						bounds.bottom - fontHeight.descent));
1186			}
1187		}
1188	} else {
1189		float lineHeight = ceilf(fontHeight.ascent) + ceilf(fontHeight.descent)
1190			+ ceilf(fontHeight.leading);
1191		float baseLine = ceilf(fontHeight.ascent);
1192
1193		if (Label()) {
1194			if (be_control_look == NULL) {
1195				view->DrawString(Label(), BPoint((bounds.Width()
1196					- StringWidth(Label())) / 2.0, baseLine));
1197			} else {
1198				be_control_look->DrawLabel(view, Label(), base, flags,
1199					BPoint((bounds.Width() - StringWidth(Label())) / 2.0,
1200						baseLine));
1201			}
1202			baseLine += lineHeight;
1203		}
1204
1205		if (fMaxLimitLabel) {
1206			if (be_control_look == NULL) {
1207				view->DrawString(fMaxLimitLabel, BPoint((bounds.Width()
1208					- StringWidth(fMaxLimitLabel)) / 2.0, baseLine));
1209			} else {
1210				be_control_look->DrawLabel(view, fMaxLimitLabel, base, flags,
1211					BPoint((bounds.Width()
1212						- StringWidth(fMaxLimitLabel)) / 2.0, baseLine));
1213			}
1214		}
1215
1216		baseLine = bounds.bottom - ceilf(fontHeight.descent);
1217
1218		if (fMinLimitLabel) {
1219			if (be_control_look == NULL) {
1220				view->DrawString(fMinLimitLabel, BPoint((bounds.Width()
1221					- StringWidth(fMinLimitLabel)) / 2.0, baseLine));
1222			} else {
1223				be_control_look->DrawLabel(view, fMinLimitLabel, base, flags,
1224					BPoint((bounds.Width()
1225						- StringWidth(fMinLimitLabel)) / 2.0, baseLine));
1226			}
1227			baseLine -= lineHeight;
1228		}
1229
1230		if (fUpdateText != NULL) {
1231			if (be_control_look == NULL) {
1232				view->DrawString(fUpdateText, BPoint((bounds.Width()
1233					- StringWidth(fUpdateText)) / 2.0, baseLine));
1234			} else {
1235				be_control_look->DrawLabel(view, fUpdateText, base, flags,
1236					BPoint((bounds.Width()
1237						- StringWidth(fUpdateText)) / 2.0, baseLine));
1238			}
1239		}
1240	}
1241}
1242
1243
1244// #pragma mark -
1245
1246
1247const char*
1248BSlider::UpdateText() const
1249{
1250	return NULL;
1251}
1252
1253
1254void
1255BSlider::UpdateTextChanged()
1256{
1257	// update text label
1258	float oldWidth = 0.0;
1259	if (fUpdateText != NULL)
1260		oldWidth = StringWidth(fUpdateText);
1261
1262	const char* oldUpdateText = fUpdateText;
1263	fUpdateText = UpdateText();
1264	bool updateTextOnOff = (fUpdateText == NULL && oldUpdateText != NULL)
1265		|| (fUpdateText != NULL && oldUpdateText == NULL);
1266
1267	float newWidth = 0.0;
1268	if (fUpdateText != NULL)
1269		newWidth = StringWidth(fUpdateText);
1270
1271	float width = ceilf(max_c(newWidth, oldWidth)) + 2.0f;
1272	if (width != 0) {
1273		font_height fontHeight;
1274		GetFontHeight(&fontHeight);
1275
1276		float height = ceilf(fontHeight.ascent) + ceilf(fontHeight.descent);
1277		float lineHeight = height + ceilf(fontHeight.leading);
1278		BRect invalid(Bounds());
1279		if (fOrientation == B_HORIZONTAL)
1280			invalid = BRect(invalid.right - width, 0, invalid.right, height);
1281		else {
1282			if (!updateTextOnOff) {
1283				invalid.left = (invalid.left + invalid.right - width) / 2;
1284				invalid.right = invalid.left + width;
1285				if (fMinLimitLabel)
1286					invalid.bottom -= lineHeight;
1287				invalid.top = invalid.bottom - height;
1288			}
1289		}
1290		Invalidate(invalid);
1291	}
1292
1293	float oldMaxUpdateTextWidth = fMaxUpdateTextWidth;
1294	fMaxUpdateTextWidth = MaxUpdateTextWidth();
1295	if (oldMaxUpdateTextWidth != fMaxUpdateTextWidth)
1296		InvalidateLayout();
1297}
1298
1299
1300BRect
1301BSlider::BarFrame() const
1302{
1303	BRect frame(Bounds());
1304
1305	font_height fontHeight;
1306	GetFontHeight(&fontHeight);
1307
1308	float textHeight = ceilf(fontHeight.ascent) + ceilf(fontHeight.descent);
1309	float leading = ceilf(fontHeight.leading);
1310
1311	float thumbInset;
1312	if (fStyle == B_BLOCK_THUMB)
1313		thumbInset = 8.0;
1314	else
1315		thumbInset = 7.0;
1316
1317	if (Orientation() == B_HORIZONTAL) {
1318		frame.left = thumbInset;
1319		frame.top = 6.0 + (Label() || fUpdateText ? textHeight + 4.0 : 0.0);
1320		frame.right -= thumbInset;
1321		frame.bottom = frame.top + fBarThickness;
1322	} else {
1323		frame.left = floorf((frame.Width() - fBarThickness) / 2.0);
1324		frame.top = thumbInset;
1325		if (Label())
1326			frame.top += textHeight;
1327		if (fMaxLimitLabel) {
1328			frame.top += textHeight;
1329			if (Label())
1330				frame.top += leading;
1331		}
1332
1333		frame.right = frame.left + fBarThickness;
1334		frame.bottom = frame.bottom - thumbInset;
1335		if (fMinLimitLabel)
1336			frame.bottom -= textHeight;
1337		if (fUpdateText) {
1338			frame.bottom -= textHeight;
1339			if (fMinLimitLabel)
1340				frame.bottom -= leading;
1341		}
1342	}
1343
1344	return frame;
1345}
1346
1347
1348BRect
1349BSlider::HashMarksFrame() const
1350{
1351	BRect frame(BarFrame());
1352
1353	if (fOrientation == B_HORIZONTAL) {
1354		frame.top -= 6.0;
1355		frame.bottom += 6.0;
1356	} else {
1357		frame.left -= 6.0;
1358		frame.right += 6.0;
1359	}
1360
1361	return frame;
1362}
1363
1364
1365BRect
1366BSlider::ThumbFrame() const
1367{
1368	// TODO: The slider looks really ugly and broken when it is too little.
1369	// I would suggest using BarFrame() here to get the top and bottom coords
1370	// and spread them further apart for the thumb
1371
1372	BRect frame = Bounds();
1373
1374	font_height fontHeight;
1375	GetFontHeight(&fontHeight);
1376
1377	float textHeight = ceilf(fontHeight.ascent) + ceilf(fontHeight.descent);
1378
1379	if (fStyle == B_BLOCK_THUMB) {
1380		if (Orientation() == B_HORIZONTAL) {
1381			frame.left = floorf(Position() * (_MaxPosition()
1382				- _MinPosition()) + _MinPosition()) - 8;
1383			frame.top = 2 + (Label() || fUpdateText ? textHeight + 4 : 0);
1384			frame.right = frame.left + 17;
1385			frame.bottom = frame.top + fBarThickness + 7;
1386		} else {
1387			frame.left = floor((frame.Width() - fBarThickness) / 2) - 4;
1388			frame.top = floorf(Position() * (_MaxPosition()
1389				- _MinPosition()) + _MinPosition()) - 8;
1390			frame.right = frame.left + fBarThickness + 7;
1391			frame.bottom = frame.top + 17;
1392		}
1393	} else {
1394		if (Orientation() == B_HORIZONTAL) {
1395			frame.left = floorf(Position() * (_MaxPosition()
1396				- _MinPosition()) + _MinPosition()) - 6;
1397			frame.right = frame.left + 12;
1398			frame.top = 3 + fBarThickness + (Label() ? textHeight + 4 : 0);
1399			frame.bottom = frame.top + 8;
1400		} else {
1401			frame.left = floorf((frame.Width() + fBarThickness) / 2) - 3;
1402			frame.top = floorf(Position() * (_MaxPosition()
1403				- _MinPosition())) + _MinPosition() - 6;
1404			frame.right = frame.left + 8;
1405			frame.bottom = frame.top + 12;
1406		}
1407	}
1408
1409	return frame;
1410}
1411
1412
1413void
1414BSlider::SetFlags(uint32 flags)
1415{
1416	BControl::SetFlags(flags);
1417}
1418
1419
1420void
1421BSlider::SetResizingMode(uint32 mode)
1422{
1423	BControl::SetResizingMode(mode);
1424}
1425
1426
1427void
1428BSlider::GetPreferredSize(float* _width, float* _height)
1429{
1430	BSize preferredSize = PreferredSize();
1431
1432	if (Orientation() == B_HORIZONTAL) {
1433		if (_width != NULL) {
1434			// NOTE: For compatibility reasons, a horizontal BSlider
1435			// never shrinks horizontally. This only affects applications
1436			// which do not use the new layout system.
1437			*_width = max_c(Bounds().Width(), preferredSize.width);
1438		}
1439
1440		if (_height != NULL)
1441			*_height = preferredSize.height;
1442	} else {
1443		if (_width != NULL)
1444			*_width = preferredSize.width;
1445
1446		if (_height != NULL) {
1447			// NOTE: Similarly, a vertical BSlider never shrinks
1448			// vertically. This only affects applications which do not
1449			// use the new layout system.
1450			*_height = max_c(Bounds().Height(), preferredSize.height);
1451		}
1452	}
1453}
1454
1455
1456void
1457BSlider::ResizeToPreferred()
1458{
1459	BControl::ResizeToPreferred();
1460}
1461
1462
1463status_t
1464BSlider::Invoke(BMessage* message)
1465{
1466	return BControl::Invoke(message);
1467}
1468
1469
1470BHandler*
1471BSlider::ResolveSpecifier(BMessage* message, int32 index, BMessage* specifier,
1472	int32 command, const char *property)
1473{
1474	return BControl::ResolveSpecifier(message, index, specifier, command,
1475		property);
1476}
1477
1478
1479status_t
1480BSlider::GetSupportedSuites(BMessage* message)
1481{
1482	return BControl::GetSupportedSuites(message);
1483}
1484
1485
1486void
1487BSlider::SetModificationMessage(BMessage* message)
1488{
1489	delete fModificationMessage;
1490	fModificationMessage = message;
1491}
1492
1493
1494BMessage*
1495BSlider::ModificationMessage() const
1496{
1497	return fModificationMessage;
1498}
1499
1500
1501void
1502BSlider::SetSnoozeAmount(int32 snoozeTime)
1503{
1504	if (snoozeTime < 10000)
1505		snoozeTime = 10000;
1506	else if (snoozeTime > 1000000)
1507		snoozeTime = 1000000;
1508
1509	fSnoozeAmount = snoozeTime;
1510}
1511
1512
1513int32
1514BSlider::SnoozeAmount() const
1515{
1516	return fSnoozeAmount;
1517}
1518
1519
1520void
1521BSlider::SetKeyIncrementValue(int32 incrementValue)
1522{
1523	fKeyIncrementValue = incrementValue;
1524}
1525
1526
1527int32
1528BSlider::KeyIncrementValue() const
1529{
1530	return fKeyIncrementValue;
1531}
1532
1533
1534void
1535BSlider::SetHashMarkCount(int32 hashMarkCount)
1536{
1537	fHashMarkCount = hashMarkCount;
1538	Invalidate();
1539}
1540
1541
1542int32
1543BSlider::HashMarkCount() const
1544{
1545	return fHashMarkCount;
1546}
1547
1548
1549void
1550BSlider::SetHashMarks(hash_mark_location where)
1551{
1552	fHashMarks = where;
1553// TODO: enable if the hashmark look is influencing the control size!
1554//	InvalidateLayout();
1555	Invalidate();
1556}
1557
1558
1559hash_mark_location
1560BSlider::HashMarks() const
1561{
1562	return fHashMarks;
1563}
1564
1565
1566void
1567BSlider::SetStyle(thumb_style style)
1568{
1569	fStyle = style;
1570	InvalidateLayout();
1571	Invalidate();
1572}
1573
1574
1575thumb_style
1576BSlider::Style() const
1577{
1578	return fStyle;
1579}
1580
1581
1582void
1583BSlider::SetBarColor(rgb_color barColor)
1584{
1585	fBarColor = barColor;
1586	Invalidate(BarFrame());
1587}
1588
1589
1590rgb_color
1591BSlider::BarColor() const
1592{
1593	return fBarColor;
1594}
1595
1596
1597void
1598BSlider::UseFillColor(bool useFill, const rgb_color* barColor)
1599{
1600	fUseFillColor = useFill;
1601
1602	if (useFill && barColor)
1603		fFillColor = *barColor;
1604
1605	Invalidate(BarFrame());
1606}
1607
1608
1609bool
1610BSlider::FillColor(rgb_color* barColor) const
1611{
1612	if (barColor && fUseFillColor)
1613		*barColor = fFillColor;
1614
1615	return fUseFillColor;
1616}
1617
1618
1619BView*
1620BSlider::OffscreenView() const
1621{
1622#if USE_OFF_SCREEN_VIEW
1623	return fOffScreenView;
1624#else
1625	return (BView*)this;
1626#endif
1627}
1628
1629
1630orientation
1631BSlider::Orientation() const
1632{
1633	return fOrientation;
1634}
1635
1636
1637void
1638BSlider::SetOrientation(orientation posture)
1639{
1640	if (fOrientation == posture)
1641		return;
1642
1643	fOrientation = posture;
1644	InvalidateLayout();
1645	Invalidate();
1646}
1647
1648
1649float
1650BSlider::BarThickness() const
1651{
1652	return fBarThickness;
1653}
1654
1655
1656void
1657BSlider::SetBarThickness(float thickness)
1658{
1659	if (thickness < 1.0)
1660		thickness = 1.0;
1661	else
1662		thickness = roundf(thickness);
1663
1664	if (thickness != fBarThickness) {
1665		// calculate invalid barframe and extend by hashmark size
1666		float hInset = 0.0;
1667		float vInset = 0.0;
1668		if (fOrientation == B_HORIZONTAL)
1669			vInset = -6.0;
1670		else
1671			hInset = -6.0;
1672		BRect invalid = BarFrame().InsetByCopy(hInset, vInset) | ThumbFrame();
1673
1674		fBarThickness = thickness;
1675
1676		invalid = invalid | BarFrame().InsetByCopy(hInset, vInset)
1677			| ThumbFrame();
1678		Invalidate(invalid);
1679		InvalidateLayout();
1680	}
1681}
1682
1683
1684void
1685BSlider::SetFont(const BFont *font, uint32 properties)
1686{
1687	BControl::SetFont(font, properties);
1688
1689#if USE_OFF_SCREEN_VIEW
1690	if (fOffScreenView && fOffScreenBits) {
1691		if (fOffScreenBits->Lock()) {
1692			fOffScreenView->SetFont(font, properties);
1693			fOffScreenBits->Unlock();
1694		}
1695	}
1696#endif
1697
1698	InvalidateLayout();
1699}
1700
1701
1702void
1703BSlider::SetLimits(int32 minimum, int32 maximum)
1704{
1705	if (minimum <= maximum) {
1706		fMinValue = minimum;
1707		fMaxValue = maximum;
1708
1709		int32 value = Value();
1710		value = max_c(minimum, value);
1711		value = min_c(maximum, value);
1712
1713		if (value != Value()) {
1714			SetValue(value);
1715		}
1716	}
1717}
1718
1719
1720float
1721BSlider::MaxUpdateTextWidth()
1722{
1723	// very simplistic implementation that assumes the string will be widest
1724	// at the maximum value
1725	int32 value = Value();
1726	SetValueNoUpdate(fMaxValue);
1727	float width = StringWidth(UpdateText());
1728	SetValueNoUpdate(value);
1729	// in case the derived class uses a fixed buffer, the contents
1730	// should be reset for the old value
1731	UpdateText();
1732	return width;
1733}
1734
1735
1736// #pragma mark - layout related
1737
1738
1739BSize
1740BSlider::MinSize()
1741{
1742	return BLayoutUtils::ComposeSize(ExplicitMinSize(),
1743		_ValidateMinSize());
1744}
1745
1746
1747BSize
1748BSlider::MaxSize()
1749{
1750	BSize maxSize = _ValidateMinSize();
1751	if (fOrientation == B_HORIZONTAL)
1752		maxSize.width = B_SIZE_UNLIMITED;
1753	else
1754		maxSize.height = B_SIZE_UNLIMITED;
1755	return BLayoutUtils::ComposeSize(ExplicitMaxSize(), maxSize);
1756}
1757
1758
1759BSize
1760BSlider::PreferredSize()
1761{
1762	BSize preferredSize = _ValidateMinSize();
1763	if (fOrientation == B_HORIZONTAL)
1764		preferredSize.width = max_c(100.0, preferredSize.width);
1765	else
1766		preferredSize.height = max_c(100.0, preferredSize.height);
1767	return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), preferredSize);
1768}
1769
1770
1771void
1772BSlider::LayoutInvalidated(bool descendants)
1773{
1774	// invalidate cached preferred size
1775	fMinSize.Set(-1, -1);
1776}
1777
1778
1779// #pragma mark - private
1780
1781void
1782BSlider::_DrawBlockThumb()
1783{
1784	BRect frame = ThumbFrame();
1785	BView *view = OffscreenView();
1786
1787	if (be_control_look != NULL) {
1788		rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
1789		uint32 flags = be_control_look->Flags(this);
1790		be_control_look->DrawSliderThumb(view, frame, frame, base, flags,
1791			fOrientation);
1792		return;
1793	}
1794
1795	rgb_color no_tint = ui_color(B_PANEL_BACKGROUND_COLOR);
1796	rgb_color lighten2;
1797	rgb_color lighten1;
1798	rgb_color darken2;
1799	rgb_color darken3;
1800	rgb_color darkenmax;
1801
1802	if (IsEnabled()) {
1803		lighten2	= tint_color(no_tint, B_LIGHTEN_2_TINT);
1804		lighten1	= no_tint;
1805		darken2		= tint_color(no_tint, B_DARKEN_2_TINT);
1806		darken3		= tint_color(no_tint, B_DARKEN_3_TINT);
1807		darkenmax	= tint_color(no_tint, B_DARKEN_MAX_TINT);
1808	} else {
1809		lighten2	= tint_color(no_tint, B_LIGHTEN_2_TINT);
1810		lighten1	= tint_color(no_tint, B_LIGHTEN_1_TINT);
1811		darken2		= tint_color(no_tint, (B_NO_TINT + B_DARKEN_1_TINT) / 2.0);
1812		darken3		= tint_color(no_tint, B_DARKEN_1_TINT);
1813		darkenmax	= tint_color(no_tint, B_DARKEN_3_TINT);
1814	}
1815
1816	// blank background for shadow
1817	// ToDo: this also draws over the hash marks (though it's not *that* noticeable)
1818	view->SetHighColor(no_tint);
1819	view->StrokeLine(BPoint(frame.left, frame.top),
1820					 BPoint(frame.left, frame.top));
1821
1822	BRect barFrame = BarFrame();
1823	if (barFrame.right >= frame.right) {
1824		// leave out barFrame from shadow background clearing
1825		view->StrokeLine(BPoint(frame.right, frame.top),
1826						 BPoint(frame.right, barFrame.top - 1.0f));
1827		view->StrokeLine(BPoint(frame.right, barFrame.bottom + 1.0f),
1828						 BPoint(frame.right, frame.bottom));
1829	} else {
1830		view->StrokeLine(BPoint(frame.right, frame.top),
1831						 BPoint(frame.right, frame.bottom));
1832	}
1833
1834	view->StrokeLine(BPoint(frame.left, frame.bottom),
1835					 BPoint(frame.right - 1.0f, frame.bottom));
1836	view->StrokeLine(BPoint(frame.left, frame.bottom - 1.0f),
1837					 BPoint(frame.left, frame.bottom - 1.0f));
1838	view->StrokeLine(BPoint(frame.right - 1.0f, frame.top),
1839					 BPoint(frame.right - 1.0f, frame.top));
1840
1841	// Outline (top, left)
1842	view->SetHighColor(darken3);
1843	view->StrokeLine(BPoint(frame.left, frame.bottom - 2.0f),
1844					 BPoint(frame.left, frame.top + 1.0f));
1845	view->StrokeLine(BPoint(frame.left + 1.0f, frame.top),
1846					 BPoint(frame.right - 2.0f, frame.top));
1847
1848	// Shadow
1849	view->SetHighColor(0, 0, 0, IsEnabled() ? 100 : 50);
1850	view->SetDrawingMode(B_OP_ALPHA);
1851	view->StrokeLine(BPoint(frame.right, frame.top + 2.0f),
1852					 BPoint(frame.right, frame.bottom - 1.0f));
1853	view->StrokeLine(BPoint(frame.left + 2.0f, frame.bottom),
1854					 BPoint(frame.right - 1.0f, frame.bottom));
1855
1856	view->SetDrawingMode(B_OP_COPY);
1857	view->SetHighColor(darken3);
1858	view->StrokeLine(BPoint(frame.right - 1.0f, frame.bottom - 1.0f),
1859					 BPoint(frame.right - 1.0f, frame.bottom - 1.0f));
1860
1861
1862	// First bevel
1863	frame.InsetBy(1.0f, 1.0f);
1864
1865	view->SetHighColor(darkenmax);
1866	view->StrokeLine(BPoint(frame.left, frame.bottom),
1867					 BPoint(frame.right - 1.0f, frame.bottom));
1868	view->StrokeLine(BPoint(frame.right, frame.bottom - 1.0f),
1869					 BPoint(frame.right, frame.top));
1870
1871	view->SetHighColor(lighten2);
1872	view->StrokeLine(BPoint(frame.left, frame.top),
1873					 BPoint(frame.left, frame.bottom - 1.0f));
1874	view->StrokeLine(BPoint(frame.left + 1.0f, frame.top),
1875					 BPoint(frame.right - 1.0f, frame.top));
1876
1877	frame.InsetBy(1.0f, 1.0f);
1878
1879	view->FillRect(BRect(frame.left, frame.top, frame.right - 1.0f, frame.bottom - 1.0f));
1880
1881	// Second bevel and center dots
1882	view->SetHighColor(darken2);
1883	view->StrokeLine(BPoint(frame.left, frame.bottom),
1884					 BPoint(frame.right, frame.bottom));
1885	view->StrokeLine(BPoint(frame.right, frame.bottom - 1.0f),
1886					 BPoint(frame.right, frame.top));
1887
1888	if (Orientation() == B_HORIZONTAL) {
1889		view->StrokeLine(BPoint(frame.left + 6.0f, frame.top + 2.0f),
1890						 BPoint(frame.left + 6.0f, frame.top + 2.0f));
1891		view->StrokeLine(BPoint(frame.left + 6.0f, frame.top + 4.0f),
1892						 BPoint(frame.left + 6.0f, frame.top + 4.0f));
1893		view->StrokeLine(BPoint(frame.left + 6.0f, frame.top + 6.0f),
1894						 BPoint(frame.left + 6.0f, frame.top + 6.0f));
1895	} else {
1896		view->StrokeLine(BPoint(frame.left + 2.0f, frame.top + 6.0f),
1897						 BPoint(frame.left + 2.0f, frame.top + 6.0f));
1898		view->StrokeLine(BPoint(frame.left + 4.0f, frame.top + 6.0f),
1899						 BPoint(frame.left + 4.0f, frame.top + 6.0f));
1900		view->StrokeLine(BPoint(frame.left + 6.0f, frame.top + 6.0f),
1901						 BPoint(frame.left + 6.0f, frame.top + 6.0f));
1902	}
1903
1904	frame.InsetBy(1.0f, 1.0f);
1905
1906	// Third bevel
1907	view->SetHighColor(lighten1);
1908	view->StrokeLine(BPoint(frame.left, frame.bottom),
1909					 BPoint(frame.right, frame.bottom));
1910	view->StrokeLine(BPoint(frame.right, frame.bottom - 1.0f),
1911					 BPoint(frame.right, frame.top));
1912}
1913
1914
1915void
1916BSlider::_DrawTriangleThumb()
1917{
1918	BRect frame = ThumbFrame();
1919	BView *view = OffscreenView();
1920
1921	if (be_control_look != NULL) {
1922		rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
1923		uint32 flags = be_control_look->Flags(this);
1924		be_control_look->DrawSliderTriangle(view, frame, frame, base, flags,
1925			fOrientation);
1926		return;
1927	}
1928
1929	rgb_color no_tint = ui_color(B_PANEL_BACKGROUND_COLOR);
1930	rgb_color lightenmax;
1931	rgb_color lighten1;
1932	rgb_color darken2;
1933	rgb_color darken3;
1934	rgb_color darkenmax;
1935
1936	if (IsEnabled()) {
1937		lightenmax	= tint_color(no_tint, B_LIGHTEN_MAX_TINT);
1938		lighten1	= no_tint;
1939		darken2		= tint_color(no_tint, B_DARKEN_2_TINT);
1940		darken3		= tint_color(no_tint, B_DARKEN_3_TINT);
1941		darkenmax	= tint_color(no_tint, B_DARKEN_MAX_TINT);
1942	} else {
1943		lightenmax	= tint_color(no_tint, B_LIGHTEN_2_TINT);
1944		lighten1	= tint_color(no_tint, B_LIGHTEN_1_TINT);
1945		darken2		= tint_color(no_tint, (B_NO_TINT + B_DARKEN_1_TINT) / 2);
1946		darken3		= tint_color(no_tint, B_DARKEN_1_TINT);
1947		darkenmax	= tint_color(no_tint, B_DARKEN_3_TINT);
1948	}
1949
1950	if (Orientation() == B_HORIZONTAL) {
1951		view->SetHighColor(lighten1);
1952		view->FillTriangle(
1953			BPoint(frame.left + 1, frame.bottom - 3),
1954			BPoint((frame.left + frame.right) / 2, frame.top + 1),
1955			BPoint(frame.right - 1, frame.bottom - 3));
1956
1957		view->SetHighColor(no_tint);
1958		view->StrokeLine(BPoint(frame.right - 2, frame.bottom - 3),
1959			BPoint(frame.left + 3, frame.bottom - 3));
1960
1961		view->SetHighColor(darkenmax);
1962		view->StrokeLine(BPoint(frame.left, frame.bottom - 1),
1963			BPoint(frame.right, frame.bottom - 1));
1964		view->StrokeLine(BPoint(frame.right, frame.bottom - 2),
1965			BPoint((frame.left + frame.right) / 2, frame.top));
1966
1967		view->SetHighColor(darken2);
1968		view->StrokeLine(BPoint(frame.right - 1, frame.bottom - 2),
1969			BPoint(frame.left + 1, frame.bottom - 2));
1970		view->SetHighColor(darken3);
1971		view->StrokeLine(BPoint(frame.left, frame.bottom - 2),
1972			BPoint((frame.left + frame.right) / 2 - 1, frame.top + 1));
1973
1974		view->SetHighColor(lightenmax);
1975		view->StrokeLine(BPoint(frame.left + 2, frame.bottom - 3),
1976			BPoint((frame.left + frame.right) / 2, frame.top + 1));
1977
1978		// Shadow
1979		view->SetHighColor(0, 0, 0, IsEnabled() ? 80 : 40);
1980		view->SetDrawingMode(B_OP_ALPHA);
1981		view->StrokeLine(BPoint(frame.left + 1, frame.bottom),
1982			BPoint(frame.right, frame.bottom));
1983	} else {
1984		view->SetHighColor(lighten1);
1985		view->FillTriangle(
1986			BPoint(frame.left, (frame.top + frame.bottom) / 2),
1987			BPoint(frame.right - 1, frame.top + 1),
1988			BPoint(frame.right - 1, frame.bottom - 1));
1989
1990		view->SetHighColor(darkenmax);
1991		view->StrokeLine(BPoint(frame.right - 1, frame.top),
1992			BPoint(frame.right - 1, frame.bottom));
1993		view->StrokeLine(BPoint(frame.right - 1, frame.bottom),
1994			BPoint(frame.right - 2, frame.bottom));
1995
1996		view->SetHighColor(darken2);
1997		view->StrokeLine(BPoint(frame.right - 2, frame.top + 2),
1998			BPoint(frame.right - 2, frame.bottom - 1));
1999		view->StrokeLine(
2000			BPoint(frame.left, (frame.top + frame.bottom) / 2),
2001			BPoint(frame.right - 2, frame.top));
2002		view->SetHighColor(darken3);
2003		view->StrokeLine(
2004			BPoint(frame.left + 1, (frame.top + frame.bottom) / 2 + 1),
2005			BPoint(frame.right - 3, frame.bottom - 1));
2006
2007		view->SetHighColor(lightenmax);
2008		view->StrokeLine(
2009			BPoint(frame.left + 1, (frame.top + frame.bottom) / 2),
2010			BPoint(frame.right - 2, frame.top + 1));
2011
2012		// Shadow
2013		view->SetHighColor(0, 0, 0, IsEnabled() ? 80 : 40);
2014		view->SetDrawingMode(B_OP_ALPHA);
2015		view->StrokeLine(BPoint(frame.right, frame.top + 1),
2016			BPoint(frame.right, frame.bottom));
2017	}
2018
2019	view->SetDrawingMode(B_OP_COPY);
2020}
2021
2022
2023BPoint
2024BSlider::_Location() const
2025{
2026	return fLocation;
2027}
2028
2029
2030void
2031BSlider::_SetLocationForValue(int32 value)
2032{
2033	BPoint loc;
2034	float range = (float)(fMaxValue - fMinValue);
2035	if (range == 0)
2036		range = 1;
2037
2038	float pos = (float)(value - fMinValue) / range *
2039		(_MaxPosition() - _MinPosition());
2040
2041	if (fOrientation == B_HORIZONTAL) {
2042		loc.x = ceil(_MinPosition() + pos);
2043		loc.y = 0;
2044	} else {
2045		loc.x = 0;
2046		loc.y = floor(_MaxPosition() - pos);
2047	}
2048	fLocation = loc;
2049}
2050
2051
2052float
2053BSlider::_MinPosition() const
2054{
2055	if (fOrientation == B_HORIZONTAL)
2056		return BarFrame().left + 1.0f;
2057
2058	return BarFrame().bottom - 1.0f;
2059}
2060
2061
2062float
2063BSlider::_MaxPosition() const
2064{
2065	if (fOrientation == B_HORIZONTAL)
2066		return BarFrame().right - 1.0f;
2067
2068	return BarFrame().top + 1.0f;
2069}
2070
2071
2072BSize
2073BSlider::_ValidateMinSize()
2074{
2075	if (fMinSize.width >= 0) {
2076		// the preferred size is up to date
2077		return fMinSize;
2078	}
2079
2080	font_height fontHeight;
2081	GetFontHeight(&fontHeight);
2082
2083	float width = 0.0;
2084	float height = 0.0;
2085
2086	if (fMaxUpdateTextWidth < 0.0)
2087		fMaxUpdateTextWidth = MaxUpdateTextWidth();
2088
2089	if (Orientation() == B_HORIZONTAL) {
2090		height = 12.0 + fBarThickness;
2091		int32 rows = 0;
2092
2093		float labelWidth = 0;
2094		int32 labelRows = 0;
2095		float labelSpacing = StringWidth("M") * 2;
2096		if (Label()) {
2097			labelWidth = StringWidth(Label());
2098			labelRows = 1;
2099		}
2100		if (fMaxUpdateTextWidth > 0.0) {
2101			if (labelWidth > 0)
2102				labelWidth += labelSpacing;
2103			labelWidth += fMaxUpdateTextWidth;
2104			labelRows = 1;
2105		}
2106		rows += labelRows;
2107
2108		if (MinLimitLabel())
2109			width = StringWidth(MinLimitLabel());
2110		if (MaxLimitLabel()) {
2111			// some space between the labels
2112			if (MinLimitLabel())
2113				width += labelSpacing;
2114
2115			width += StringWidth(MaxLimitLabel());
2116		}
2117
2118		if (labelWidth > width)
2119			width = labelWidth;
2120		if (width < 32.0)
2121			width = 32.0;
2122
2123		if (MinLimitLabel() || MaxLimitLabel())
2124			rows++;
2125
2126		height += rows * (ceilf(fontHeight.ascent)
2127			+ ceilf(fontHeight.descent) + 4.0);
2128	} else {
2129		// B_VERTICAL
2130		width = 12.0 + fBarThickness;
2131		height = 32.0;
2132
2133		float lineHeightNoLeading = ceilf(fontHeight.ascent)
2134			+ ceilf(fontHeight.descent);
2135		float lineHeight = lineHeightNoLeading + ceilf(fontHeight.leading);
2136
2137		// find largest label
2138		float labelWidth = 0;
2139		if (Label()) {
2140			labelWidth = StringWidth(Label());
2141			height += lineHeightNoLeading;
2142		}
2143		if (MaxLimitLabel()) {
2144			labelWidth = max_c(labelWidth, StringWidth(MaxLimitLabel()));
2145			height += Label() ? lineHeight : lineHeightNoLeading;
2146		}
2147		if (MinLimitLabel()) {
2148			labelWidth = max_c(labelWidth, StringWidth(MinLimitLabel()));
2149			height += lineHeightNoLeading;
2150		}
2151		if (fMaxUpdateTextWidth > 0.0) {
2152			labelWidth = max_c(labelWidth, fMaxUpdateTextWidth);
2153			height += MinLimitLabel() ? lineHeight : lineHeightNoLeading;
2154		}
2155
2156		width = max_c(labelWidth, width);
2157	}
2158
2159	fMinSize.width = width;
2160	fMinSize.height = height;
2161
2162	ResetLayoutInvalidation();
2163
2164	return fMinSize;
2165}
2166
2167
2168// #pragma mark - FBC padding
2169
2170void BSlider::_ReservedSlider6() {}
2171void BSlider::_ReservedSlider7() {}
2172void BSlider::_ReservedSlider8() {}
2173void BSlider::_ReservedSlider9() {}
2174void BSlider::_ReservedSlider10() {}
2175void BSlider::_ReservedSlider11() {}
2176void BSlider::_ReservedSlider12() {}
2177
2178
2179BSlider &
2180BSlider::operator=(const BSlider &)
2181{
2182	return *this;
2183}
2184
2185
2186//	#pragma mark - BeOS compatibility
2187
2188
2189#if __GNUC__ < 3
2190
2191extern "C" void
2192GetLimits__7BSliderPlT1(BSlider* slider, int32* minimum, int32* maximum)
2193{
2194	slider->GetLimits(minimum, maximum);
2195}
2196
2197
2198extern "C" void
2199_ReservedSlider4__7BSlider(BSlider *slider, int32 minimum, int32 maximum)
2200{
2201	slider->BSlider::SetLimits(minimum, maximum);
2202}
2203
2204extern "C" float
2205_ReservedSlider5__7BSlider(BSlider *slider)
2206{
2207	return slider->BSlider::MaxUpdateTextWidth();
2208}
2209
2210
2211extern "C" void
2212_ReservedSlider1__7BSlider(BSlider* slider, orientation _orientation)
2213{
2214	slider->BSlider::SetOrientation(_orientation);
2215}
2216
2217
2218extern "C" void
2219_ReservedSlider2__7BSlider(BSlider* slider, float thickness)
2220{
2221	slider->BSlider::SetBarThickness(thickness);
2222}
2223
2224
2225extern "C" void
2226_ReservedSlider3__7BSlider(BSlider* slider, const BFont* font,
2227	uint32 properties)
2228{
2229	slider->BSlider::SetFont(font, properties);
2230}
2231
2232
2233#endif	// __GNUC__ < 3
2234
2235
2236extern "C" void
2237B_IF_GCC_2(InvalidateLayout__7BSliderb, _ZN7BSlider16InvalidateLayoutEb)(
2238	BView* view, bool descendants)
2239{
2240	perform_data_layout_invalidated data;
2241	data.descendants = descendants;
2242
2243	view->Perform(PERFORM_CODE_LAYOUT_INVALIDATED, &data);
2244}
2245
2246