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