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