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