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