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