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