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