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