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