1/*
2 * Copyright 2001-2014 Haiku, Inc. All rights reserved.
3 * Distributed under the terms of the MIT license.
4 *
5 * Authors:
6 *		Stephan A��mus, superstippi@gmx.de
7 *		Stefano Ceccherini, burton666@libero.it
8 *		DarkWyrm, bpmagic@columbus.rr.com
9 *		Marc Flerackers, mflerackers@androme.be
10 *		John Scipione, jscipione@gmail.com
11 */
12
13
14#include <ScrollBar.h>
15
16#include <math.h>
17#include <stdio.h>
18#include <stdlib.h>
19#include <string.h>
20
21#include <ControlLook.h>
22#include <LayoutUtils.h>
23#include <Message.h>
24#include <OS.h>
25#include <Shape.h>
26#include <Window.h>
27
28#include <binary_compatibility/Interface.h>
29
30
31//#define TRACE_SCROLLBAR
32#ifdef TRACE_SCROLLBAR
33#	define TRACE(x...) printf(x)
34#else
35#	define TRACE(x...)
36#endif
37
38
39typedef enum {
40	ARROW_LEFT = 0,
41	ARROW_RIGHT,
42	ARROW_UP,
43	ARROW_DOWN,
44	ARROW_NONE
45} arrow_direction;
46
47
48#define SBC_SCROLLBYVALUE	0
49#define SBC_SETDOUBLE		1
50#define SBC_SETPROPORTIONAL	2
51#define SBC_SETSTYLE		3
52
53// Quick constants for determining which arrow is down and are defined with
54// respect to double arrow mode. ARROW1 and ARROW4 refer to the outer pair of
55// arrows and ARROW2 and ARROW3 refer to the inner ones. ARROW1 points left/up
56// and ARROW4 points right/down.
57#define ARROW1	0
58#define ARROW2	1
59#define ARROW3	2
60#define ARROW4	3
61#define THUMB	4
62#define NOARROW	-1
63
64
65static const bigtime_t kRepeatDelay = 300000;
66
67
68// Because the R5 version kept a lot of data on server-side, we need to kludge
69// our way into binary compatibility
70class BScrollBar::Private {
71public:
72	Private(BScrollBar* scrollBar)
73	:
74	fScrollBar(scrollBar),
75	fEnabled(true),
76	fRepeaterThread(-1),
77	fExitRepeater(false),
78	fRepeaterDelay(0),
79	fThumbFrame(0.0, 0.0, -1.0, -1.0),
80	fDoRepeat(false),
81	fClickOffset(0.0, 0.0),
82	fThumbInc(0.0),
83	fStopValue(0.0),
84	fUpArrowsEnabled(true),
85	fDownArrowsEnabled(true),
86	fBorderHighlighted(false),
87	fButtonDown(NOARROW)
88	{
89#ifdef TEST_MODE
90			fScrollBarInfo.proportional = true;
91			fScrollBarInfo.double_arrows = true;
92			fScrollBarInfo.knob = 0;
93			fScrollBarInfo.min_knob_size = 15;
94#else
95			get_scroll_bar_info(&fScrollBarInfo);
96#endif
97	}
98
99	~Private()
100	{
101		if (fRepeaterThread >= 0) {
102			status_t dummy;
103			fExitRepeater = true;
104			wait_for_thread(fRepeaterThread, &dummy);
105		}
106	}
107
108	void DrawScrollBarButton(BScrollBar* owner, arrow_direction direction,
109		BRect frame, bool down = false);
110
111	static int32 button_repeater_thread(void* data);
112
113	int32 ButtonRepeaterThread();
114
115	BScrollBar*			fScrollBar;
116	bool				fEnabled;
117
118	// TODO: This should be a static, initialized by
119	// _init_interface_kit() at application startup-time,
120	// like BMenu::sMenuInfo
121	scroll_bar_info		fScrollBarInfo;
122
123	thread_id			fRepeaterThread;
124	volatile bool		fExitRepeater;
125	bigtime_t			fRepeaterDelay;
126
127	BRect				fThumbFrame;
128	volatile bool		fDoRepeat;
129	BPoint				fClickOffset;
130
131	float				fThumbInc;
132	float				fStopValue;
133
134	bool				fUpArrowsEnabled;
135	bool				fDownArrowsEnabled;
136
137	bool				fBorderHighlighted;
138
139	int8				fButtonDown;
140};
141
142
143// This thread is spawned when a button is initially pushed and repeatedly scrolls
144// the scrollbar by a little bit after a short delay
145int32
146BScrollBar::Private::button_repeater_thread(void* data)
147{
148	BScrollBar::Private* privateData = (BScrollBar::Private*)data;
149	return privateData->ButtonRepeaterThread();
150}
151
152
153int32
154BScrollBar::Private::ButtonRepeaterThread()
155{
156	// Wait a bit before auto scrolling starts. As long as the user releases
157	// and presses the button again while the repeat delay has not yet
158	// triggered, the value is pushed into the future, so we need to loop such
159	// that repeating starts at exactly the correct delay after the last
160	// button press.
161	while (fRepeaterDelay > system_time() && !fExitRepeater)
162		snooze_until(fRepeaterDelay, B_SYSTEM_TIMEBASE);
163
164	// repeat loop
165	while (!fExitRepeater) {
166		if (fScrollBar->LockLooper()) {
167			if (fDoRepeat) {
168				float value = fScrollBar->Value() + fThumbInc;
169				if (fButtonDown == NOARROW) {
170					// in this case we want to stop when we're under the mouse
171					if (fThumbInc > 0.0 && value <= fStopValue)
172						fScrollBar->SetValue(value);
173					if (fThumbInc < 0.0 && value >= fStopValue)
174						fScrollBar->SetValue(value);
175				} else
176					fScrollBar->SetValue(value);
177			}
178
179			fScrollBar->UnlockLooper();
180		}
181
182		snooze(25000);
183	}
184
185	// tell scrollbar we're gone
186	if (fScrollBar->LockLooper()) {
187		fRepeaterThread = -1;
188		fScrollBar->UnlockLooper();
189	}
190
191	return 0;
192}
193
194
195//	#pragma mark - BScrollBar
196
197
198BScrollBar::BScrollBar(BRect frame, const char* name, BView* target,
199	float min, float max, orientation direction)
200	:
201	BView(frame, name, B_FOLLOW_NONE,
202		B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE | B_FRAME_EVENTS),
203	fMin(min),
204	fMax(max),
205	fSmallStep(1.0f),
206	fLargeStep(10.0f),
207	fValue(0),
208	fProportion(0.0f),
209	fTarget(NULL),
210	fOrientation(direction)
211{
212	SetViewColor(B_TRANSPARENT_COLOR);
213
214	fPrivateData = new BScrollBar::Private(this);
215
216	SetTarget(target);
217	SetEventMask(B_NO_POINTER_HISTORY);
218
219	_UpdateThumbFrame();
220	_UpdateArrowButtons();
221
222	SetResizingMode(direction == B_VERTICAL
223		? B_FOLLOW_TOP_BOTTOM | B_FOLLOW_RIGHT
224		: B_FOLLOW_LEFT_RIGHT | B_FOLLOW_BOTTOM);
225}
226
227
228BScrollBar::BScrollBar(const char* name, BView* target,
229	float min, float max, orientation direction)
230	:
231	BView(name, B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE | B_FRAME_EVENTS),
232	fMin(min),
233	fMax(max),
234	fSmallStep(1.0f),
235	fLargeStep(10.0f),
236	fValue(0),
237	fProportion(0.0f),
238	fTarget(NULL),
239	fOrientation(direction)
240{
241	SetViewColor(B_TRANSPARENT_COLOR);
242
243	fPrivateData = new BScrollBar::Private(this);
244
245	SetTarget(target);
246	SetEventMask(B_NO_POINTER_HISTORY);
247
248	_UpdateThumbFrame();
249	_UpdateArrowButtons();
250}
251
252
253BScrollBar::BScrollBar(BMessage* data)
254	:
255	BView(data),
256	fTarget(NULL)
257{
258	fPrivateData = new BScrollBar::Private(this);
259
260	// TODO: Does the BeOS implementation try to find the target
261	// by name again? Does it archive the name at all?
262	if (data->FindFloat("_range", 0, &fMin) < B_OK)
263		fMin = 0.0f;
264
265	if (data->FindFloat("_range", 1, &fMax) < B_OK)
266		fMax = 0.0f;
267
268	if (data->FindFloat("_steps", 0, &fSmallStep) < B_OK)
269		fSmallStep = 1.0f;
270
271	if (data->FindFloat("_steps", 1, &fLargeStep) < B_OK)
272		fLargeStep = 10.0f;
273
274	if (data->FindFloat("_val", &fValue) < B_OK)
275		fValue = 0.0;
276
277	int32 orientation;
278	if (data->FindInt32("_orient", &orientation) < B_OK) {
279		fOrientation = B_VERTICAL;
280	} else
281		fOrientation = (enum orientation)orientation;
282
283	if ((Flags() & B_SUPPORTS_LAYOUT) == 0) {
284		// just to make sure
285		SetResizingMode(fOrientation == B_VERTICAL
286			? B_FOLLOW_TOP_BOTTOM | B_FOLLOW_RIGHT
287			: B_FOLLOW_LEFT_RIGHT | B_FOLLOW_BOTTOM);
288	}
289
290	if (data->FindFloat("_prop", &fProportion) < B_OK)
291		fProportion = 0.0;
292
293	_UpdateThumbFrame();
294	_UpdateArrowButtons();
295}
296
297
298BScrollBar::~BScrollBar()
299{
300	SetTarget((BView*)NULL);
301	delete fPrivateData;
302}
303
304
305BArchivable*
306BScrollBar::Instantiate(BMessage* data)
307{
308	if (validate_instantiation(data, "BScrollBar"))
309		return new BScrollBar(data);
310	return NULL;
311}
312
313
314status_t
315BScrollBar::Archive(BMessage* data, bool deep) const
316{
317	status_t err = BView::Archive(data, deep);
318	if (err != B_OK)
319		return err;
320
321	err = data->AddFloat("_range", fMin);
322	if (err != B_OK)
323		return err;
324
325	err = data->AddFloat("_range", fMax);
326	if (err != B_OK)
327		return err;
328
329	err = data->AddFloat("_steps", fSmallStep);
330	if (err != B_OK)
331		return err;
332
333	err = data->AddFloat("_steps", fLargeStep);
334	if (err != B_OK)
335		return err;
336
337	err = data->AddFloat("_val", fValue);
338	if (err != B_OK)
339		return err;
340
341	err = data->AddInt32("_orient", (int32)fOrientation);
342	if (err != B_OK)
343		return err;
344
345	err = data->AddFloat("_prop", fProportion);
346
347	return err;
348}
349
350
351void
352BScrollBar::AllAttached()
353{
354	BView::AllAttached();
355}
356
357
358void
359BScrollBar::AllDetached()
360{
361	BView::AllDetached();
362}
363
364
365void
366BScrollBar::AttachedToWindow()
367{
368	BView::AttachedToWindow();
369}
370
371
372void
373BScrollBar::DetachedFromWindow()
374{
375	BView::DetachedFromWindow();
376}
377
378
379void
380BScrollBar::Draw(BRect updateRect)
381{
382	BRect bounds = Bounds();
383
384	rgb_color normal = ui_color(B_PANEL_BACKGROUND_COLOR);
385
386	// stroke a dark frame around the entire scrollbar
387	// (independent of enabled state)
388	// take care of border highlighting (scroll target is focus view)
389	SetHighColor(tint_color(normal, B_DARKEN_2_TINT));
390	if (fPrivateData->fBorderHighlighted && fPrivateData->fEnabled) {
391		rgb_color borderColor = HighColor();
392		rgb_color highlightColor = ui_color(B_KEYBOARD_NAVIGATION_COLOR);
393		BeginLineArray(4);
394		AddLine(BPoint(bounds.left + 1, bounds.bottom),
395			BPoint(bounds.right, bounds.bottom), borderColor);
396		AddLine(BPoint(bounds.right, bounds.top + 1),
397			BPoint(bounds.right, bounds.bottom - 1), borderColor);
398		if (fOrientation == B_HORIZONTAL) {
399			AddLine(BPoint(bounds.left, bounds.top + 1),
400				BPoint(bounds.left, bounds.bottom), borderColor);
401		} else {
402			AddLine(BPoint(bounds.left, bounds.top),
403				BPoint(bounds.left, bounds.bottom), highlightColor);
404		}
405		if (fOrientation == B_HORIZONTAL) {
406			AddLine(BPoint(bounds.left, bounds.top),
407				BPoint(bounds.right, bounds.top), highlightColor);
408		} else {
409			AddLine(BPoint(bounds.left + 1, bounds.top),
410				BPoint(bounds.right, bounds.top), borderColor);
411		}
412		EndLineArray();
413	} else
414		StrokeRect(bounds);
415
416	bounds.InsetBy(1.0f, 1.0f);
417
418	bool enabled = fPrivateData->fEnabled && fMin < fMax
419		&& fProportion < 1.0f && fProportion >= 0.0f;
420
421	rgb_color light, dark, dark1, dark2;
422	if (enabled) {
423		light = tint_color(normal, B_LIGHTEN_MAX_TINT);
424		dark = tint_color(normal, B_DARKEN_3_TINT);
425		dark1 = tint_color(normal, B_DARKEN_1_TINT);
426		dark2 = tint_color(normal, B_DARKEN_2_TINT);
427	} else {
428		light = tint_color(normal, B_LIGHTEN_MAX_TINT);
429		dark = tint_color(normal, B_DARKEN_2_TINT);
430		dark1 = tint_color(normal, B_LIGHTEN_2_TINT);
431		dark2 = tint_color(normal, B_LIGHTEN_1_TINT);
432	}
433
434	SetDrawingMode(B_OP_OVER);
435
436	BRect thumbBG = bounds;
437	bool doubleArrows = _DoubleArrows();
438
439	// Draw arrows
440	if (fOrientation == B_HORIZONTAL) {
441		BRect buttonFrame(bounds.left, bounds.top,
442			bounds.left + bounds.Height(), bounds.bottom);
443
444		_DrawArrowButton(ARROW_LEFT, doubleArrows, buttonFrame, updateRect,
445			enabled, fPrivateData->fButtonDown == ARROW1);
446
447		if (doubleArrows) {
448			buttonFrame.OffsetBy(bounds.Height() + 1, 0.0f);
449			_DrawArrowButton(ARROW_RIGHT, doubleArrows, buttonFrame, updateRect,
450				enabled, fPrivateData->fButtonDown == ARROW2);
451
452			buttonFrame.OffsetTo(bounds.right - ((bounds.Height() * 2) + 1),
453				bounds.top);
454			_DrawArrowButton(ARROW_LEFT, doubleArrows, buttonFrame, updateRect,
455				enabled, fPrivateData->fButtonDown == ARROW3);
456
457			thumbBG.left += bounds.Height() * 2 + 2;
458			thumbBG.right -= bounds.Height() * 2 + 2;
459		} else {
460			thumbBG.left += bounds.Height() + 1;
461			thumbBG.right -= bounds.Height() + 1;
462		}
463
464		buttonFrame.OffsetTo(bounds.right - bounds.Height(), bounds.top);
465		_DrawArrowButton(ARROW_RIGHT, doubleArrows, buttonFrame, updateRect,
466			enabled, fPrivateData->fButtonDown == ARROW4);
467	} else {
468		BRect buttonFrame(bounds.left, bounds.top, bounds.right,
469			bounds.top + bounds.Width());
470
471		_DrawArrowButton(ARROW_UP, doubleArrows, buttonFrame, updateRect,
472			enabled, fPrivateData->fButtonDown == ARROW1);
473
474		if (doubleArrows) {
475			buttonFrame.OffsetBy(0.0f, bounds.Width() + 1);
476			_DrawArrowButton(ARROW_DOWN, doubleArrows, buttonFrame, updateRect,
477				enabled, fPrivateData->fButtonDown == ARROW2);
478
479			buttonFrame.OffsetTo(bounds.left, bounds.bottom
480				- ((bounds.Width() * 2) + 1));
481			_DrawArrowButton(ARROW_UP, doubleArrows, buttonFrame, updateRect,
482				enabled, fPrivateData->fButtonDown == ARROW3);
483
484			thumbBG.top += bounds.Width() * 2 + 2;
485			thumbBG.bottom -= bounds.Width() * 2 + 2;
486		} else {
487			thumbBG.top += bounds.Width() + 1;
488			thumbBG.bottom -= bounds.Width() + 1;
489		}
490
491		buttonFrame.OffsetTo(bounds.left, bounds.bottom - bounds.Width());
492		_DrawArrowButton(ARROW_DOWN, doubleArrows, buttonFrame, updateRect,
493			enabled, fPrivateData->fButtonDown == ARROW4);
494	}
495
496	SetDrawingMode(B_OP_COPY);
497
498	// background for thumb area
499	BRect rect(fPrivateData->fThumbFrame);
500
501	SetHighColor(dark1);
502
503	uint32 flags = 0;
504	if (!enabled)
505		flags |= BControlLook::B_DISABLED;
506
507	// fill background besides the thumb
508	if (fOrientation == B_HORIZONTAL) {
509		BRect leftOfThumb(thumbBG.left, thumbBG.top, rect.left - 1,
510			thumbBG.bottom);
511		BRect rightOfThumb(rect.right + 1, thumbBG.top, thumbBG.right,
512			thumbBG.bottom);
513
514		be_control_look->DrawScrollBarBackground(this, leftOfThumb,
515			rightOfThumb, updateRect, normal, flags, fOrientation);
516	} else {
517		BRect topOfThumb(thumbBG.left, thumbBG.top,
518			thumbBG.right, rect.top - 1);
519
520		BRect bottomOfThumb(thumbBG.left, rect.bottom + 1,
521			thumbBG.right, thumbBG.bottom);
522
523		be_control_look->DrawScrollBarBackground(this, topOfThumb,
524			bottomOfThumb, updateRect, normal, flags, fOrientation);
525	}
526
527	rgb_color thumbColor = ui_color(B_SCROLL_BAR_THUMB_COLOR);
528
529	// Draw scroll thumb
530	if (enabled) {
531		// fill the clickable surface of the thumb
532		be_control_look->DrawButtonBackground(this, rect, updateRect,
533			thumbColor, 0, BControlLook::B_ALL_BORDERS, fOrientation);
534		// TODO: Add the other thumb styles - dots and lines
535	} else {
536		if (fMin >= fMax || fProportion >= 1.0f || fProportion < 0.0f) {
537			// we cannot scroll at all
538			_DrawDisabledBackground(thumbBG, light, dark, dark1);
539		} else {
540			// we could scroll, but we're simply disabled
541			float bgTint = 1.06;
542			rgb_color bgLight = tint_color(light, bgTint * 3);
543			rgb_color bgShadow = tint_color(dark, bgTint);
544			rgb_color bgFill = tint_color(dark1, bgTint);
545			if (fOrientation == B_HORIZONTAL) {
546				// left of thumb
547				BRect besidesThumb(thumbBG);
548				besidesThumb.right = rect.left - 1;
549				_DrawDisabledBackground(besidesThumb, bgLight, bgShadow, bgFill);
550				// right of thumb
551				besidesThumb.left = rect.right + 1;
552				besidesThumb.right = thumbBG.right;
553				_DrawDisabledBackground(besidesThumb, bgLight, bgShadow, bgFill);
554			} else {
555				// above thumb
556				BRect besidesThumb(thumbBG);
557				besidesThumb.bottom = rect.top - 1;
558				_DrawDisabledBackground(besidesThumb, bgLight, bgShadow, bgFill);
559				// below thumb
560				besidesThumb.top = rect.bottom + 1;
561				besidesThumb.bottom = thumbBG.bottom;
562				_DrawDisabledBackground(besidesThumb, bgLight, bgShadow, bgFill);
563			}
564			// thumb bevel
565			BeginLineArray(4);
566				AddLine(BPoint(rect.left, rect.bottom),
567						BPoint(rect.left, rect.top), light);
568				AddLine(BPoint(rect.left + 1, rect.top),
569						BPoint(rect.right, rect.top), light);
570				AddLine(BPoint(rect.right, rect.top + 1),
571						BPoint(rect.right, rect.bottom), dark2);
572				AddLine(BPoint(rect.right - 1, rect.bottom),
573						BPoint(rect.left + 1, rect.bottom), dark2);
574			EndLineArray();
575			// thumb fill
576			rect.InsetBy(1.0, 1.0);
577			SetHighColor(dark1);
578			FillRect(rect);
579		}
580	}
581}
582
583
584void
585BScrollBar::FrameMoved(BPoint newPosition)
586{
587	BView::FrameMoved(newPosition);
588}
589
590
591void
592BScrollBar::FrameResized(float newWidth, float newHeight)
593{
594	_UpdateThumbFrame();
595}
596
597
598void
599BScrollBar::MessageReceived(BMessage* message)
600{
601	switch(message->what) {
602		case B_VALUE_CHANGED:
603		{
604			int32 value;
605			if (message->FindInt32("value", &value) == B_OK)
606				ValueChanged(value);
607
608			break;
609		}
610
611		case B_MOUSE_WHEEL_CHANGED:
612		{
613			// Must handle this here since BView checks for the existence of
614			// scrollbars, which a scrollbar itself does not have
615			float deltaX = 0.0f;
616			float deltaY = 0.0f;
617			message->FindFloat("be:wheel_delta_x", &deltaX);
618			message->FindFloat("be:wheel_delta_y", &deltaY);
619
620			if (deltaX == 0.0f && deltaY == 0.0f)
621				break;
622
623			if (deltaX != 0.0f && deltaY == 0.0f)
624				deltaY = deltaX;
625
626			ScrollWithMouseWheelDelta(this, deltaY);
627		}
628
629		default:
630			BView::MessageReceived(message);
631	}
632}
633
634
635void
636BScrollBar::MouseDown(BPoint where)
637{
638	if (!fPrivateData->fEnabled || fMin == fMax)
639		return;
640
641	SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS);
642
643	int32 buttons;
644	if (Looper() == NULL || Looper()->CurrentMessage() == NULL
645		|| Looper()->CurrentMessage()->FindInt32("buttons", &buttons) != B_OK) {
646		buttons = B_PRIMARY_MOUSE_BUTTON;
647	}
648
649	if (buttons & B_SECONDARY_MOUSE_BUTTON) {
650		// special absolute scrolling: move thumb to where we clicked
651		fPrivateData->fButtonDown = THUMB;
652		fPrivateData->fClickOffset = fPrivateData->fThumbFrame.LeftTop() - where;
653		if (Orientation() == B_HORIZONTAL)
654			fPrivateData->fClickOffset.x = -fPrivateData->fThumbFrame.Width() / 2;
655		else
656			fPrivateData->fClickOffset.y = -fPrivateData->fThumbFrame.Height() / 2;
657
658		SetValue(_ValueFor(where + fPrivateData->fClickOffset));
659		return;
660	}
661
662	// hit test for the thumb
663	if (fPrivateData->fThumbFrame.Contains(where)) {
664		fPrivateData->fButtonDown = THUMB;
665		fPrivateData->fClickOffset = fPrivateData->fThumbFrame.LeftTop() - where;
666		Invalidate(fPrivateData->fThumbFrame);
667		return;
668	}
669
670	// hit test for arrows or empty area
671	float scrollValue = 0.0;
672
673	// pressing the shift key scrolls faster
674	float buttonStepSize
675		= (modifiers() & B_SHIFT_KEY) != 0 ? fLargeStep : fSmallStep;
676
677	fPrivateData->fButtonDown = _ButtonFor(where);
678	switch (fPrivateData->fButtonDown) {
679		case ARROW1:
680			scrollValue = -buttonStepSize;
681			break;
682
683		case ARROW2:
684			scrollValue = buttonStepSize;
685			break;
686
687		case ARROW3:
688			scrollValue = -buttonStepSize;
689			break;
690
691		case ARROW4:
692			scrollValue = buttonStepSize;
693			break;
694
695		case NOARROW:
696			// we hit the empty area, figure out which side of the thumb
697			if (fOrientation == B_VERTICAL) {
698				if (where.y < fPrivateData->fThumbFrame.top)
699					scrollValue = -fLargeStep;
700				else
701					scrollValue = fLargeStep;
702			} else {
703				if (where.x < fPrivateData->fThumbFrame.left)
704					scrollValue = -fLargeStep;
705				else
706					scrollValue = fLargeStep;
707			}
708			_UpdateTargetValue(where);
709			break;
710	}
711	if (scrollValue != 0.0) {
712		SetValue(fValue + scrollValue);
713		Invalidate(_ButtonRectFor(fPrivateData->fButtonDown));
714
715		// launch the repeat thread
716		if (fPrivateData->fRepeaterThread == -1) {
717			fPrivateData->fExitRepeater = false;
718			fPrivateData->fRepeaterDelay = system_time() + kRepeatDelay;
719			fPrivateData->fThumbInc = scrollValue;
720			fPrivateData->fDoRepeat = true;
721			fPrivateData->fRepeaterThread = spawn_thread(
722				fPrivateData->button_repeater_thread, "scroll repeater",
723				B_NORMAL_PRIORITY, fPrivateData);
724			resume_thread(fPrivateData->fRepeaterThread);
725		} else {
726			fPrivateData->fExitRepeater = false;
727			fPrivateData->fRepeaterDelay = system_time() + kRepeatDelay;
728			fPrivateData->fDoRepeat = true;
729		}
730	}
731}
732
733
734void
735BScrollBar::MouseMoved(BPoint where, uint32 code, const BMessage* dragMessage)
736{
737	if (!fPrivateData->fEnabled || fMin >= fMax || fProportion >= 1.0f
738		|| fProportion < 0.0f) {
739		return;
740	}
741
742	if (fPrivateData->fButtonDown != NOARROW) {
743		if (fPrivateData->fButtonDown == THUMB) {
744			SetValue(_ValueFor(where + fPrivateData->fClickOffset));
745		} else {
746			// suspend the repeating if the mouse is not over the button
747			bool repeat = _ButtonRectFor(fPrivateData->fButtonDown).Contains(
748				where);
749			if (fPrivateData->fDoRepeat != repeat) {
750				fPrivateData->fDoRepeat = repeat;
751				Invalidate(_ButtonRectFor(fPrivateData->fButtonDown));
752			}
753		}
754	} else {
755		// update the value at which we want to stop repeating
756		if (fPrivateData->fDoRepeat) {
757			_UpdateTargetValue(where);
758			// we might have to turn arround
759			if ((fValue < fPrivateData->fStopValue
760					&& fPrivateData->fThumbInc < 0)
761				|| (fValue > fPrivateData->fStopValue
762					&& fPrivateData->fThumbInc > 0)) {
763				fPrivateData->fThumbInc = -fPrivateData->fThumbInc;
764			}
765		}
766	}
767}
768
769
770void
771BScrollBar::MouseUp(BPoint where)
772{
773	if (fPrivateData->fButtonDown == THUMB)
774		Invalidate(fPrivateData->fThumbFrame);
775	else
776		Invalidate(_ButtonRectFor(fPrivateData->fButtonDown));
777
778	fPrivateData->fButtonDown = NOARROW;
779	fPrivateData->fExitRepeater = true;
780	fPrivateData->fDoRepeat = false;
781}
782
783
784#if DISABLES_ON_WINDOW_DEACTIVATION
785void
786BScrollBar::WindowActivated(bool active)
787{
788	fPrivateData->fEnabled = active;
789	Invalidate();
790}
791#endif // DISABLES_ON_WINDOW_DEACTIVATION
792
793
794void
795BScrollBar::SetValue(float value)
796{
797	if (value > fMax)
798		value = fMax;
799	else if (value < fMin)
800		value = fMin;
801	else if (isnan(value) || isinf(value))
802		return;
803
804	value = roundf(value);
805	if (value == fValue)
806		return;
807
808	TRACE("BScrollBar(%s)::SetValue(%.1f)\n", Name(), value);
809
810	fValue = value;
811
812	_UpdateThumbFrame();
813	_UpdateArrowButtons();
814
815	ValueChanged(fValue);
816}
817
818
819float
820BScrollBar::Value() const
821{
822	return fValue;
823}
824
825
826void
827BScrollBar::ValueChanged(float newValue)
828{
829	TRACE("BScrollBar(%s)::ValueChanged(%.1f)\n", Name(), newValue);
830
831	if (fTarget != NULL) {
832		// cache target bounds
833		BRect targetBounds = fTarget->Bounds();
834		// if vertical, check bounds top and scroll if different from newValue
835		if (fOrientation == B_VERTICAL && targetBounds.top != newValue)
836			fTarget->ScrollBy(0.0, newValue - targetBounds.top);
837
838		// if horizontal, check bounds left and scroll if different from newValue
839		if (fOrientation == B_HORIZONTAL && targetBounds.left != newValue)
840			fTarget->ScrollBy(newValue - targetBounds.left, 0.0);
841	}
842
843	TRACE(" -> %.1f\n", newValue);
844
845	SetValue(newValue);
846}
847
848
849void
850BScrollBar::SetProportion(float value)
851{
852	if (value < 0.0f)
853		value = 0.0f;
854	else if (value > 1.0f)
855		value = 1.0f;
856
857	if (value == fProportion)
858		return;
859
860	TRACE("BScrollBar(%s)::SetProportion(%.1f)\n", Name(), value);
861
862	bool oldEnabled = fPrivateData->fEnabled && fMin < fMax
863		&& fProportion < 1.0f && fProportion >= 0.0f;
864
865	fProportion = value;
866
867	bool newEnabled = fPrivateData->fEnabled && fMin < fMax
868		&& fProportion < 1.0f && fProportion >= 0.0f;
869
870	_UpdateThumbFrame();
871
872	if (oldEnabled != newEnabled)
873		Invalidate();
874}
875
876
877float
878BScrollBar::Proportion() const
879{
880	return fProportion;
881}
882
883
884void
885BScrollBar::SetRange(float min, float max)
886{
887	if (min > max || isnanf(min) || isnanf(max)
888		|| isinff(min) || isinff(max)) {
889		min = 0.0f;
890		max = 0.0f;
891	}
892
893	min = roundf(min);
894	max = roundf(max);
895
896	if (fMin == min && fMax == max)
897		return;
898
899	TRACE("BScrollBar(%s)::SetRange(min=%.1f, max=%.1f)\n", Name(), min, max);
900
901	fMin = min;
902	fMax = max;
903
904	if (fValue < fMin || fValue > fMax)
905		SetValue(fValue);
906	else {
907		_UpdateThumbFrame();
908		Invalidate();
909	}
910}
911
912
913void
914BScrollBar::GetRange(float* min, float* max) const
915{
916	if (min != NULL)
917		*min = fMin;
918
919	if (max != NULL)
920		*max = fMax;
921}
922
923
924void
925BScrollBar::SetSteps(float smallStep, float largeStep)
926{
927	// Under R5, steps can be set only after being attached to a window,
928	// probably because the data is kept server-side. We'll just remove
929	// that limitation... :P
930
931	// The BeBook also says that we need to specify an integer value even
932	// though the step values are floats. For the moment, we'll just make
933	// sure that they are integers
934	smallStep = roundf(smallStep);
935	largeStep = roundf(largeStep);
936	if (fSmallStep == smallStep && fLargeStep == largeStep)
937		return;
938
939	TRACE("BScrollBar(%s)::SetSteps(small=%.1f, large=%.1f)\n", Name(),
940		smallStep, largeStep);
941
942	fSmallStep = smallStep;
943	fLargeStep = largeStep;
944
945	if (fProportion == 0.0) {
946		// special case, proportion is based on fLargeStep if it was never
947		// set, so it means we need to invalidate here
948		_UpdateThumbFrame();
949		Invalidate();
950	}
951
952	// TODO: test use of fractional values and make them work properly if
953	// they don't
954}
955
956
957void
958BScrollBar::GetSteps(float* smallStep, float* largeStep) const
959{
960	if (smallStep != NULL)
961		*smallStep = fSmallStep;
962
963	if (largeStep != NULL)
964		*largeStep = fLargeStep;
965}
966
967
968void
969BScrollBar::SetTarget(BView* target)
970{
971	if (fTarget) {
972		// unset the previous target's scrollbar pointer
973		if (fOrientation == B_VERTICAL)
974			fTarget->fVerScroller = NULL;
975		else
976			fTarget->fHorScroller = NULL;
977	}
978
979	fTarget = target;
980	if (fTarget) {
981		if (fOrientation == B_VERTICAL)
982			fTarget->fVerScroller = this;
983		else
984			fTarget->fHorScroller = this;
985	}
986}
987
988
989void
990BScrollBar::SetTarget(const char* targetName)
991{
992	// NOTE 1: BeOS implementation crashes for targetName == NULL
993	// NOTE 2: BeOS implementation also does not modify the target
994	// if it can't be found
995	if (targetName == NULL)
996		return;
997
998	if (Window() == NULL)
999		debugger("Method requires window and doesn't have one");
1000
1001	BView* target = Window()->FindView(targetName);
1002	if (target != NULL)
1003		SetTarget(target);
1004}
1005
1006
1007BView*
1008BScrollBar::Target() const
1009{
1010	return fTarget;
1011}
1012
1013
1014void
1015BScrollBar::SetOrientation(orientation direction)
1016{
1017	if (fOrientation == direction)
1018		return;
1019
1020	fOrientation = direction;
1021	InvalidateLayout();
1022	Invalidate();
1023}
1024
1025
1026orientation
1027BScrollBar::Orientation() const
1028{
1029	return fOrientation;
1030}
1031
1032
1033status_t
1034BScrollBar::SetBorderHighlighted(bool highlight)
1035{
1036	if (fPrivateData->fBorderHighlighted == highlight)
1037		return B_OK;
1038
1039	fPrivateData->fBorderHighlighted = highlight;
1040
1041	BRect dirty(Bounds());
1042	if (fOrientation == B_HORIZONTAL)
1043		dirty.bottom = dirty.top;
1044	else
1045		dirty.right = dirty.left;
1046
1047	Invalidate(dirty);
1048
1049	return B_OK;
1050}
1051
1052
1053void
1054BScrollBar::GetPreferredSize(float* _width, float* _height)
1055{
1056	if (fOrientation == B_VERTICAL) {
1057		if (_width)
1058			*_width = B_V_SCROLL_BAR_WIDTH;
1059
1060		if (_height)
1061			*_height = Bounds().Height();
1062	} else if (fOrientation == B_HORIZONTAL) {
1063		if (_width)
1064			*_width = Bounds().Width();
1065
1066		if (_height)
1067			*_height = B_H_SCROLL_BAR_HEIGHT;
1068	}
1069}
1070
1071
1072void
1073BScrollBar::ResizeToPreferred()
1074{
1075	BView::ResizeToPreferred();
1076}
1077
1078
1079
1080void
1081BScrollBar::MakeFocus(bool focus)
1082{
1083	BView::MakeFocus(focus);
1084}
1085
1086
1087BSize
1088BScrollBar::MinSize()
1089{
1090	return BLayoutUtils::ComposeSize(ExplicitMinSize(), _MinSize());
1091}
1092
1093
1094BSize
1095BScrollBar::MaxSize()
1096{
1097	BSize maxSize = _MinSize();
1098	if (fOrientation == B_HORIZONTAL)
1099		maxSize.width = B_SIZE_UNLIMITED;
1100	else
1101		maxSize.height = B_SIZE_UNLIMITED;
1102	return BLayoutUtils::ComposeSize(ExplicitMaxSize(), maxSize);
1103}
1104
1105
1106BSize
1107BScrollBar::PreferredSize()
1108{
1109	BSize preferredSize = _MinSize();
1110	if (fOrientation == B_HORIZONTAL)
1111		preferredSize.width *= 2;
1112	else
1113		preferredSize.height *= 2;
1114
1115	return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), preferredSize);
1116}
1117
1118
1119status_t
1120BScrollBar::GetSupportedSuites(BMessage* message)
1121{
1122	return BView::GetSupportedSuites(message);
1123}
1124
1125
1126BHandler*
1127BScrollBar::ResolveSpecifier(BMessage* message, int32 index,
1128	BMessage* specifier, int32 what, const char* property)
1129{
1130	return BView::ResolveSpecifier(message, index, specifier, what, property);
1131}
1132
1133
1134status_t
1135BScrollBar::Perform(perform_code code, void* _data)
1136{
1137	switch (code) {
1138		case PERFORM_CODE_MIN_SIZE:
1139			((perform_data_min_size*)_data)->return_value
1140				= BScrollBar::MinSize();
1141
1142			return B_OK;
1143
1144		case PERFORM_CODE_MAX_SIZE:
1145			((perform_data_max_size*)_data)->return_value
1146				= BScrollBar::MaxSize();
1147
1148			return B_OK;
1149
1150		case PERFORM_CODE_PREFERRED_SIZE:
1151			((perform_data_preferred_size*)_data)->return_value
1152				= BScrollBar::PreferredSize();
1153
1154			return B_OK;
1155
1156		case PERFORM_CODE_LAYOUT_ALIGNMENT:
1157			((perform_data_layout_alignment*)_data)->return_value
1158				= BScrollBar::LayoutAlignment();
1159
1160			return B_OK;
1161
1162		case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH:
1163			((perform_data_has_height_for_width*)_data)->return_value
1164				= BScrollBar::HasHeightForWidth();
1165
1166			return B_OK;
1167
1168		case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH:
1169		{
1170			perform_data_get_height_for_width* data
1171				= (perform_data_get_height_for_width*)_data;
1172			BScrollBar::GetHeightForWidth(data->width, &data->min, &data->max,
1173				&data->preferred);
1174
1175			return B_OK;
1176		}
1177
1178		case PERFORM_CODE_SET_LAYOUT:
1179		{
1180			perform_data_set_layout* data = (perform_data_set_layout*)_data;
1181			BScrollBar::SetLayout(data->layout);
1182
1183			return B_OK;
1184		}
1185
1186		case PERFORM_CODE_LAYOUT_INVALIDATED:
1187		{
1188			perform_data_layout_invalidated* data
1189				= (perform_data_layout_invalidated*)_data;
1190			BScrollBar::LayoutInvalidated(data->descendants);
1191
1192			return B_OK;
1193		}
1194
1195		case PERFORM_CODE_DO_LAYOUT:
1196		{
1197			BScrollBar::DoLayout();
1198
1199			return B_OK;
1200		}
1201	}
1202
1203	return BView::Perform(code, _data);
1204}
1205
1206
1207void BScrollBar::_ReservedScrollBar1() {}
1208void BScrollBar::_ReservedScrollBar2() {}
1209void BScrollBar::_ReservedScrollBar3() {}
1210void BScrollBar::_ReservedScrollBar4() {}
1211
1212
1213BScrollBar&
1214BScrollBar::operator=(const BScrollBar&)
1215{
1216	return *this;
1217}
1218
1219
1220bool
1221BScrollBar::_DoubleArrows() const
1222{
1223	if (!fPrivateData->fScrollBarInfo.double_arrows)
1224		return false;
1225
1226	// if there is not enough room, switch to single arrows even though
1227	// double arrows is specified
1228	if (fOrientation == B_HORIZONTAL) {
1229		return Bounds().Width() > (Bounds().Height() + 1) * 4
1230			+ fPrivateData->fScrollBarInfo.min_knob_size * 2;
1231	} else {
1232		return Bounds().Height() > (Bounds().Width() + 1) * 4
1233			+ fPrivateData->fScrollBarInfo.min_knob_size * 2;
1234	}
1235}
1236
1237
1238void
1239BScrollBar::_UpdateThumbFrame()
1240{
1241	BRect bounds = Bounds();
1242	bounds.InsetBy(1.0, 1.0);
1243
1244	BRect oldFrame = fPrivateData->fThumbFrame;
1245	fPrivateData->fThumbFrame = bounds;
1246	float minSize = fPrivateData->fScrollBarInfo.min_knob_size;
1247	float maxSize;
1248	float buttonSize;
1249
1250	// assume square buttons
1251	if (fOrientation == B_VERTICAL) {
1252		maxSize = bounds.Height();
1253		buttonSize = bounds.Width() + 1.0;
1254	} else {
1255		maxSize = bounds.Width();
1256		buttonSize = bounds.Height() + 1.0;
1257	}
1258
1259	if (_DoubleArrows()) {
1260		// subtract the size of four buttons
1261		maxSize -= buttonSize * 4;
1262	} else {
1263		// subtract the size of two buttons
1264		maxSize -= buttonSize * 2;
1265	}
1266	// visual adjustments (room for darker line between thumb and buttons)
1267	maxSize--;
1268
1269	float thumbSize;
1270	if (fPrivateData->fScrollBarInfo.proportional) {
1271		float proportion = fProportion;
1272		if (fMin >= fMax || proportion > 1.0 || proportion < 0.0)
1273			proportion = 1.0;
1274
1275		if (proportion == 0.0) {
1276			// Special case a proportion of 0.0, use the large step value
1277			// in that case (NOTE: fMin == fMax already handled above)
1278			// This calculation is based on the assumption that "large step"
1279			// scrolls by one "page size".
1280			proportion = fLargeStep / (2 * (fMax - fMin));
1281			if (proportion > 1.0)
1282				proportion = 1.0;
1283		}
1284		thumbSize = maxSize * proportion;
1285		if (thumbSize < minSize)
1286			thumbSize = minSize;
1287	} else
1288		thumbSize = minSize;
1289
1290	thumbSize = floorf(thumbSize + 0.5);
1291	thumbSize--;
1292
1293	// the thumb can be scrolled within the remaining area "maxSize - thumbSize - 1.0"
1294	float offset = 0.0;
1295	if (fMax > fMin) {
1296		offset = floorf(((fValue - fMin) / (fMax - fMin))
1297			* (maxSize - thumbSize - 1.0));
1298	}
1299
1300	if (_DoubleArrows()) {
1301		offset += buttonSize * 2;
1302	} else
1303		offset += buttonSize;
1304
1305	// visual adjustments (room for darker line between thumb and buttons)
1306	offset++;
1307
1308	if (fOrientation == B_VERTICAL) {
1309		fPrivateData->fThumbFrame.bottom = fPrivateData->fThumbFrame.top
1310			+ thumbSize;
1311		fPrivateData->fThumbFrame.OffsetBy(0.0, offset);
1312	} else {
1313		fPrivateData->fThumbFrame.right = fPrivateData->fThumbFrame.left
1314			+ thumbSize;
1315		fPrivateData->fThumbFrame.OffsetBy(offset, 0.0);
1316	}
1317
1318	if (Window() != NULL) {
1319		BRect invalid = oldFrame.IsValid()
1320			? oldFrame | fPrivateData->fThumbFrame
1321			: fPrivateData->fThumbFrame;
1322		// account for those two dark lines
1323		if (fOrientation == B_HORIZONTAL)
1324			invalid.InsetBy(-2.0, 0.0);
1325		else
1326			invalid.InsetBy(0.0, -2.0);
1327
1328		Invalidate(invalid);
1329	}
1330}
1331
1332
1333float
1334BScrollBar::_ValueFor(BPoint where) const
1335{
1336	BRect bounds = Bounds();
1337	bounds.InsetBy(1.0f, 1.0f);
1338
1339	float offset;
1340	float thumbSize;
1341	float maxSize;
1342	float buttonSize;
1343
1344	if (fOrientation == B_VERTICAL) {
1345		offset = where.y;
1346		thumbSize = fPrivateData->fThumbFrame.Height();
1347		maxSize = bounds.Height();
1348		buttonSize = bounds.Width() + 1.0f;
1349	} else {
1350		offset = where.x;
1351		thumbSize = fPrivateData->fThumbFrame.Width();
1352		maxSize = bounds.Width();
1353		buttonSize = bounds.Height() + 1.0f;
1354	}
1355
1356	if (_DoubleArrows()) {
1357		// subtract the size of four buttons
1358		maxSize -= buttonSize * 4;
1359		// convert point to inside of area between buttons
1360		offset -= buttonSize * 2;
1361	} else {
1362		// subtract the size of two buttons
1363		maxSize -= buttonSize * 2;
1364		// convert point to inside of area between buttons
1365		offset -= buttonSize;
1366	}
1367	// visual adjustments (room for darker line between thumb and buttons)
1368	maxSize--;
1369	offset++;
1370
1371	return roundf(fMin + (offset / (maxSize - thumbSize)
1372		* (fMax - fMin + 1.0f)));
1373}
1374
1375
1376int32
1377BScrollBar::_ButtonFor(BPoint where) const
1378{
1379	BRect bounds = Bounds();
1380	bounds.InsetBy(1.0f, 1.0f);
1381
1382	float buttonSize = fOrientation == B_VERTICAL
1383		? bounds.Width() + 1.0f
1384		: bounds.Height() + 1.0f;
1385
1386	BRect rect(bounds.left, bounds.top,
1387		bounds.left + buttonSize, bounds.top + buttonSize);
1388
1389	if (fOrientation == B_VERTICAL) {
1390		if (rect.Contains(where))
1391			return ARROW1;
1392
1393		if (_DoubleArrows()) {
1394			rect.OffsetBy(0.0, buttonSize);
1395			if (rect.Contains(where))
1396				return ARROW2;
1397
1398			rect.OffsetTo(bounds.left, bounds.bottom - 2 * buttonSize);
1399			if (rect.Contains(where))
1400				return ARROW3;
1401		}
1402		rect.OffsetTo(bounds.left, bounds.bottom - buttonSize);
1403		if (rect.Contains(where))
1404			return ARROW4;
1405	} else {
1406		if (rect.Contains(where))
1407			return ARROW1;
1408
1409		if (_DoubleArrows()) {
1410			rect.OffsetBy(buttonSize, 0.0);
1411			if (rect.Contains(where))
1412				return ARROW2;
1413
1414			rect.OffsetTo(bounds.right - 2 * buttonSize, bounds.top);
1415			if (rect.Contains(where))
1416				return ARROW3;
1417		}
1418		rect.OffsetTo(bounds.right - buttonSize, bounds.top);
1419		if (rect.Contains(where))
1420			return ARROW4;
1421	}
1422
1423	return NOARROW;
1424}
1425
1426
1427BRect
1428BScrollBar::_ButtonRectFor(int32 button) const
1429{
1430	BRect bounds = Bounds();
1431	bounds.InsetBy(1.0f, 1.0f);
1432
1433	float buttonSize = fOrientation == B_VERTICAL
1434		? bounds.Width() + 1.0f
1435		: bounds.Height() + 1.0f;
1436
1437	BRect rect(bounds.left, bounds.top,
1438		bounds.left + buttonSize - 1.0f, bounds.top + buttonSize - 1.0f);
1439
1440	if (fOrientation == B_VERTICAL) {
1441		switch (button) {
1442			case ARROW1:
1443				break;
1444
1445			case ARROW2:
1446				rect.OffsetBy(0.0, buttonSize);
1447				break;
1448
1449			case ARROW3:
1450				rect.OffsetTo(bounds.left, bounds.bottom - 2 * buttonSize + 1);
1451				break;
1452
1453			case ARROW4:
1454				rect.OffsetTo(bounds.left, bounds.bottom - buttonSize + 1);
1455				break;
1456		}
1457	} else {
1458		switch (button) {
1459			case ARROW1:
1460				break;
1461
1462			case ARROW2:
1463				rect.OffsetBy(buttonSize, 0.0);
1464				break;
1465
1466			case ARROW3:
1467				rect.OffsetTo(bounds.right - 2 * buttonSize + 1, bounds.top);
1468				break;
1469
1470			case ARROW4:
1471				rect.OffsetTo(bounds.right - buttonSize + 1, bounds.top);
1472				break;
1473		}
1474	}
1475
1476	return rect;
1477}
1478
1479
1480void
1481BScrollBar::_UpdateTargetValue(BPoint where)
1482{
1483	if (fOrientation == B_VERTICAL) {
1484		fPrivateData->fStopValue = _ValueFor(BPoint(where.x, where.y
1485			- fPrivateData->fThumbFrame.Height() / 2.0));
1486	} else {
1487		fPrivateData->fStopValue = _ValueFor(BPoint(where.x
1488			- fPrivateData->fThumbFrame.Width() / 2.0, where.y));
1489	}
1490}
1491
1492
1493void
1494BScrollBar::_UpdateArrowButtons()
1495{
1496	bool upEnabled = fValue > fMin;
1497	if (fPrivateData->fUpArrowsEnabled != upEnabled) {
1498		fPrivateData->fUpArrowsEnabled = upEnabled;
1499		Invalidate(_ButtonRectFor(ARROW1));
1500		if (_DoubleArrows())
1501			Invalidate(_ButtonRectFor(ARROW3));
1502	}
1503
1504	bool downEnabled = fValue < fMax;
1505	if (fPrivateData->fDownArrowsEnabled != downEnabled) {
1506		fPrivateData->fDownArrowsEnabled = downEnabled;
1507		Invalidate(_ButtonRectFor(ARROW4));
1508		if (_DoubleArrows())
1509			Invalidate(_ButtonRectFor(ARROW2));
1510	}
1511}
1512
1513
1514status_t
1515control_scrollbar(scroll_bar_info* info, BScrollBar* bar)
1516{
1517	if (bar == NULL || info == NULL)
1518		return B_BAD_VALUE;
1519
1520	if (bar->fPrivateData->fScrollBarInfo.double_arrows
1521			!= info->double_arrows) {
1522		bar->fPrivateData->fScrollBarInfo.double_arrows = info->double_arrows;
1523
1524		int8 multiplier = (info->double_arrows) ? 1 : -1;
1525
1526		if (bar->fOrientation == B_VERTICAL) {
1527			bar->fPrivateData->fThumbFrame.OffsetBy(0, multiplier
1528				* B_H_SCROLL_BAR_HEIGHT);
1529		} else {
1530			bar->fPrivateData->fThumbFrame.OffsetBy(multiplier
1531				* B_V_SCROLL_BAR_WIDTH, 0);
1532		}
1533	}
1534
1535	bar->fPrivateData->fScrollBarInfo.proportional = info->proportional;
1536
1537	// TODO: Figure out how proportional relates to the size of the thumb
1538
1539	// TODO: Add redraw code to reflect the changes
1540
1541	if (info->knob >= 0 && info->knob <= 2)
1542		bar->fPrivateData->fScrollBarInfo.knob = info->knob;
1543	else
1544		return B_BAD_VALUE;
1545
1546	if (info->min_knob_size >= SCROLL_BAR_MINIMUM_KNOB_SIZE
1547			&& info->min_knob_size <= SCROLL_BAR_MAXIMUM_KNOB_SIZE) {
1548		bar->fPrivateData->fScrollBarInfo.min_knob_size = info->min_knob_size;
1549	} else
1550		return B_BAD_VALUE;
1551
1552	return B_OK;
1553}
1554
1555
1556void
1557BScrollBar::_DrawDisabledBackground(BRect area, const rgb_color& light,
1558	const rgb_color& dark, const rgb_color& fill)
1559{
1560	if (!area.IsValid())
1561		return;
1562
1563	if (fOrientation == B_VERTICAL) {
1564		int32 height = area.IntegerHeight();
1565		if (height == 0) {
1566			SetHighColor(dark);
1567			StrokeLine(area.LeftTop(), area.RightTop());
1568		} else if (height == 1) {
1569			SetHighColor(dark);
1570			FillRect(area);
1571		} else {
1572			BeginLineArray(4);
1573				AddLine(BPoint(area.left, area.top),
1574						BPoint(area.right, area.top), dark);
1575				AddLine(BPoint(area.left, area.bottom - 1),
1576						BPoint(area.left, area.top + 1), light);
1577				AddLine(BPoint(area.left + 1, area.top + 1),
1578						BPoint(area.right, area.top + 1), light);
1579				AddLine(BPoint(area.right, area.bottom),
1580						BPoint(area.left, area.bottom), dark);
1581			EndLineArray();
1582			area.left++;
1583			area.top += 2;
1584			area.bottom--;
1585			if (area.IsValid()) {
1586				SetHighColor(fill);
1587				FillRect(area);
1588			}
1589		}
1590	} else {
1591		int32 width = area.IntegerWidth();
1592		if (width == 0) {
1593			SetHighColor(dark);
1594			StrokeLine(area.LeftBottom(), area.LeftTop());
1595		} else if (width == 1) {
1596			SetHighColor(dark);
1597			FillRect(area);
1598		} else {
1599			BeginLineArray(4);
1600				AddLine(BPoint(area.left, area.bottom),
1601						BPoint(area.left, area.top), dark);
1602				AddLine(BPoint(area.left + 1, area.bottom),
1603						BPoint(area.left + 1, area.top + 1), light);
1604				AddLine(BPoint(area.left + 1, area.top),
1605						BPoint(area.right - 1, area.top), light);
1606				AddLine(BPoint(area.right, area.top),
1607						BPoint(area.right, area.bottom), dark);
1608			EndLineArray();
1609			area.left += 2;
1610			area.top ++;
1611			area.right--;
1612			if (area.IsValid()) {
1613				SetHighColor(fill);
1614				FillRect(area);
1615			}
1616		}
1617	}
1618}
1619
1620
1621void
1622BScrollBar::_DrawArrowButton(int32 direction, bool doubleArrows, BRect rect,
1623	const BRect& updateRect, bool enabled, bool down)
1624{
1625	if (!updateRect.Intersects(rect))
1626		return;
1627
1628	uint32 flags = 0;
1629	if (!enabled)
1630		flags |= BControlLook::B_DISABLED;
1631
1632	if (down && fPrivateData->fDoRepeat)
1633		flags |= BControlLook::B_ACTIVATED;
1634
1635	// TODO: Why does BControlLook need this as the base color for the
1636	// scrollbar to look right?
1637	rgb_color baseColor = tint_color(ui_color(B_PANEL_BACKGROUND_COLOR),
1638		B_LIGHTEN_1_TINT);
1639
1640	be_control_look->DrawButtonBackground(this, rect, updateRect, baseColor,
1641		flags, BControlLook::B_ALL_BORDERS, fOrientation);
1642
1643	// TODO: Why does BControlLook need this negative inset for the arrow to
1644	// look right?
1645	rect.InsetBy(-1.0f, -1.0f);
1646	be_control_look->DrawArrowShape(this, rect, updateRect,
1647		baseColor, direction, flags, B_DARKEN_MAX_TINT);
1648}
1649
1650
1651BSize
1652BScrollBar::_MinSize() const
1653{
1654	BSize minSize;
1655	if (fOrientation == B_HORIZONTAL) {
1656		minSize.width = 2 * B_V_SCROLL_BAR_WIDTH
1657			+ 2 * fPrivateData->fScrollBarInfo.min_knob_size;
1658		minSize.height = B_H_SCROLL_BAR_HEIGHT;
1659	} else {
1660		minSize.width = B_V_SCROLL_BAR_WIDTH;
1661		minSize.height = 2 * B_H_SCROLL_BAR_HEIGHT
1662			+ 2 * fPrivateData->fScrollBarInfo.min_knob_size;
1663	}
1664
1665	return minSize;
1666}
1667