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