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