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