xref: /haiku/src/kits/interface/ScrollBar.cpp (revision 54391a5df206f617a4bf6e67fe5baf9c8b70a525)
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 
850 	bounds.InsetBy(1.0, 1.0);
851 
852 	bool enabled = fPrivateData->fEnabled && fMin < fMax
853 		&& fProportion < 1.0 && fProportion >= 0.0;
854 
855 	rgb_color light, light1, dark, dark1, dark2, dark4;
856 	if (enabled) {
857 		light = tint_color(normal, B_LIGHTEN_MAX_TINT);
858 		light1 = tint_color(normal, B_LIGHTEN_1_TINT);
859 		dark = tint_color(normal, B_DARKEN_3_TINT);
860 		dark1 = tint_color(normal, B_DARKEN_1_TINT);
861 		dark2 = tint_color(normal, B_DARKEN_2_TINT);
862 		dark4 = tint_color(normal, B_DARKEN_4_TINT);
863 	} else {
864 		light = tint_color(normal, B_LIGHTEN_MAX_TINT);
865 		light1 = normal;
866 		dark = tint_color(normal, B_DARKEN_2_TINT);
867 		dark1 = tint_color(normal, B_LIGHTEN_2_TINT);
868 		dark2 = tint_color(normal, B_LIGHTEN_1_TINT);
869 		dark4 = tint_color(normal, B_DARKEN_3_TINT);
870 	}
871 
872 	SetDrawingMode(B_OP_OVER);
873 
874 	BRect thumbBG = bounds;
875 	bool doubleArrows = _DoubleArrows();
876 
877 	// Draw arrows
878 	if (fOrientation == B_HORIZONTAL) {
879 		BRect buttonFrame(bounds.left, bounds.top,
880 			bounds.left + bounds.Height(), bounds.bottom);
881 
882 		_DrawArrowButton(ARROW_LEFT, doubleArrows, buttonFrame, updateRect,
883 			enabled, fPrivateData->fButtonDown == ARROW1);
884 
885 		if (doubleArrows) {
886 			buttonFrame.OffsetBy(bounds.Height() + 1, 0.0);
887 			_DrawArrowButton(ARROW_RIGHT, doubleArrows, buttonFrame, updateRect,
888 				enabled, fPrivateData->fButtonDown == ARROW2);
889 
890 			buttonFrame.OffsetTo(bounds.right - ((bounds.Height() * 2) + 1),
891 				bounds.top);
892 			_DrawArrowButton(ARROW_LEFT, doubleArrows, buttonFrame, updateRect,
893 				enabled, fPrivateData->fButtonDown == ARROW3);
894 
895 			thumbBG.left += bounds.Height() * 2 + 2;
896 			thumbBG.right -= bounds.Height() * 2 + 2;
897 		} else {
898 			thumbBG.left += bounds.Height() + 1;
899 			thumbBG.right -= bounds.Height() + 1;
900 		}
901 
902 		buttonFrame.OffsetTo(bounds.right - bounds.Height(), bounds.top);
903 		_DrawArrowButton(ARROW_RIGHT, doubleArrows, buttonFrame, updateRect,
904 			enabled, fPrivateData->fButtonDown == ARROW4);
905 	} else {
906 		BRect buttonFrame(bounds.left, bounds.top, bounds.right,
907 			bounds.top + bounds.Width());
908 
909 		_DrawArrowButton(ARROW_UP, doubleArrows, buttonFrame, updateRect,
910 			enabled, fPrivateData->fButtonDown == ARROW1);
911 
912 		if (doubleArrows) {
913 			buttonFrame.OffsetBy(0.0, bounds.Width() + 1);
914 			_DrawArrowButton(ARROW_DOWN, doubleArrows, buttonFrame, updateRect,
915 				enabled, fPrivateData->fButtonDown == ARROW2);
916 
917 			buttonFrame.OffsetTo(bounds.left, bounds.bottom
918 				- ((bounds.Width() * 2) + 1));
919 			_DrawArrowButton(ARROW_UP, doubleArrows, buttonFrame, updateRect,
920 				enabled, fPrivateData->fButtonDown == ARROW3);
921 
922 			thumbBG.top += bounds.Width() * 2 + 2;
923 			thumbBG.bottom -= bounds.Width() * 2 + 2;
924 		} else {
925 			thumbBG.top += bounds.Width() + 1;
926 			thumbBG.bottom -= bounds.Width() + 1;
927 		}
928 
929 		buttonFrame.OffsetTo(bounds.left, bounds.bottom - bounds.Width());
930 		_DrawArrowButton(ARROW_DOWN, doubleArrows, buttonFrame, updateRect,
931 			enabled, fPrivateData->fButtonDown == ARROW4);
932 	}
933 
934 	SetDrawingMode(B_OP_COPY);
935 
936 	// background for thumb area
937 	BRect rect(fPrivateData->fThumbFrame);
938 
939 	if (be_control_look == NULL) {
940 		if (fOrientation == B_HORIZONTAL) {
941 			BeginLineArray(8);
942 
943 			if (rect.left > thumbBG.left) {
944 				AddLine(BPoint(thumbBG.left, thumbBG.bottom),
945 						BPoint(thumbBG.left, thumbBG.top),
946 						rect.left > thumbBG.left + 1 ? dark4 : dark);
947 			}
948 			if (rect.left > thumbBG.left + 1) {
949 				AddLine(BPoint(thumbBG.left + 1, thumbBG.top + 1),
950 						BPoint(thumbBG.left + 1, thumbBG.bottom), dark2);
951 				AddLine(BPoint(thumbBG.left + 1, thumbBG.top),
952 						BPoint(rect.left - 1, thumbBG.top), dark2);
953 				AddLine(BPoint(rect.left - 1, thumbBG.bottom),
954 						BPoint(thumbBG.left + 2, thumbBG.bottom), normal);
955 			}
956 
957 			if (rect.right < thumbBG.right - 1) {
958 				AddLine(BPoint(rect.right + 2, thumbBG.top + 1),
959 						BPoint(rect.right + 2, thumbBG.bottom), dark2);
960 				AddLine(BPoint(rect.right + 1, thumbBG.top),
961 						BPoint(thumbBG.right, thumbBG.top), dark2);
962 				AddLine(BPoint(thumbBG.right - 1, thumbBG.bottom),
963 						BPoint(rect.right + 3, thumbBG.bottom), normal);
964 			}
965 			if (rect.right < thumbBG.right) {
966 				AddLine(BPoint(thumbBG.right, thumbBG.top),
967 						BPoint(thumbBG.right, thumbBG.bottom), dark);
968 			}
969 
970 			EndLineArray();
971 		} else {
972 			BeginLineArray(8);
973 
974 			if (rect.top > thumbBG.top) {
975 				AddLine(BPoint(thumbBG.left, thumbBG.top),
976 						BPoint(thumbBG.right, thumbBG.top),
977 						rect.top > thumbBG.top + 1 ? dark4 : dark);
978 			}
979 			if (rect.top > thumbBG.top + 1) {
980 				AddLine(BPoint(thumbBG.left + 1, thumbBG.top + 1),
981 						BPoint(thumbBG.right, thumbBG.top + 1), dark2);
982 				AddLine(BPoint(thumbBG.left, rect.top - 1),
983 						BPoint(thumbBG.left, thumbBG.top + 1), dark2);
984 				AddLine(BPoint(thumbBG.right, rect.top - 1),
985 						BPoint(thumbBG.right, thumbBG.top + 2), normal);
986 			}
987 
988 			if (rect.bottom < thumbBG.bottom - 1) {
989 				AddLine(BPoint(thumbBG.left + 1, rect.bottom + 2),
990 						BPoint(thumbBG.right, rect.bottom + 2), dark2);
991 				AddLine(BPoint(thumbBG.left, rect.bottom + 1),
992 						BPoint(thumbBG.left, thumbBG.bottom - 1), dark2);
993 				AddLine(BPoint(thumbBG.right, rect.bottom + 3),
994 						BPoint(thumbBG.right, thumbBG.bottom - 1), normal);
995 			}
996 			if (rect.bottom < thumbBG.bottom) {
997 				AddLine(BPoint(thumbBG.left, thumbBG.bottom),
998 						BPoint(thumbBG.right, thumbBG.bottom), dark);
999 			}
1000 
1001 			EndLineArray();
1002 		}
1003 	}
1004 	SetHighColor(dark1);
1005 
1006 	if (be_control_look != NULL) {
1007 		uint32 flags = 0;
1008 		if (!enabled)
1009 			flags |= BControlLook::B_DISABLED;
1010 
1011 		// fill background besides the thumb
1012 		if (fOrientation == B_HORIZONTAL) {
1013 			BRect leftOfThumb(thumbBG.left, thumbBG.top, rect.left - 1,
1014 				thumbBG.bottom);
1015 			BRect rightOfThumb(rect.right + 1, thumbBG.top, thumbBG.right,
1016 				thumbBG.bottom);
1017 
1018 			be_control_look->DrawScrollBarBackground(this, leftOfThumb,
1019 				rightOfThumb, updateRect, normal, flags, fOrientation);
1020 		} else {
1021 			BRect topOfThumb(thumbBG.left, thumbBG.top,
1022 				thumbBG.right, rect.top - 1);
1023 
1024 			BRect bottomOfThumb(thumbBG.left, rect.bottom + 1,
1025 				thumbBG.right, thumbBG.bottom);
1026 
1027 			be_control_look->DrawScrollBarBackground(this, topOfThumb,
1028 				bottomOfThumb, updateRect, normal, flags, fOrientation);
1029 		}
1030 	}
1031 
1032 	// Draw scroll thumb
1033 	if (enabled) {
1034 		if (be_control_look == NULL) {
1035 			// fill and additional dark lines
1036 			thumbBG.InsetBy(1.0, 1.0);
1037 			if (fOrientation == B_HORIZONTAL) {
1038 				BRect leftOfThumb(thumbBG.left + 1, thumbBG.top, rect.left - 1,
1039 					thumbBG.bottom);
1040 				if (leftOfThumb.IsValid())
1041 					FillRect(leftOfThumb);
1042 
1043 				BRect rightOfThumb(rect.right + 3, thumbBG.top, thumbBG.right,
1044 					thumbBG.bottom);
1045 				if (rightOfThumb.IsValid())
1046 					FillRect(rightOfThumb);
1047 
1048 				// dark lines before and after thumb
1049 				if (rect.left > thumbBG.left) {
1050 					SetHighColor(dark);
1051 					StrokeLine(BPoint(rect.left - 1, rect.top),
1052 						BPoint(rect.left - 1, rect.bottom));
1053 				}
1054 				if (rect.right < thumbBG.right) {
1055 					SetHighColor(dark4);
1056 					StrokeLine(BPoint(rect.right + 1, rect.top),
1057 						BPoint(rect.right + 1, rect.bottom));
1058 				}
1059 			} else {
1060 				BRect topOfThumb(thumbBG.left, thumbBG.top + 1,
1061 					thumbBG.right, rect.top - 1);
1062 				if (topOfThumb.IsValid())
1063 					FillRect(topOfThumb);
1064 
1065 				BRect bottomOfThumb(thumbBG.left, rect.bottom + 3,
1066 					thumbBG.right, thumbBG.bottom);
1067 				if (bottomOfThumb.IsValid())
1068 					FillRect(bottomOfThumb);
1069 
1070 				// dark lines before and after thumb
1071 				if (rect.top > thumbBG.top) {
1072 					SetHighColor(dark);
1073 					StrokeLine(BPoint(rect.left, rect.top - 1),
1074 						BPoint(rect.right, rect.top - 1));
1075 				}
1076 				if (rect.bottom < thumbBG.bottom) {
1077 					SetHighColor(dark4);
1078 					StrokeLine(BPoint(rect.left, rect.bottom + 1),
1079 						BPoint(rect.right, rect.bottom + 1));
1080 				}
1081 			}
1082 		}
1083 
1084 		// fill the clickable surface of the thumb
1085 		if (be_control_look != NULL) {
1086 			be_control_look->DrawButtonBackground(this, rect, updateRect,
1087 				normal, 0, BControlLook::B_ALL_BORDERS, fOrientation);
1088 		} else {
1089 			BeginLineArray(4);
1090 				AddLine(BPoint(rect.left, rect.bottom),
1091 						BPoint(rect.left, rect.top), light);
1092 				AddLine(BPoint(rect.left + 1, rect.top),
1093 						BPoint(rect.right, rect.top), light);
1094 				AddLine(BPoint(rect.right, rect.top + 1),
1095 						BPoint(rect.right, rect.bottom), dark1);
1096 				AddLine(BPoint(rect.right - 1, rect.bottom),
1097 						BPoint(rect.left + 1, rect.bottom), dark1);
1098 			EndLineArray();
1099 
1100 			// fill
1101 			rect.InsetBy(1.0, 1.0);
1102 			/*if (fPrivateData->fButtonDown == THUMB)
1103 				SetHighColor(tint_color(normal, (B_NO_TINT + B_DARKEN_1_TINT) / 2));
1104 			else*/
1105 				SetHighColor(normal);
1106 
1107 			FillRect(rect);
1108 		}
1109 		// TODO: Add the other thumb styles - dots and lines
1110 	} else {
1111 		if (fMin >= fMax || fProportion >= 1.0 || fProportion < 0.0) {
1112 			// we cannot scroll at all
1113 			_DrawDisabledBackground(thumbBG, light, dark, dark1);
1114 		} else {
1115 			// we could scroll, but we're simply disabled
1116 			float bgTint = 1.06;
1117 			rgb_color bgLight = tint_color(light, bgTint * 3);
1118 			rgb_color bgShadow = tint_color(dark, bgTint);
1119 			rgb_color bgFill = tint_color(dark1, bgTint);
1120 			if (fOrientation == B_HORIZONTAL) {
1121 				// left of thumb
1122 				BRect besidesThumb(thumbBG);
1123 				besidesThumb.right = rect.left - 1;
1124 				_DrawDisabledBackground(besidesThumb, bgLight, bgShadow, bgFill);
1125 				// right of thumb
1126 				besidesThumb.left = rect.right + 1;
1127 				besidesThumb.right = thumbBG.right;
1128 				_DrawDisabledBackground(besidesThumb, bgLight, bgShadow, bgFill);
1129 			} else {
1130 				// above thumb
1131 				BRect besidesThumb(thumbBG);
1132 				besidesThumb.bottom = rect.top - 1;
1133 				_DrawDisabledBackground(besidesThumb, bgLight, bgShadow, bgFill);
1134 				// below thumb
1135 				besidesThumb.top = rect.bottom + 1;
1136 				besidesThumb.bottom = thumbBG.bottom;
1137 				_DrawDisabledBackground(besidesThumb, bgLight, bgShadow, bgFill);
1138 			}
1139 			// thumb bevel
1140 			BeginLineArray(4);
1141 				AddLine(BPoint(rect.left, rect.bottom),
1142 						BPoint(rect.left, rect.top), light);
1143 				AddLine(BPoint(rect.left + 1, rect.top),
1144 						BPoint(rect.right, rect.top), light);
1145 				AddLine(BPoint(rect.right, rect.top + 1),
1146 						BPoint(rect.right, rect.bottom), dark2);
1147 				AddLine(BPoint(rect.right - 1, rect.bottom),
1148 						BPoint(rect.left + 1, rect.bottom), dark2);
1149 			EndLineArray();
1150 			// thumb fill
1151 			rect.InsetBy(1.0, 1.0);
1152 			SetHighColor(dark1);
1153 			FillRect(rect);
1154 		}
1155 	}
1156 
1157 	if (fPrivateData->fScrollBarInfo.knob == B_KNOB_STYLE_NONE)
1158 		return;
1159 
1160 	// draw the scrollbar thumb knobs
1161 	bool square = fPrivateData->fScrollBarInfo.knob == B_KNOB_STYLE_DOTS;
1162 	int32 knobWidth = 0;
1163 	int32 knobHeight = 0;
1164 
1165 	if (square) {
1166 		knobWidth = 2;
1167 		knobHeight = 2;
1168 	} else {
1169 		knobWidth = 1;
1170 		knobHeight = 3;
1171 	}
1172 
1173 	int32 flags = 0;
1174 	if (!enabled)
1175 		flags |= BControlLook::B_DISABLED;
1176 
1177 	float hmiddle = rect.Width() / 2;
1178 	float vmiddle = rect.Height() / 2;
1179 
1180 	BRect middleKnob = BRect(
1181 		rect.left + hmiddle
1182 			- (fOrientation == B_HORIZONTAL ? knobWidth : knobHeight),
1183 		rect.top + vmiddle
1184 			- (fOrientation == B_HORIZONTAL ? knobHeight : knobWidth),
1185 		rect.left + hmiddle
1186 			+ (fOrientation == B_HORIZONTAL ? knobWidth : knobHeight),
1187 		rect.top + vmiddle
1188 			+ (fOrientation == B_HORIZONTAL ? knobHeight : knobWidth));
1189 
1190 	if (fOrientation == B_HORIZONTAL) {
1191 		BRect leftKnob = middleKnob.OffsetByCopy(knobWidth * -4, 0);
1192 		if (leftKnob.left > rect.left + knobWidth) {
1193 			be_control_look->DrawButtonBackground(this, leftKnob, updateRect,
1194 				normal, flags, BControlLook::B_ALL_BORDERS, fOrientation);
1195 		}
1196 
1197 		BRect rightKnob = middleKnob.OffsetByCopy(knobWidth * 4, 0);
1198 		if (rightKnob.right < rect.right - knobWidth) {
1199 			be_control_look->DrawButtonBackground(this, rightKnob, updateRect,
1200 				normal, flags, BControlLook::B_ALL_BORDERS, fOrientation);
1201 		}
1202 	} else {
1203 		BRect topKnob = middleKnob.OffsetByCopy(0, knobWidth * -4);
1204 		if (topKnob.top > rect.top + knobHeight) {
1205 			be_control_look->DrawButtonBackground(this, topKnob, updateRect,
1206 				normal, flags, BControlLook::B_ALL_BORDERS, fOrientation);
1207 		}
1208 
1209 		BRect bottomKnob = middleKnob.OffsetByCopy(0, knobWidth * 4);
1210 		if (bottomKnob.bottom < rect.bottom - knobHeight) {
1211 			be_control_look->DrawButtonBackground(this, bottomKnob, updateRect,
1212 				normal, flags, BControlLook::B_ALL_BORDERS, fOrientation);
1213 		}
1214 	}
1215 
1216 	// draw middle knob last because it modifies middleKnob
1217 	be_control_look->DrawButtonBackground(this, middleKnob, updateRect,
1218 		normal, flags, BControlLook::B_ALL_BORDERS, fOrientation);
1219 }
1220 
1221 
1222 void
1223 BScrollBar::FrameMoved(BPoint newPosition)
1224 {
1225 	BView::FrameMoved(newPosition);
1226 }
1227 
1228 
1229 void
1230 BScrollBar::FrameResized(float newWidth, float newHeight)
1231 {
1232 	_UpdateThumbFrame();
1233 }
1234 
1235 
1236 BHandler*
1237 BScrollBar::ResolveSpecifier(BMessage* message, int32 index,
1238 	BMessage* specifier, int32 form, const char *property)
1239 {
1240 	return BView::ResolveSpecifier(message, index, specifier, form, property);
1241 }
1242 
1243 
1244 void
1245 BScrollBar::ResizeToPreferred()
1246 {
1247 	BView::ResizeToPreferred();
1248 }
1249 
1250 
1251 void
1252 BScrollBar::GetPreferredSize(float* _width, float* _height)
1253 {
1254 	if (fOrientation == B_VERTICAL) {
1255 		if (_width)
1256 			*_width = B_V_SCROLL_BAR_WIDTH;
1257 		if (_height)
1258 			*_height = Bounds().Height();
1259 	} else if (fOrientation == B_HORIZONTAL) {
1260 		if (_width)
1261 			*_width = Bounds().Width();
1262 		if (_height)
1263 			*_height = B_H_SCROLL_BAR_HEIGHT;
1264 	}
1265 }
1266 
1267 
1268 void
1269 BScrollBar::MakeFocus(bool state)
1270 {
1271 	BView::MakeFocus(state);
1272 }
1273 
1274 
1275 void
1276 BScrollBar::AllAttached()
1277 {
1278 	BView::AllAttached();
1279 }
1280 
1281 
1282 void
1283 BScrollBar::AllDetached()
1284 {
1285 	BView::AllDetached();
1286 }
1287 
1288 
1289 status_t
1290 BScrollBar::GetSupportedSuites(BMessage *message)
1291 {
1292 	return BView::GetSupportedSuites(message);
1293 }
1294 
1295 
1296 BSize
1297 BScrollBar::MinSize()
1298 {
1299 	return BLayoutUtils::ComposeSize(ExplicitMinSize(), _MinSize());
1300 }
1301 
1302 
1303 BSize
1304 BScrollBar::MaxSize()
1305 {
1306 	BSize maxSize = _MinSize();
1307 	if (fOrientation == B_HORIZONTAL)
1308 		maxSize.width = B_SIZE_UNLIMITED;
1309 	else
1310 		maxSize.height = B_SIZE_UNLIMITED;
1311 	return BLayoutUtils::ComposeSize(ExplicitMaxSize(), maxSize);
1312 }
1313 
1314 
1315 BSize
1316 BScrollBar::PreferredSize()
1317 {
1318 	BSize preferredSize = _MinSize();
1319 	if (fOrientation == B_HORIZONTAL)
1320 		preferredSize.width *= 2;
1321 	else
1322 		preferredSize.height *= 2;
1323 	return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), preferredSize);
1324 }
1325 
1326 
1327 status_t
1328 BScrollBar::Perform(perform_code code, void* _data)
1329 {
1330 	switch (code) {
1331 		case PERFORM_CODE_MIN_SIZE:
1332 			((perform_data_min_size*)_data)->return_value
1333 				= BScrollBar::MinSize();
1334 			return B_OK;
1335 		case PERFORM_CODE_MAX_SIZE:
1336 			((perform_data_max_size*)_data)->return_value
1337 				= BScrollBar::MaxSize();
1338 			return B_OK;
1339 		case PERFORM_CODE_PREFERRED_SIZE:
1340 			((perform_data_preferred_size*)_data)->return_value
1341 				= BScrollBar::PreferredSize();
1342 			return B_OK;
1343 		case PERFORM_CODE_LAYOUT_ALIGNMENT:
1344 			((perform_data_layout_alignment*)_data)->return_value
1345 				= BScrollBar::LayoutAlignment();
1346 			return B_OK;
1347 		case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH:
1348 			((perform_data_has_height_for_width*)_data)->return_value
1349 				= BScrollBar::HasHeightForWidth();
1350 			return B_OK;
1351 		case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH:
1352 		{
1353 			perform_data_get_height_for_width* data
1354 				= (perform_data_get_height_for_width*)_data;
1355 			BScrollBar::GetHeightForWidth(data->width, &data->min, &data->max,
1356 				&data->preferred);
1357 			return B_OK;
1358 		}
1359 		case PERFORM_CODE_SET_LAYOUT:
1360 		{
1361 			perform_data_set_layout* data = (perform_data_set_layout*)_data;
1362 			BScrollBar::SetLayout(data->layout);
1363 			return B_OK;
1364 		}
1365 		case PERFORM_CODE_LAYOUT_INVALIDATED:
1366 		{
1367 			perform_data_layout_invalidated* data
1368 				= (perform_data_layout_invalidated*)_data;
1369 			BScrollBar::LayoutInvalidated(data->descendants);
1370 			return B_OK;
1371 		}
1372 		case PERFORM_CODE_DO_LAYOUT:
1373 		{
1374 			BScrollBar::DoLayout();
1375 			return B_OK;
1376 		}
1377 	}
1378 
1379 	return BView::Perform(code, _data);
1380 }
1381 
1382 
1383 #if DISABLES_ON_WINDOW_DEACTIVATION
1384 void
1385 BScrollBar::WindowActivated(bool active)
1386 {
1387 	fPrivateData->fEnabled = active;
1388 	Invalidate();
1389 }
1390 #endif // DISABLES_ON_WINDOW_DEACTIVATION
1391 
1392 
1393 void BScrollBar::_ReservedScrollBar1() {}
1394 void BScrollBar::_ReservedScrollBar2() {}
1395 void BScrollBar::_ReservedScrollBar3() {}
1396 void BScrollBar::_ReservedScrollBar4() {}
1397 
1398 
1399 
1400 BScrollBar&
1401 BScrollBar::operator=(const BScrollBar&)
1402 {
1403 	return *this;
1404 }
1405 
1406 
1407 bool
1408 BScrollBar::_DoubleArrows() const
1409 {
1410 	if (!fPrivateData->fScrollBarInfo.double_arrows)
1411 		return false;
1412 
1413 	// if there is not enough room, switch to single arrows even though
1414 	// double arrows is specified
1415 	if (fOrientation == B_HORIZONTAL) {
1416 		return Bounds().Width() > (Bounds().Height() + 1) * 4
1417 			+ fPrivateData->fScrollBarInfo.min_knob_size * 2;
1418 	} else {
1419 		return Bounds().Height() > (Bounds().Width() + 1) * 4
1420 			+ fPrivateData->fScrollBarInfo.min_knob_size * 2;
1421 	}
1422 }
1423 
1424 
1425 void
1426 BScrollBar::_UpdateThumbFrame()
1427 {
1428 	BRect bounds = Bounds();
1429 	bounds.InsetBy(1.0, 1.0);
1430 
1431 	BRect oldFrame = fPrivateData->fThumbFrame;
1432 	fPrivateData->fThumbFrame = bounds;
1433 	float minSize = fPrivateData->fScrollBarInfo.min_knob_size;
1434 	float maxSize;
1435 	float buttonSize;
1436 
1437 	// assume square buttons
1438 	if (fOrientation == B_VERTICAL) {
1439 		maxSize = bounds.Height();
1440 		buttonSize = bounds.Width() + 1.0;
1441 	} else {
1442 		maxSize = bounds.Width();
1443 		buttonSize = bounds.Height() + 1.0;
1444 	}
1445 
1446 	if (_DoubleArrows()) {
1447 		// subtract the size of four buttons
1448 		maxSize -= buttonSize * 4;
1449 	} else {
1450 		// subtract the size of two buttons
1451 		maxSize -= buttonSize * 2;
1452 	}
1453 	// visual adjustments (room for darker line between thumb and buttons)
1454 	maxSize--;
1455 
1456 	float thumbSize;
1457 	if (fPrivateData->fScrollBarInfo.proportional) {
1458 		float proportion = fProportion;
1459 		if (fMin >= fMax || proportion > 1.0 || proportion < 0.0)
1460 			proportion = 1.0;
1461 		if (proportion == 0.0) {
1462 			// Special case a proportion of 0.0, use the large step value
1463 			// in that case (NOTE: fMin == fMax already handled above)
1464 			// This calculation is based on the assumption that "large step"
1465 			// scrolls by one "page size".
1466 			proportion = fLargeStep / (2 * (fMax - fMin));
1467 			if (proportion > 1.0)
1468 				proportion = 1.0;
1469 		}
1470 		thumbSize = maxSize * proportion;
1471 		if (thumbSize < minSize)
1472 			thumbSize = minSize;
1473 	} else
1474 		thumbSize = minSize;
1475 
1476 	thumbSize = floorf(thumbSize + 0.5);
1477 	thumbSize--;
1478 
1479 	// the thumb can be scrolled within the remaining area "maxSize - thumbSize - 1.0"
1480 	float offset = 0.0;
1481 	if (fMax > fMin) {
1482 		offset = floorf(((fValue - fMin) / (fMax - fMin))
1483 			* (maxSize - thumbSize - 1.0));
1484 	}
1485 
1486 	if (_DoubleArrows()) {
1487 		offset += buttonSize * 2;
1488 	} else {
1489 		offset += buttonSize;
1490 	}
1491 	// visual adjustments (room for darker line between thumb and buttons)
1492 	offset++;
1493 
1494 	if (fOrientation == B_VERTICAL) {
1495 		fPrivateData->fThumbFrame.bottom = fPrivateData->fThumbFrame.top
1496 			+ thumbSize;
1497 		fPrivateData->fThumbFrame.OffsetBy(0.0, offset);
1498 	} else {
1499 		fPrivateData->fThumbFrame.right = fPrivateData->fThumbFrame.left
1500 			+ thumbSize;
1501 		fPrivateData->fThumbFrame.OffsetBy(offset, 0.0);
1502 	}
1503 
1504 	if (Window()) {
1505 		BRect invalid = oldFrame.IsValid() ?
1506 			oldFrame | fPrivateData->fThumbFrame
1507 			: fPrivateData->fThumbFrame;
1508 		// account for those two dark lines
1509 		if (fOrientation == B_HORIZONTAL)
1510 			invalid.InsetBy(-2.0, 0.0);
1511 		else
1512 			invalid.InsetBy(0.0, -2.0);
1513 		Invalidate(invalid);
1514 	}
1515 }
1516 
1517 
1518 float
1519 BScrollBar::_ValueFor(BPoint where) const
1520 {
1521 	BRect bounds = Bounds();
1522 	bounds.InsetBy(1.0, 1.0);
1523 
1524 	float offset;
1525 	float thumbSize;
1526 	float maxSize;
1527 	float buttonSize;
1528 
1529 	if (fOrientation == B_VERTICAL) {
1530 		offset = where.y;
1531 		thumbSize = fPrivateData->fThumbFrame.Height();
1532 		maxSize = bounds.Height();
1533 		buttonSize = bounds.Width() + 1.0;
1534 	} else {
1535 		offset = where.x;
1536 		thumbSize = fPrivateData->fThumbFrame.Width();
1537 		maxSize = bounds.Width();
1538 		buttonSize = bounds.Height() + 1.0;
1539 	}
1540 
1541 	if (_DoubleArrows()) {
1542 		// subtract the size of four buttons
1543 		maxSize -= buttonSize * 4;
1544 		// convert point to inside of area between buttons
1545 		offset -= buttonSize * 2;
1546 	} else {
1547 		// subtract the size of two buttons
1548 		maxSize -= buttonSize * 2;
1549 		// convert point to inside of area between buttons
1550 		offset -= buttonSize;
1551 	}
1552 	// visual adjustments (room for darker line between thumb and buttons)
1553 	maxSize--;
1554 	offset++;
1555 
1556 	float value = fMin + (offset / (maxSize - thumbSize) * (fMax - fMin + 1.0));
1557 	if (value >= 0.0)
1558 		return floorf(value + 0.5);
1559 	else
1560 		return ceilf(value - 0.5);
1561 }
1562 
1563 
1564 int32
1565 BScrollBar::_ButtonFor(BPoint where) const
1566 {
1567 	BRect bounds = Bounds();
1568 	bounds.InsetBy(1.0, 1.0);
1569 
1570 	float buttonSize;
1571 	if (fOrientation == B_VERTICAL) {
1572 		buttonSize = bounds.Width() + 1.0;
1573 	} else {
1574 		buttonSize = bounds.Height() + 1.0;
1575 	}
1576 
1577 	BRect rect(bounds.left, bounds.top,
1578 			   bounds.left + buttonSize, bounds.top + buttonSize);
1579 
1580 	if (fOrientation == B_VERTICAL) {
1581 		if (rect.Contains(where))
1582 			return ARROW1;
1583 		if (_DoubleArrows()) {
1584 			rect.OffsetBy(0.0, buttonSize);
1585 			if (rect.Contains(where))
1586 				return ARROW2;
1587 			rect.OffsetTo(bounds.left, bounds.bottom - 2 * buttonSize);
1588 			if (rect.Contains(where))
1589 				return ARROW3;
1590 		}
1591 		rect.OffsetTo(bounds.left, bounds.bottom - buttonSize);
1592 		if (rect.Contains(where))
1593 			return ARROW4;
1594 	} else {
1595 		if (rect.Contains(where))
1596 			return ARROW1;
1597 		if (_DoubleArrows()) {
1598 			rect.OffsetBy(buttonSize, 0.0);
1599 			if (rect.Contains(where))
1600 				return ARROW2;
1601 			rect.OffsetTo(bounds.right - 2 * buttonSize, bounds.top);
1602 			if (rect.Contains(where))
1603 				return ARROW3;
1604 		}
1605 		rect.OffsetTo(bounds.right - buttonSize, bounds.top);
1606 		if (rect.Contains(where))
1607 			return ARROW4;
1608 	}
1609 
1610 	return NOARROW;
1611 }
1612 
1613 
1614 BRect
1615 BScrollBar::_ButtonRectFor(int32 button) const
1616 {
1617 	BRect bounds = Bounds();
1618 	bounds.InsetBy(1.0, 1.0);
1619 
1620 	float buttonSize;
1621 	if (fOrientation == B_VERTICAL) {
1622 		buttonSize = bounds.Width() + 1.0;
1623 	} else {
1624 		buttonSize = bounds.Height() + 1.0;
1625 	}
1626 
1627 	BRect rect(bounds.left, bounds.top,
1628 			   bounds.left + buttonSize - 1.0, bounds.top + buttonSize - 1.0);
1629 
1630 	if (fOrientation == B_VERTICAL) {
1631 		switch (button) {
1632 			case ARROW1:
1633 				break;
1634 			case ARROW2:
1635 				rect.OffsetBy(0.0, buttonSize);
1636 				break;
1637 			case ARROW3:
1638 				rect.OffsetTo(bounds.left, bounds.bottom - 2 * buttonSize + 1);
1639 				break;
1640 			case ARROW4:
1641 				rect.OffsetTo(bounds.left, bounds.bottom - buttonSize + 1);
1642 				break;
1643 		}
1644 	} else {
1645 		switch (button) {
1646 			case ARROW1:
1647 				break;
1648 			case ARROW2:
1649 				rect.OffsetBy(buttonSize, 0.0);
1650 				break;
1651 			case ARROW3:
1652 				rect.OffsetTo(bounds.right - 2 * buttonSize + 1, bounds.top);
1653 				break;
1654 			case ARROW4:
1655 				rect.OffsetTo(bounds.right - buttonSize + 1, bounds.top);
1656 				break;
1657 		}
1658 	}
1659 
1660 	return rect;
1661 }
1662 
1663 
1664 void
1665 BScrollBar::_UpdateTargetValue(BPoint where)
1666 {
1667 	if (fOrientation == B_VERTICAL) {
1668 		fPrivateData->fStopValue = _ValueFor(BPoint(where.x, where.y
1669 			- fPrivateData->fThumbFrame.Height() / 2.0));
1670 	} else {
1671 		fPrivateData->fStopValue = _ValueFor(BPoint(where.x
1672 			- fPrivateData->fThumbFrame.Width() / 2.0, where.y));
1673 	}
1674 }
1675 
1676 
1677 void
1678 BScrollBar::_UpdateArrowButtons()
1679 {
1680 	bool upEnabled = fValue > fMin;
1681 	if (fPrivateData->fUpArrowsEnabled != upEnabled) {
1682 		fPrivateData->fUpArrowsEnabled = upEnabled;
1683 		Invalidate(_ButtonRectFor(ARROW1));
1684 		if (_DoubleArrows())
1685 			Invalidate(_ButtonRectFor(ARROW3));
1686 	}
1687 
1688 	bool downEnabled = fValue < fMax;
1689 	if (fPrivateData->fDownArrowsEnabled != downEnabled) {
1690 		fPrivateData->fDownArrowsEnabled = downEnabled;
1691 		Invalidate(_ButtonRectFor(ARROW4));
1692 		if (_DoubleArrows())
1693 			Invalidate(_ButtonRectFor(ARROW2));
1694 	}
1695 }
1696 
1697 
1698 status_t
1699 control_scrollbar(scroll_bar_info *info, BScrollBar *bar)
1700 {
1701 	if (!bar || !info)
1702 		return B_BAD_VALUE;
1703 
1704 	if (bar->fPrivateData->fScrollBarInfo.double_arrows
1705 		!= info->double_arrows) {
1706 		bar->fPrivateData->fScrollBarInfo.double_arrows = info->double_arrows;
1707 
1708 		int8 multiplier = (info->double_arrows) ? 1 : -1;
1709 
1710 		if (bar->fOrientation == B_VERTICAL) {
1711 			bar->fPrivateData->fThumbFrame.OffsetBy(0, multiplier
1712 				* B_H_SCROLL_BAR_HEIGHT);
1713 		} else {
1714 			bar->fPrivateData->fThumbFrame.OffsetBy(multiplier
1715 				* B_V_SCROLL_BAR_WIDTH, 0);
1716 		}
1717 	}
1718 
1719 	bar->fPrivateData->fScrollBarInfo.proportional = info->proportional;
1720 
1721 	// TODO: Figure out how proportional relates to the size of the thumb
1722 
1723 	// TODO: Add redraw code to reflect the changes
1724 
1725 	if (info->knob >= 0 && info->knob <= 2)
1726 		bar->fPrivateData->fScrollBarInfo.knob = info->knob;
1727 	else
1728 		return B_BAD_VALUE;
1729 
1730 	if (info->min_knob_size >= SCROLL_BAR_MINIMUM_KNOB_SIZE
1731 			&& info->min_knob_size <= SCROLL_BAR_MAXIMUM_KNOB_SIZE)
1732 		bar->fPrivateData->fScrollBarInfo.min_knob_size = info->min_knob_size;
1733 	else
1734 		return B_BAD_VALUE;
1735 
1736 	return B_OK;
1737 }
1738 
1739 
1740 void
1741 BScrollBar::_DrawDisabledBackground(BRect area,
1742 									const rgb_color& light,
1743 									const rgb_color& dark,
1744 									const rgb_color& fill)
1745 {
1746 	if (!area.IsValid())
1747 		return;
1748 
1749 	if (fOrientation == B_VERTICAL) {
1750 		int32 height = area.IntegerHeight();
1751 		if (height == 0) {
1752 			SetHighColor(dark);
1753 			StrokeLine(area.LeftTop(), area.RightTop());
1754 		} else if (height == 1) {
1755 			SetHighColor(dark);
1756 			FillRect(area);
1757 		} else {
1758 			BeginLineArray(4);
1759 				AddLine(BPoint(area.left, area.top),
1760 						BPoint(area.right, area.top), dark);
1761 				AddLine(BPoint(area.left, area.bottom - 1),
1762 						BPoint(area.left, area.top + 1), light);
1763 				AddLine(BPoint(area.left + 1, area.top + 1),
1764 						BPoint(area.right, area.top + 1), light);
1765 				AddLine(BPoint(area.right, area.bottom),
1766 						BPoint(area.left, area.bottom), dark);
1767 			EndLineArray();
1768 			area.left++;
1769 			area.top += 2;
1770 			area.bottom--;
1771 			if (area.IsValid()) {
1772 				SetHighColor(fill);
1773 				FillRect(area);
1774 			}
1775 		}
1776 	} else {
1777 		int32 width = area.IntegerWidth();
1778 		if (width == 0) {
1779 			SetHighColor(dark);
1780 			StrokeLine(area.LeftBottom(), area.LeftTop());
1781 		} else if (width == 1) {
1782 			SetHighColor(dark);
1783 			FillRect(area);
1784 		} else {
1785 			BeginLineArray(4);
1786 				AddLine(BPoint(area.left, area.bottom),
1787 						BPoint(area.left, area.top), dark);
1788 				AddLine(BPoint(area.left + 1, area.bottom),
1789 						BPoint(area.left + 1, area.top + 1), light);
1790 				AddLine(BPoint(area.left + 1, area.top),
1791 						BPoint(area.right - 1, area.top), light);
1792 				AddLine(BPoint(area.right, area.top),
1793 						BPoint(area.right, area.bottom), dark);
1794 			EndLineArray();
1795 			area.left += 2;
1796 			area.top ++;
1797 			area.right--;
1798 			if (area.IsValid()) {
1799 				SetHighColor(fill);
1800 				FillRect(area);
1801 			}
1802 		}
1803 	}
1804 }
1805 
1806 
1807 void
1808 BScrollBar::_DrawArrowButton(int32 direction, bool doubleArrows, BRect r,
1809 							 const BRect& updateRect, bool enabled, bool down)
1810 {
1811 	if (!updateRect.Intersects(r))
1812 		return;
1813 
1814 	rgb_color c = ui_color(B_PANEL_BACKGROUND_COLOR);
1815 	rgb_color light, dark, darker, normal, arrow;
1816 
1817 	if (down && fPrivateData->fDoRepeat) {
1818 		light = tint_color(c, (B_DARKEN_1_TINT + B_DARKEN_2_TINT) / 2.0);
1819 		dark = darker = c;
1820 		normal = tint_color(c, B_DARKEN_1_TINT);
1821 		arrow = tint_color(c, B_DARKEN_MAX_TINT);
1822 
1823 	} else {
1824 		// Add a usability perk - disable buttons if they would not do anything
1825 		// - like a left arrow if the value == fMin
1826 // NOTE: disabled because of too much visual noise/distraction
1827 /*		if ((direction == ARROW_LEFT || direction == ARROW_UP)
1828 			&& (fValue == fMin)) {
1829 			use_enabled_colors = false;
1830 		} else if ((direction == ARROW_RIGHT || direction == ARROW_DOWN)
1831 			&& (fValue == fMax)) {
1832 			use_enabled_colors = false;
1833 		}*/
1834 
1835 		if (enabled) {
1836 			light = tint_color(c, B_LIGHTEN_MAX_TINT);
1837 			dark = tint_color(c, B_DARKEN_1_TINT);
1838 			darker = tint_color(c, B_DARKEN_2_TINT);
1839 			normal = c;
1840 			arrow = tint_color(c, (B_DARKEN_MAX_TINT + B_DARKEN_4_TINT) / 2.0);
1841 		} else {
1842 			light = tint_color(c, B_LIGHTEN_MAX_TINT);
1843 			dark = tint_color(c, B_LIGHTEN_1_TINT);
1844 			darker = tint_color(c, B_DARKEN_2_TINT);
1845 			normal = tint_color(c, B_LIGHTEN_2_TINT);
1846 			arrow = tint_color(c, B_DARKEN_1_TINT);
1847 		}
1848 	}
1849 
1850 	BPoint tri1, tri2, tri3;
1851 	float hInset = r.Width() / 3;
1852 	float vInset = r.Height() / 3;
1853 	r.InsetBy(hInset, vInset);
1854 
1855 	switch (direction) {
1856 		case ARROW_LEFT:
1857 			tri1.Set(r.right, r.top);
1858 			tri2.Set(r.right - r.Width() / 1.33, (r.top + r.bottom + 1) /2 );
1859 			tri3.Set(r.right, r.bottom + 1);
1860 			break;
1861 		case ARROW_RIGHT:
1862 			tri1.Set(r.left, r.bottom + 1);
1863 			tri2.Set(r.left + r.Width() / 1.33, (r.top + r.bottom + 1) / 2);
1864 			tri3.Set(r.left, r.top);
1865 			break;
1866 		case ARROW_UP:
1867 			tri1.Set(r.left, r.bottom);
1868 			tri2.Set((r.left + r.right + 1) / 2, r.bottom - r.Height() / 1.33);
1869 			tri3.Set(r.right + 1, r.bottom);
1870 			break;
1871 		default:
1872 			tri1.Set(r.left, r.top);
1873 			tri2.Set((r.left + r.right + 1) / 2, r.top + r.Height() / 1.33);
1874 			tri3.Set(r.right + 1, r.top);
1875 			break;
1876 	}
1877 	// offset triangle if down
1878 	if (down && fPrivateData->fDoRepeat) {
1879 		BPoint offset(1.0, 1.0);
1880 		tri1 = tri1 + offset;
1881 		tri2 = tri2 + offset;
1882 		tri3 = tri3 + offset;
1883 	}
1884 
1885 	r.InsetBy(-(hInset - 1), -(vInset - 1));
1886 	if (be_control_look != NULL) {
1887 		BRect temp(r.InsetByCopy(-1, -1));
1888 		uint32 flags = 0;
1889 		if (down)
1890 			flags |= BControlLook::B_ACTIVATED;
1891 		be_control_look->DrawButtonBackground(this, temp, updateRect,
1892 			down ? c : normal, flags, BControlLook::B_ALL_BORDERS,
1893 			fOrientation);
1894 	} else {
1895 		SetHighColor(normal);
1896 		FillRect(r);
1897 	}
1898 
1899 	BShape arrowShape;
1900 	arrowShape.MoveTo(tri1);
1901 	arrowShape.LineTo(tri2);
1902 	arrowShape.LineTo(tri3);
1903 
1904 	SetHighColor(arrow);
1905 	SetPenSize(ceilf(hInset / 2.0));
1906 	StrokeShape(&arrowShape);
1907 	SetPenSize(1.0);
1908 
1909 	if (be_control_look != NULL)
1910 		return;
1911 
1912 	r.InsetBy(-1, -1);
1913 	BeginLineArray(4);
1914 	if (direction == ARROW_LEFT || direction == ARROW_RIGHT) {
1915 		// horizontal
1916 		if (doubleArrows && direction == ARROW_LEFT) {
1917 			// draw in such a way that the arrows are
1918 			// more visually separated
1919 			AddLine(BPoint(r.left + 1, r.top),
1920 					BPoint(r.right - 1, r.top), light);
1921 			AddLine(BPoint(r.right, r.top),
1922 					BPoint(r.right, r.bottom), darker);
1923 		} else {
1924 			AddLine(BPoint(r.left + 1, r.top),
1925 					BPoint(r.right, r.top), light);
1926 			AddLine(BPoint(r.right, r.top + 1),
1927 					BPoint(r.right, r.bottom), dark);
1928 		}
1929 		AddLine(BPoint(r.left, r.bottom),
1930 				BPoint(r.left, r.top), light);
1931 		AddLine(BPoint(r.right - 1, r.bottom),
1932 				BPoint(r.left + 1, r.bottom), dark);
1933 	} else {
1934 		// vertical
1935 		if (doubleArrows && direction == ARROW_UP) {
1936 			// draw in such a way that the arrows are
1937 			// more visually separated
1938 			AddLine(BPoint(r.left, r.bottom - 1),
1939 					BPoint(r.left, r.top), light);
1940 			AddLine(BPoint(r.right, r.bottom),
1941 					BPoint(r.left, r.bottom), darker);
1942 		} else {
1943 			AddLine(BPoint(r.left, r.bottom),
1944 					BPoint(r.left, r.top), light);
1945 			AddLine(BPoint(r.right, r.bottom),
1946 					BPoint(r.left + 1, r.bottom), dark);
1947 		}
1948 		AddLine(BPoint(r.left + 1, r.top),
1949 				BPoint(r.right, r.top), light);
1950 		AddLine(BPoint(r.right, r.top + 1),
1951 				BPoint(r.right, r.bottom - 1), dark);
1952 	}
1953 	EndLineArray();
1954 }
1955 
1956 
1957 BSize
1958 BScrollBar::_MinSize() const
1959 {
1960 	BSize minSize;
1961 	if (fOrientation == B_HORIZONTAL) {
1962 		minSize.width = 2 * B_V_SCROLL_BAR_WIDTH
1963 			+ 2 * fPrivateData->fScrollBarInfo.min_knob_size;
1964 		minSize.height = B_H_SCROLL_BAR_HEIGHT;
1965 	} else {
1966 		minSize.width = B_V_SCROLL_BAR_WIDTH;
1967 		minSize.height = 2 * B_H_SCROLL_BAR_HEIGHT
1968 			+ 2 * fPrivateData->fScrollBarInfo.min_knob_size;
1969 	}
1970 	return minSize;
1971 }
1972 
1973