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