xref: /haiku/src/kits/interface/ScrollBar.cpp (revision 409d65c0d64212efc6882e81140647201c03c59d)
1 /*
2  * Copyright 2001-2020 Haiku, Inc. All rights reserved.
3  * Distributed under the terms of the MIT license.
4  *
5  * Authors:
6  *		Stephan Aßmus, superstippi@gmx.de
7  *		Stefano Ceccherini, burton666@libero.it
8  *		DarkWyrm, bpmagic@columbus.rr.com
9  *		Marc Flerackers, mflerackers@androme.be
10  *		John Scipione, jscipione@gmail.com
11  */
12 
13 
14 #include <ScrollBar.h>
15 
16 #include <algorithm>
17 
18 #include <math.h>
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <string.h>
22 
23 #include <ControlLook.h>
24 #include <LayoutUtils.h>
25 #include <Message.h>
26 #include <OS.h>
27 #include <Shape.h>
28 #include <Window.h>
29 
30 #include <binary_compatibility/Interface.h>
31 
32 
33 //#define TRACE_SCROLLBAR
34 #ifdef TRACE_SCROLLBAR
35 #	define TRACE(x...) printf(x)
36 #else
37 #	define TRACE(x...)
38 #endif
39 
40 
41 typedef enum {
42 	ARROW_LEFT = 0,
43 	ARROW_RIGHT,
44 	ARROW_UP,
45 	ARROW_DOWN,
46 	ARROW_NONE
47 } arrow_direction;
48 
49 
50 #define SCROLL_BAR_MAXIMUM_KNOB_SIZE	50
51 #define SCROLL_BAR_MINIMUM_KNOB_SIZE	9
52 
53 #define SBC_SCROLLBYVALUE	0
54 #define SBC_SETDOUBLE		1
55 #define SBC_SETPROPORTIONAL	2
56 #define SBC_SETSTYLE		3
57 
58 // Quick constants for determining which arrow is down and are defined with
59 // respect to double arrow mode. ARROW1 and ARROW4 refer to the outer pair of
60 // arrows and ARROW2 and ARROW3 refer to the inner ones. ARROW1 points left/up
61 // and ARROW4 points right/down.
62 #define ARROW1	0
63 #define ARROW2	1
64 #define ARROW3	2
65 #define ARROW4	3
66 #define THUMB	4
67 #define NOARROW	-1
68 
69 
70 static const bigtime_t kRepeatDelay = 300000;
71 
72 
73 // Because the R5 version kept a lot of data on server-side, we need to kludge
74 // our way into binary compatibility
75 class BScrollBar::Private {
76 public:
Private(BScrollBar * scrollBar)77 	Private(BScrollBar* scrollBar)
78 	:
79 	fScrollBar(scrollBar),
80 	fEnabled(true),
81 	fRepeaterThread(-1),
82 	fExitRepeater(false),
83 	fRepeaterDelay(0),
84 	fThumbFrame(0.0, 0.0, -1.0, -1.0),
85 	fDoRepeat(false),
86 	fClickOffset(0.0, 0.0),
87 	fThumbInc(0.0),
88 	fStopValue(0.0),
89 	fUpArrowsEnabled(true),
90 	fDownArrowsEnabled(true),
91 	fBorderHighlighted(false),
92 	fButtonDown(NOARROW)
93 	{
94 #ifdef TEST_MODE
95 		fScrollBarInfo.proportional = true;
96 		fScrollBarInfo.double_arrows = true;
97 		fScrollBarInfo.knob = 0;
98 		fScrollBarInfo.min_knob_size = 15;
99 #else
100 		get_scroll_bar_info(&fScrollBarInfo);
101 #endif
102 
103 		fScrollBarInfo.min_knob_size = (int32)(fScrollBarInfo.min_knob_size *
104 				(be_plain_font->Size() / 12.0f));
105 	}
106 
~Private()107 	~Private()
108 	{
109 		if (fRepeaterThread >= 0) {
110 			status_t dummy;
111 			fExitRepeater = true;
112 			wait_for_thread(fRepeaterThread, &dummy);
113 		}
114 	}
115 
116 	void DrawScrollBarButton(BScrollBar* owner, arrow_direction direction,
117 		BRect frame, bool down = false);
118 
119 	static int32 button_repeater_thread(void* data);
120 
121 	int32 ButtonRepeaterThread();
122 
123 	BScrollBar*			fScrollBar;
124 	bool				fEnabled;
125 
126 	// TODO: This should be a static, initialized by
127 	// _init_interface_kit() at application startup-time,
128 	// like BMenu::sMenuInfo
129 	scroll_bar_info		fScrollBarInfo;
130 
131 	thread_id			fRepeaterThread;
132 	volatile bool		fExitRepeater;
133 	bigtime_t			fRepeaterDelay;
134 
135 	BRect				fThumbFrame;
136 	volatile bool		fDoRepeat;
137 	BPoint				fClickOffset;
138 
139 	float				fThumbInc;
140 	float				fStopValue;
141 
142 	bool				fUpArrowsEnabled;
143 	bool				fDownArrowsEnabled;
144 
145 	bool				fBorderHighlighted;
146 
147 	int8				fButtonDown;
148 };
149 
150 
151 // This thread is spawned when a button is initially pushed and repeatedly scrolls
152 // the scrollbar by a little bit after a short delay
153 int32
button_repeater_thread(void * data)154 BScrollBar::Private::button_repeater_thread(void* data)
155 {
156 	BScrollBar::Private* privateData = (BScrollBar::Private*)data;
157 	return privateData->ButtonRepeaterThread();
158 }
159 
160 
161 int32
ButtonRepeaterThread()162 BScrollBar::Private::ButtonRepeaterThread()
163 {
164 	// Wait a bit before auto scrolling starts. As long as the user releases
165 	// and presses the button again while the repeat delay has not yet
166 	// triggered, the value is pushed into the future, so we need to loop such
167 	// that repeating starts at exactly the correct delay after the last
168 	// button press.
169 	while (fRepeaterDelay > system_time() && !fExitRepeater)
170 		snooze_until(fRepeaterDelay, B_SYSTEM_TIMEBASE);
171 
172 	// repeat loop
173 	while (!fExitRepeater) {
174 		if (fScrollBar->LockLooper()) {
175 			if (fDoRepeat) {
176 				float value = fScrollBar->Value() + fThumbInc;
177 				if (fButtonDown == NOARROW) {
178 					// in this case we want to stop when we're under the mouse
179 					if (fThumbInc > 0.0 && value <= fStopValue)
180 						fScrollBar->SetValue(value);
181 					if (fThumbInc < 0.0 && value >= fStopValue)
182 						fScrollBar->SetValue(value);
183 				} else
184 					fScrollBar->SetValue(value);
185 			}
186 
187 			fScrollBar->UnlockLooper();
188 		}
189 
190 		snooze(25000);
191 	}
192 
193 	// tell scrollbar we're gone
194 	if (fScrollBar->LockLooper()) {
195 		fRepeaterThread = -1;
196 		fScrollBar->UnlockLooper();
197 	}
198 
199 	return 0;
200 }
201 
202 
203 //	#pragma mark - BScrollBar
204 
205 
BScrollBar(BRect frame,const char * name,BView * target,float min,float max,orientation direction)206 BScrollBar::BScrollBar(BRect frame, const char* name, BView* target,
207 	float min, float max, orientation direction)
208 	:
209 	BView(frame, name, B_FOLLOW_NONE,
210 		B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE | B_FRAME_EVENTS),
211 	fMin(min),
212 	fMax(max),
213 	fSmallStep(1.0f),
214 	fLargeStep(10.0f),
215 	fValue(0),
216 	fProportion(0.0f),
217 	fTarget(NULL),
218 	fOrientation(direction)
219 {
220 	SetViewColor(B_TRANSPARENT_COLOR);
221 
222 	fPrivateData = new BScrollBar::Private(this);
223 
224 	SetTarget(target);
225 	SetEventMask(B_NO_POINTER_HISTORY);
226 
227 	_UpdateThumbFrame();
228 	_UpdateArrowButtons();
229 
230 	SetResizingMode(direction == B_VERTICAL
231 		? B_FOLLOW_TOP_BOTTOM | B_FOLLOW_RIGHT
232 		: B_FOLLOW_LEFT_RIGHT | B_FOLLOW_BOTTOM);
233 }
234 
235 
BScrollBar(const char * name,BView * target,float min,float max,orientation direction)236 BScrollBar::BScrollBar(const char* name, BView* target,
237 	float min, float max, orientation direction)
238 	:
239 	BView(name, B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE | B_FRAME_EVENTS),
240 	fMin(min),
241 	fMax(max),
242 	fSmallStep(1.0f),
243 	fLargeStep(10.0f),
244 	fValue(0),
245 	fProportion(0.0f),
246 	fTarget(NULL),
247 	fOrientation(direction)
248 {
249 	SetViewColor(B_TRANSPARENT_COLOR);
250 
251 	fPrivateData = new BScrollBar::Private(this);
252 
253 	SetTarget(target);
254 	SetEventMask(B_NO_POINTER_HISTORY);
255 
256 	_UpdateThumbFrame();
257 	_UpdateArrowButtons();
258 }
259 
260 
BScrollBar(BMessage * data)261 BScrollBar::BScrollBar(BMessage* data)
262 	:
263 	BView(data),
264 	fTarget(NULL)
265 {
266 	fPrivateData = new BScrollBar::Private(this);
267 
268 	// TODO: Does the BeOS implementation try to find the target
269 	// by name again? Does it archive the name at all?
270 	if (data->FindFloat("_range", 0, &fMin) < B_OK)
271 		fMin = 0.0f;
272 
273 	if (data->FindFloat("_range", 1, &fMax) < B_OK)
274 		fMax = 0.0f;
275 
276 	if (data->FindFloat("_steps", 0, &fSmallStep) < B_OK)
277 		fSmallStep = 1.0f;
278 
279 	if (data->FindFloat("_steps", 1, &fLargeStep) < B_OK)
280 		fLargeStep = 10.0f;
281 
282 	if (data->FindFloat("_val", &fValue) < B_OK)
283 		fValue = 0.0;
284 
285 	int32 orientation;
286 	if (data->FindInt32("_orient", &orientation) < B_OK) {
287 		fOrientation = B_VERTICAL;
288 	} else
289 		fOrientation = (enum orientation)orientation;
290 
291 	if ((Flags() & B_SUPPORTS_LAYOUT) == 0) {
292 		// just to make sure
293 		SetResizingMode(fOrientation == B_VERTICAL
294 			? B_FOLLOW_TOP_BOTTOM | B_FOLLOW_RIGHT
295 			: B_FOLLOW_LEFT_RIGHT | B_FOLLOW_BOTTOM);
296 	}
297 
298 	if (data->FindFloat("_prop", &fProportion) < B_OK)
299 		fProportion = 0.0;
300 
301 	_UpdateThumbFrame();
302 	_UpdateArrowButtons();
303 }
304 
305 
~BScrollBar()306 BScrollBar::~BScrollBar()
307 {
308 	SetTarget((BView*)NULL);
309 	delete fPrivateData;
310 }
311 
312 
313 BArchivable*
Instantiate(BMessage * data)314 BScrollBar::Instantiate(BMessage* data)
315 {
316 	if (validate_instantiation(data, "BScrollBar"))
317 		return new BScrollBar(data);
318 	return NULL;
319 }
320 
321 
322 status_t
Archive(BMessage * data,bool deep) const323 BScrollBar::Archive(BMessage* data, bool deep) const
324 {
325 	status_t err = BView::Archive(data, deep);
326 	if (err != B_OK)
327 		return err;
328 
329 	err = data->AddFloat("_range", fMin);
330 	if (err != B_OK)
331 		return err;
332 
333 	err = data->AddFloat("_range", fMax);
334 	if (err != B_OK)
335 		return err;
336 
337 	err = data->AddFloat("_steps", fSmallStep);
338 	if (err != B_OK)
339 		return err;
340 
341 	err = data->AddFloat("_steps", fLargeStep);
342 	if (err != B_OK)
343 		return err;
344 
345 	err = data->AddFloat("_val", fValue);
346 	if (err != B_OK)
347 		return err;
348 
349 	err = data->AddInt32("_orient", (int32)fOrientation);
350 	if (err != B_OK)
351 		return err;
352 
353 	err = data->AddFloat("_prop", fProportion);
354 
355 	return err;
356 }
357 
358 
359 void
AllAttached()360 BScrollBar::AllAttached()
361 {
362 	BView::AllAttached();
363 }
364 
365 
366 void
AllDetached()367 BScrollBar::AllDetached()
368 {
369 	BView::AllDetached();
370 }
371 
372 
373 void
AttachedToWindow()374 BScrollBar::AttachedToWindow()
375 {
376 	BView::AttachedToWindow();
377 }
378 
379 
380 void
DetachedFromWindow()381 BScrollBar::DetachedFromWindow()
382 {
383 	BView::DetachedFromWindow();
384 }
385 
386 
387 void
Draw(BRect updateRect)388 BScrollBar::Draw(BRect updateRect)
389 {
390 	rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
391 
392 	uint32 flags = 0;
393 	bool scrollingEnabled = fMin < fMax
394 		&& fProportion >= 0.0f && fProportion < 1.0f;
395 	if (scrollingEnabled)
396 		flags |= BControlLook::B_PARTIALLY_ACTIVATED;
397 
398 	if (!fPrivateData->fEnabled || !scrollingEnabled)
399 		flags |= BControlLook::B_DISABLED;
400 
401 	bool isFocused = fPrivateData->fBorderHighlighted;
402 	if (isFocused)
403 		flags |= BControlLook::B_FOCUSED;
404 
405 	bool doubleArrows = _DoubleArrows();
406 
407 	// draw border
408 	BRect rect = Bounds();
409 	be_control_look->DrawScrollBarBorder(this, rect, updateRect, base, flags,
410 		fOrientation);
411 
412 	// inset past border
413 	rect.InsetBy(1, 1);
414 
415 	// draw arrows buttons
416 	BRect thumbBG = rect;
417 	if (fOrientation == B_HORIZONTAL) {
418 		BRect buttonFrame(rect.left, rect.top,
419 			rect.left + rect.Height(), rect.bottom);
420 
421 		be_control_look->DrawScrollBarButton(this, buttonFrame, updateRect,
422 			base, flags | (fPrivateData->fButtonDown == ARROW1
423 				? BControlLook::B_ACTIVATED : 0),
424 			BControlLook::B_LEFT_ARROW, fOrientation,
425 			fPrivateData->fButtonDown == ARROW1);
426 
427 		if (doubleArrows) {
428 			buttonFrame.OffsetBy(rect.Height() + 1, 0.0f);
429 			be_control_look->DrawScrollBarButton(this, buttonFrame, updateRect,
430 				base, flags | (fPrivateData->fButtonDown == ARROW2
431 					? BControlLook::B_ACTIVATED : 0),
432 				BControlLook::B_RIGHT_ARROW, fOrientation,
433 				fPrivateData->fButtonDown == ARROW2);
434 
435 			buttonFrame.OffsetTo(rect.right - ((rect.Height() * 2) + 1),
436 				rect.top);
437 			be_control_look->DrawScrollBarButton(this, buttonFrame, updateRect,
438 				base, flags | (fPrivateData->fButtonDown == ARROW3
439 					? BControlLook::B_ACTIVATED : 0),
440 				BControlLook::B_LEFT_ARROW, fOrientation,
441 				fPrivateData->fButtonDown == ARROW3);
442 
443 			thumbBG.left += rect.Height() * 2 + 2;
444 			thumbBG.right -= rect.Height() * 2 + 2;
445 		} else {
446 			thumbBG.left += rect.Height() + 1;
447 			thumbBG.right -= rect.Height() + 1;
448 		}
449 
450 		buttonFrame.OffsetTo(rect.right - rect.Height(), rect.top);
451 		be_control_look->DrawScrollBarButton(this, buttonFrame, updateRect,
452 			base, flags | (fPrivateData->fButtonDown == ARROW4
453 				? BControlLook::B_ACTIVATED : 0),
454 			BControlLook::B_RIGHT_ARROW, fOrientation,
455 			fPrivateData->fButtonDown == ARROW4);
456 	} else {
457 		BRect buttonFrame(rect.left, rect.top, rect.right,
458 			rect.top + rect.Width());
459 
460 		be_control_look->DrawScrollBarButton(this, buttonFrame, updateRect,
461 			base, flags | (fPrivateData->fButtonDown == ARROW1
462 				? BControlLook::B_ACTIVATED : 0),
463 			BControlLook::B_UP_ARROW, fOrientation,
464 			fPrivateData->fButtonDown == ARROW1);
465 
466 		if (doubleArrows) {
467 			buttonFrame.OffsetBy(0, rect.Width() + 1);
468 			be_control_look->DrawScrollBarButton(this, buttonFrame,
469 				updateRect, base, flags | (fPrivateData->fButtonDown == ARROW2
470 					? BControlLook::B_ACTIVATED : 0),
471 				BControlLook::B_DOWN_ARROW, fOrientation,
472 				fPrivateData->fButtonDown == ARROW2);
473 
474 			buttonFrame.OffsetTo(rect.left, rect.bottom
475 				- ((rect.Width() * 2) + 1));
476 			be_control_look->DrawScrollBarButton(this, buttonFrame,
477 				updateRect, base, flags | (fPrivateData->fButtonDown == ARROW3
478 					? BControlLook::B_ACTIVATED : 0),
479 				BControlLook::B_UP_ARROW, fOrientation,
480 				fPrivateData->fButtonDown == ARROW3);
481 
482 			thumbBG.top += rect.Width() * 2 + 2;
483 			thumbBG.bottom -= rect.Width() * 2 + 2;
484 		} else {
485 			thumbBG.top += rect.Width() + 1;
486 			thumbBG.bottom -= rect.Width() + 1;
487 		}
488 
489 		buttonFrame.OffsetTo(rect.left, rect.bottom - rect.Width());
490 		be_control_look->DrawScrollBarButton(this, buttonFrame, updateRect,
491 			base, flags | (fPrivateData->fButtonDown == ARROW4
492 				? BControlLook::B_ACTIVATED : 0),
493 			BControlLook::B_DOWN_ARROW, fOrientation,
494 			fPrivateData->fButtonDown == ARROW4);
495 	}
496 
497 	// fill background besides the thumb
498 	rect = fPrivateData->fThumbFrame;
499 	if (fOrientation == B_HORIZONTAL) {
500 		BRect leftOfThumb(thumbBG.left, thumbBG.top,
501 			rect.left - 1, thumbBG.bottom);
502 		BRect rightOfThumb(rect.right + 1, thumbBG.top,
503 			thumbBG.right, thumbBG.bottom);
504 		be_control_look->DrawScrollBarBackground(this, leftOfThumb,
505 			rightOfThumb, updateRect, base, flags, fOrientation);
506 	} else {
507 		BRect topOfThumb(thumbBG.left, thumbBG.top,
508 			thumbBG.right, rect.top - 1);
509 		BRect bottomOfThumb(thumbBG.left, rect.bottom + 1,
510 			thumbBG.right, thumbBG.bottom);
511 		be_control_look->DrawScrollBarBackground(this, topOfThumb,
512 			bottomOfThumb, updateRect, base, flags, fOrientation);
513 	}
514 
515 	// draw thumb
516 	rect = fPrivateData->fThumbFrame;
517 	be_control_look->DrawScrollBarThumb(this, rect, updateRect,
518 		ui_color(B_SCROLL_BAR_THUMB_COLOR), flags, fOrientation,
519 		fPrivateData->fScrollBarInfo.knob);
520 }
521 
522 
523 void
FrameMoved(BPoint newPosition)524 BScrollBar::FrameMoved(BPoint newPosition)
525 {
526 	BView::FrameMoved(newPosition);
527 }
528 
529 
530 void
FrameResized(float newWidth,float newHeight)531 BScrollBar::FrameResized(float newWidth, float newHeight)
532 {
533 	_UpdateThumbFrame();
534 }
535 
536 
537 void
MessageReceived(BMessage * message)538 BScrollBar::MessageReceived(BMessage* message)
539 {
540 	switch(message->what) {
541 		case B_VALUE_CHANGED:
542 		{
543 			int32 value;
544 			if (message->FindInt32("value", &value) == B_OK)
545 				ValueChanged(value);
546 
547 			break;
548 		}
549 
550 		case B_MOUSE_WHEEL_CHANGED:
551 		{
552 			// Must handle this here since BView checks for the existence of
553 			// scrollbars, which a scrollbar itself does not have
554 			float deltaX = 0.0f;
555 			float deltaY = 0.0f;
556 			message->FindFloat("be:wheel_delta_x", &deltaX);
557 			message->FindFloat("be:wheel_delta_y", &deltaY);
558 
559 			if (deltaX == 0.0f && deltaY == 0.0f)
560 				break;
561 
562 			if (deltaX != 0.0f && deltaY == 0.0f)
563 				deltaY = deltaX;
564 
565 			ScrollWithMouseWheelDelta(this, deltaY);
566 		}
567 
568 		default:
569 			BView::MessageReceived(message);
570 	}
571 }
572 
573 
574 void
MouseDown(BPoint where)575 BScrollBar::MouseDown(BPoint where)
576 {
577 	if (!fPrivateData->fEnabled || fMin == fMax)
578 		return;
579 
580 	SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS);
581 
582 	int32 buttons;
583 	if (Looper() == NULL || Looper()->CurrentMessage() == NULL
584 		|| Looper()->CurrentMessage()->FindInt32("buttons", &buttons) != B_OK) {
585 		buttons = B_PRIMARY_MOUSE_BUTTON;
586 	}
587 
588 	if (buttons & B_SECONDARY_MOUSE_BUTTON) {
589 		// special absolute scrolling: move thumb to where we clicked
590 		fPrivateData->fButtonDown = THUMB;
591 		fPrivateData->fClickOffset
592 			= fPrivateData->fThumbFrame.LeftTop() - where;
593 		if (Orientation() == B_HORIZONTAL) {
594 			fPrivateData->fClickOffset.x
595 				= -fPrivateData->fThumbFrame.Width() / 2;
596 		} else {
597 			fPrivateData->fClickOffset.y
598 				= -fPrivateData->fThumbFrame.Height() / 2;
599 		}
600 
601 		SetValue(_ValueFor(where + fPrivateData->fClickOffset));
602 		return;
603 	}
604 
605 	// hit test for the thumb
606 	if (fPrivateData->fThumbFrame.Contains(where)) {
607 		fPrivateData->fButtonDown = THUMB;
608 		fPrivateData->fClickOffset
609 			= fPrivateData->fThumbFrame.LeftTop() - where;
610 		Invalidate(fPrivateData->fThumbFrame);
611 		return;
612 	}
613 
614 	// hit test for arrows or empty area
615 	float scrollValue = 0.0;
616 
617 	// pressing the shift key scrolls faster
618 	float buttonStepSize
619 		= (modifiers() & B_SHIFT_KEY) != 0 ? fLargeStep : fSmallStep;
620 
621 	fPrivateData->fButtonDown = _ButtonFor(where);
622 	switch (fPrivateData->fButtonDown) {
623 		case ARROW1:
624 			scrollValue = -buttonStepSize;
625 			break;
626 
627 		case ARROW2:
628 			scrollValue = buttonStepSize;
629 			break;
630 
631 		case ARROW3:
632 			scrollValue = -buttonStepSize;
633 			break;
634 
635 		case ARROW4:
636 			scrollValue = buttonStepSize;
637 			break;
638 
639 		case NOARROW:
640 			// we hit the empty area, figure out which side of the thumb
641 			if (fOrientation == B_VERTICAL) {
642 				if (where.y < fPrivateData->fThumbFrame.top)
643 					scrollValue = -fLargeStep;
644 				else
645 					scrollValue = fLargeStep;
646 			} else {
647 				if (where.x < fPrivateData->fThumbFrame.left)
648 					scrollValue = -fLargeStep;
649 				else
650 					scrollValue = fLargeStep;
651 			}
652 			_UpdateTargetValue(where);
653 			break;
654 	}
655 	if (scrollValue != 0.0) {
656 		SetValue(fValue + scrollValue);
657 		Invalidate(_ButtonRectFor(fPrivateData->fButtonDown));
658 
659 		// launch the repeat thread
660 		if (fPrivateData->fRepeaterThread == -1) {
661 			fPrivateData->fExitRepeater = false;
662 			fPrivateData->fRepeaterDelay = system_time() + kRepeatDelay;
663 			fPrivateData->fThumbInc = scrollValue;
664 			fPrivateData->fDoRepeat = true;
665 			fPrivateData->fRepeaterThread = spawn_thread(
666 				fPrivateData->button_repeater_thread, "scroll repeater",
667 				B_NORMAL_PRIORITY, fPrivateData);
668 			resume_thread(fPrivateData->fRepeaterThread);
669 		} else {
670 			fPrivateData->fExitRepeater = false;
671 			fPrivateData->fRepeaterDelay = system_time() + kRepeatDelay;
672 			fPrivateData->fDoRepeat = true;
673 		}
674 	}
675 }
676 
677 
678 void
MouseMoved(BPoint where,uint32 code,const BMessage * dragMessage)679 BScrollBar::MouseMoved(BPoint where, uint32 code, const BMessage* dragMessage)
680 {
681 	if (!fPrivateData->fEnabled || fMin >= fMax || fProportion >= 1.0f
682 		|| fProportion < 0.0f) {
683 		return;
684 	}
685 
686 	if (fPrivateData->fButtonDown != NOARROW) {
687 		if (fPrivateData->fButtonDown == THUMB) {
688 			SetValue(_ValueFor(where + fPrivateData->fClickOffset));
689 		} else {
690 			// suspend the repeating if the mouse is not over the button
691 			bool repeat = _ButtonRectFor(fPrivateData->fButtonDown).Contains(
692 				where);
693 			if (fPrivateData->fDoRepeat != repeat) {
694 				fPrivateData->fDoRepeat = repeat;
695 				Invalidate(_ButtonRectFor(fPrivateData->fButtonDown));
696 			}
697 		}
698 	} else {
699 		// update the value at which we want to stop repeating
700 		if (fPrivateData->fDoRepeat) {
701 			_UpdateTargetValue(where);
702 			// we might have to turn arround
703 			if ((fValue < fPrivateData->fStopValue
704 					&& fPrivateData->fThumbInc < 0)
705 				|| (fValue > fPrivateData->fStopValue
706 					&& fPrivateData->fThumbInc > 0)) {
707 				fPrivateData->fThumbInc = -fPrivateData->fThumbInc;
708 			}
709 		}
710 	}
711 }
712 
713 
714 void
MouseUp(BPoint where)715 BScrollBar::MouseUp(BPoint where)
716 {
717 	if (fPrivateData->fButtonDown == THUMB)
718 		Invalidate(fPrivateData->fThumbFrame);
719 	else
720 		Invalidate(_ButtonRectFor(fPrivateData->fButtonDown));
721 
722 	fPrivateData->fButtonDown = NOARROW;
723 	fPrivateData->fExitRepeater = true;
724 	fPrivateData->fDoRepeat = false;
725 }
726 
727 
728 void
WindowActivated(bool active)729 BScrollBar::WindowActivated(bool active)
730 {
731 	fPrivateData->fEnabled = active;
732 	Invalidate();
733 }
734 
735 
736 void
SetValue(float value)737 BScrollBar::SetValue(float value)
738 {
739 	if (value > fMax)
740 		value = fMax;
741 	else if (value < fMin)
742 		value = fMin;
743 	else if (isnan(value) || isinf(value))
744 		return;
745 
746 	value = roundf(value);
747 	if (value == fValue)
748 		return;
749 
750 	TRACE("BScrollBar(%s)::SetValue(%.1f)\n", Name(), value);
751 
752 	fValue = value;
753 
754 	_UpdateThumbFrame();
755 	_UpdateArrowButtons();
756 
757 	ValueChanged(fValue);
758 }
759 
760 
761 float
Value() const762 BScrollBar::Value() const
763 {
764 	return fValue;
765 }
766 
767 
768 void
ValueChanged(float newValue)769 BScrollBar::ValueChanged(float newValue)
770 {
771 	TRACE("BScrollBar(%s)::ValueChanged(%.1f)\n", Name(), newValue);
772 
773 	if (fTarget != NULL) {
774 		// cache target bounds
775 		BRect targetBounds = fTarget->Bounds();
776 		// if vertical, check bounds top and scroll if different from newValue
777 		if (fOrientation == B_VERTICAL && targetBounds.top != newValue)
778 			fTarget->ScrollBy(0.0, newValue - targetBounds.top);
779 
780 		// if horizontal, check bounds left and scroll if different from newValue
781 		if (fOrientation == B_HORIZONTAL && targetBounds.left != newValue)
782 			fTarget->ScrollBy(newValue - targetBounds.left, 0.0);
783 	}
784 
785 	TRACE(" -> %.1f\n", newValue);
786 
787 	SetValue(newValue);
788 }
789 
790 
791 void
SetProportion(float value)792 BScrollBar::SetProportion(float value)
793 {
794 	if (value < 0.0f)
795 		value = 0.0f;
796 	else if (value > 1.0f)
797 		value = 1.0f;
798 
799 	if (value == fProportion)
800 		return;
801 
802 	TRACE("BScrollBar(%s)::SetProportion(%.1f)\n", Name(), value);
803 
804 	bool oldEnabled = fPrivateData->fEnabled && fMin < fMax
805 		&& fProportion < 1.0f && fProportion >= 0.0f;
806 
807 	fProportion = value;
808 
809 	bool newEnabled = fPrivateData->fEnabled && fMin < fMax
810 		&& fProportion < 1.0f && fProportion >= 0.0f;
811 
812 	_UpdateThumbFrame();
813 
814 	if (oldEnabled != newEnabled)
815 		Invalidate();
816 }
817 
818 
819 float
Proportion() const820 BScrollBar::Proportion() const
821 {
822 	return fProportion;
823 }
824 
825 
826 void
SetRange(float min,float max)827 BScrollBar::SetRange(float min, float max)
828 {
829 	if (min > max || isnanf(min) || isnanf(max)
830 		|| isinff(min) || isinff(max)) {
831 		min = 0.0f;
832 		max = 0.0f;
833 	}
834 
835 	min = roundf(min);
836 	max = roundf(max);
837 
838 	if (fMin == min && fMax == max)
839 		return;
840 
841 	TRACE("BScrollBar(%s)::SetRange(min=%.1f, max=%.1f)\n", Name(), min, max);
842 
843 	fMin = min;
844 	fMax = max;
845 
846 	if (fValue < fMin || fValue > fMax)
847 		SetValue(fValue);
848 	else {
849 		_UpdateThumbFrame();
850 		Invalidate();
851 	}
852 }
853 
854 
855 void
GetRange(float * min,float * max) const856 BScrollBar::GetRange(float* min, float* max) const
857 {
858 	if (min != NULL)
859 		*min = fMin;
860 
861 	if (max != NULL)
862 		*max = fMax;
863 }
864 
865 
866 void
SetSteps(float smallStep,float largeStep)867 BScrollBar::SetSteps(float smallStep, float largeStep)
868 {
869 	// Under R5, steps can be set only after being attached to a window,
870 	// probably because the data is kept server-side. We'll just remove
871 	// that limitation... :P
872 
873 	// The BeBook also says that we need to specify an integer value even
874 	// though the step values are floats. For the moment, we'll just make
875 	// sure that they are integers
876 	smallStep = roundf(smallStep);
877 	largeStep = roundf(largeStep);
878 	if (fSmallStep == smallStep && fLargeStep == largeStep)
879 		return;
880 
881 	TRACE("BScrollBar(%s)::SetSteps(small=%.1f, large=%.1f)\n", Name(),
882 		smallStep, largeStep);
883 
884 	fSmallStep = smallStep;
885 	fLargeStep = largeStep;
886 
887 	if (fProportion == 0.0) {
888 		// special case, proportion is based on fLargeStep if it was never
889 		// set, so it means we need to invalidate here
890 		_UpdateThumbFrame();
891 		Invalidate();
892 	}
893 
894 	// TODO: test use of fractional values and make them work properly if
895 	// they don't
896 }
897 
898 
899 void
GetSteps(float * smallStep,float * largeStep) const900 BScrollBar::GetSteps(float* smallStep, float* largeStep) const
901 {
902 	if (smallStep != NULL)
903 		*smallStep = fSmallStep;
904 
905 	if (largeStep != NULL)
906 		*largeStep = fLargeStep;
907 }
908 
909 
910 void
SetTarget(BView * target)911 BScrollBar::SetTarget(BView* target)
912 {
913 	if (fTarget) {
914 		// unset the previous target's scrollbar pointer
915 		if (fOrientation == B_VERTICAL)
916 			fTarget->fVerScroller = NULL;
917 		else
918 			fTarget->fHorScroller = NULL;
919 	}
920 
921 	fTarget = target;
922 	if (fTarget) {
923 		if (fOrientation == B_VERTICAL)
924 			fTarget->fVerScroller = this;
925 		else
926 			fTarget->fHorScroller = this;
927 	}
928 }
929 
930 
931 void
SetTarget(const char * targetName)932 BScrollBar::SetTarget(const char* targetName)
933 {
934 	// NOTE 1: BeOS implementation crashes for targetName == NULL
935 	// NOTE 2: BeOS implementation also does not modify the target
936 	// if it can't be found
937 	if (targetName == NULL)
938 		return;
939 
940 	if (Window() == NULL)
941 		debugger("Method requires window and doesn't have one");
942 
943 	BView* target = Window()->FindView(targetName);
944 	if (target != NULL)
945 		SetTarget(target);
946 }
947 
948 
949 BView*
Target() const950 BScrollBar::Target() const
951 {
952 	return fTarget;
953 }
954 
955 
956 void
SetOrientation(orientation direction)957 BScrollBar::SetOrientation(orientation direction)
958 {
959 	if (fOrientation == direction)
960 		return;
961 
962 	fOrientation = direction;
963 	InvalidateLayout();
964 	Invalidate();
965 }
966 
967 
968 orientation
Orientation() const969 BScrollBar::Orientation() const
970 {
971 	return fOrientation;
972 }
973 
974 
975 status_t
SetBorderHighlighted(bool highlight)976 BScrollBar::SetBorderHighlighted(bool highlight)
977 {
978 	if (fPrivateData->fBorderHighlighted == highlight)
979 		return B_OK;
980 
981 	fPrivateData->fBorderHighlighted = highlight;
982 
983 	BRect dirty(Bounds());
984 	if (fOrientation == B_HORIZONTAL)
985 		dirty.bottom = dirty.top;
986 	else
987 		dirty.right = dirty.left;
988 
989 	Invalidate(dirty);
990 
991 	return B_OK;
992 }
993 
994 
995 void
GetPreferredSize(float * _width,float * _height)996 BScrollBar::GetPreferredSize(float* _width, float* _height)
997 {
998 	if (fOrientation == B_VERTICAL) {
999 		if (_width)
1000 			*_width = be_control_look->GetScrollBarWidth(B_VERTICAL);
1001 
1002 		if (_height)
1003 			*_height = _MinSize().Height();
1004 	} else if (fOrientation == B_HORIZONTAL) {
1005 		if (_width)
1006 			*_width = _MinSize().Width();
1007 
1008 		if (_height)
1009 			*_height = be_control_look->GetScrollBarWidth(B_HORIZONTAL);
1010 	}
1011 }
1012 
1013 
1014 void
ResizeToPreferred()1015 BScrollBar::ResizeToPreferred()
1016 {
1017 	BView::ResizeToPreferred();
1018 }
1019 
1020 
1021 
1022 void
MakeFocus(bool focus)1023 BScrollBar::MakeFocus(bool focus)
1024 {
1025 	BView::MakeFocus(focus);
1026 }
1027 
1028 
1029 BSize
MinSize()1030 BScrollBar::MinSize()
1031 {
1032 	return BLayoutUtils::ComposeSize(ExplicitMinSize(), _MinSize());
1033 }
1034 
1035 
1036 BSize
MaxSize()1037 BScrollBar::MaxSize()
1038 {
1039 	BSize maxSize;
1040 	GetPreferredSize(&maxSize.width, &maxSize.height);
1041 	if (fOrientation == B_HORIZONTAL)
1042 		maxSize.width = B_SIZE_UNLIMITED;
1043 	else
1044 		maxSize.height = B_SIZE_UNLIMITED;
1045 	return BLayoutUtils::ComposeSize(ExplicitMaxSize(), maxSize);
1046 }
1047 
1048 
1049 BSize
PreferredSize()1050 BScrollBar::PreferredSize()
1051 {
1052 	BSize preferredSize;
1053 	GetPreferredSize(&preferredSize.width, &preferredSize.height);
1054 	return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), preferredSize);
1055 }
1056 
1057 
1058 status_t
GetSupportedSuites(BMessage * message)1059 BScrollBar::GetSupportedSuites(BMessage* message)
1060 {
1061 	return BView::GetSupportedSuites(message);
1062 }
1063 
1064 
1065 BHandler*
ResolveSpecifier(BMessage * message,int32 index,BMessage * specifier,int32 what,const char * property)1066 BScrollBar::ResolveSpecifier(BMessage* message, int32 index,
1067 	BMessage* specifier, int32 what, const char* property)
1068 {
1069 	return BView::ResolveSpecifier(message, index, specifier, what, property);
1070 }
1071 
1072 
1073 status_t
Perform(perform_code code,void * _data)1074 BScrollBar::Perform(perform_code code, void* _data)
1075 {
1076 	switch (code) {
1077 		case PERFORM_CODE_MIN_SIZE:
1078 			((perform_data_min_size*)_data)->return_value
1079 				= BScrollBar::MinSize();
1080 
1081 			return B_OK;
1082 
1083 		case PERFORM_CODE_MAX_SIZE:
1084 			((perform_data_max_size*)_data)->return_value
1085 				= BScrollBar::MaxSize();
1086 
1087 			return B_OK;
1088 
1089 		case PERFORM_CODE_PREFERRED_SIZE:
1090 			((perform_data_preferred_size*)_data)->return_value
1091 				= BScrollBar::PreferredSize();
1092 
1093 			return B_OK;
1094 
1095 		case PERFORM_CODE_LAYOUT_ALIGNMENT:
1096 			((perform_data_layout_alignment*)_data)->return_value
1097 				= BScrollBar::LayoutAlignment();
1098 
1099 			return B_OK;
1100 
1101 		case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH:
1102 			((perform_data_has_height_for_width*)_data)->return_value
1103 				= BScrollBar::HasHeightForWidth();
1104 
1105 			return B_OK;
1106 
1107 		case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH:
1108 		{
1109 			perform_data_get_height_for_width* data
1110 				= (perform_data_get_height_for_width*)_data;
1111 			BScrollBar::GetHeightForWidth(data->width, &data->min, &data->max,
1112 				&data->preferred);
1113 
1114 			return B_OK;
1115 		}
1116 
1117 		case PERFORM_CODE_SET_LAYOUT:
1118 		{
1119 			perform_data_set_layout* data = (perform_data_set_layout*)_data;
1120 			BScrollBar::SetLayout(data->layout);
1121 
1122 			return B_OK;
1123 		}
1124 
1125 		case PERFORM_CODE_LAYOUT_INVALIDATED:
1126 		{
1127 			perform_data_layout_invalidated* data
1128 				= (perform_data_layout_invalidated*)_data;
1129 			BScrollBar::LayoutInvalidated(data->descendants);
1130 
1131 			return B_OK;
1132 		}
1133 
1134 		case PERFORM_CODE_DO_LAYOUT:
1135 		{
1136 			BScrollBar::DoLayout();
1137 
1138 			return B_OK;
1139 		}
1140 	}
1141 
1142 	return BView::Perform(code, _data);
1143 }
1144 
1145 
_ReservedScrollBar1()1146 void BScrollBar::_ReservedScrollBar1() {}
_ReservedScrollBar2()1147 void BScrollBar::_ReservedScrollBar2() {}
_ReservedScrollBar3()1148 void BScrollBar::_ReservedScrollBar3() {}
_ReservedScrollBar4()1149 void BScrollBar::_ReservedScrollBar4() {}
1150 
1151 
1152 BScrollBar&
operator =(const BScrollBar &)1153 BScrollBar::operator=(const BScrollBar&)
1154 {
1155 	return *this;
1156 }
1157 
1158 
1159 bool
_DoubleArrows() const1160 BScrollBar::_DoubleArrows() const
1161 {
1162 	if (!fPrivateData->fScrollBarInfo.double_arrows)
1163 		return false;
1164 
1165 	// if there is not enough room, switch to single arrows even though
1166 	// double arrows is specified
1167 	if (fOrientation == B_HORIZONTAL) {
1168 		return Bounds().Width() > (Bounds().Height() + 1) * 4
1169 			+ fPrivateData->fScrollBarInfo.min_knob_size * 2;
1170 	} else {
1171 		return Bounds().Height() > (Bounds().Width() + 1) * 4
1172 			+ fPrivateData->fScrollBarInfo.min_knob_size * 2;
1173 	}
1174 }
1175 
1176 
1177 void
_UpdateThumbFrame()1178 BScrollBar::_UpdateThumbFrame()
1179 {
1180 	BRect bounds = Bounds();
1181 	bounds.InsetBy(1.0, 1.0);
1182 
1183 	BRect oldFrame = fPrivateData->fThumbFrame;
1184 	fPrivateData->fThumbFrame = bounds;
1185 	float minSize = fPrivateData->fScrollBarInfo.min_knob_size;
1186 	float maxSize;
1187 	float buttonSize;
1188 
1189 	// assume square buttons
1190 	if (fOrientation == B_VERTICAL) {
1191 		maxSize = bounds.Height();
1192 		buttonSize = bounds.Width() + 1.0;
1193 	} else {
1194 		maxSize = bounds.Width();
1195 		buttonSize = bounds.Height() + 1.0;
1196 	}
1197 
1198 	if (_DoubleArrows()) {
1199 		// subtract the size of four buttons
1200 		maxSize -= buttonSize * 4;
1201 	} else {
1202 		// subtract the size of two buttons
1203 		maxSize -= buttonSize * 2;
1204 	}
1205 	// visual adjustments (room for darker line between thumb and buttons)
1206 	maxSize--;
1207 
1208 	float thumbSize;
1209 	if (fPrivateData->fScrollBarInfo.proportional) {
1210 		float proportion = fProportion;
1211 		if (fMin >= fMax || proportion > 1.0 || proportion < 0.0)
1212 			proportion = 1.0;
1213 
1214 		if (proportion == 0.0) {
1215 			// Special case a proportion of 0.0, use the large step value
1216 			// in that case (NOTE: fMin == fMax already handled above)
1217 			// This calculation is based on the assumption that "large step"
1218 			// scrolls by one "page size".
1219 			proportion = fLargeStep / (2 * (fMax - fMin));
1220 			if (proportion > 1.0)
1221 				proportion = 1.0;
1222 		}
1223 		thumbSize = maxSize * proportion;
1224 		if (thumbSize < minSize)
1225 			thumbSize = minSize;
1226 	} else
1227 		thumbSize = minSize;
1228 
1229 	thumbSize = floorf(thumbSize + 0.5);
1230 	thumbSize--;
1231 
1232 	// the thumb can be scrolled within the remaining area "maxSize - thumbSize - 1.0"
1233 	float offset = 0.0;
1234 	if (fMax > fMin) {
1235 		offset = floorf(((fValue - fMin) / (fMax - fMin))
1236 			* (maxSize - thumbSize - 1.0));
1237 	}
1238 
1239 	if (_DoubleArrows()) {
1240 		offset += buttonSize * 2;
1241 	} else
1242 		offset += buttonSize;
1243 
1244 	// visual adjustments (room for darker line between thumb and buttons)
1245 	offset++;
1246 
1247 	if (fOrientation == B_VERTICAL) {
1248 		fPrivateData->fThumbFrame.bottom = fPrivateData->fThumbFrame.top
1249 			+ thumbSize;
1250 		fPrivateData->fThumbFrame.OffsetBy(0.0, offset);
1251 	} else {
1252 		fPrivateData->fThumbFrame.right = fPrivateData->fThumbFrame.left
1253 			+ thumbSize;
1254 		fPrivateData->fThumbFrame.OffsetBy(offset, 0.0);
1255 	}
1256 
1257 	if (Window() != NULL) {
1258 		BRect invalid = oldFrame.IsValid()
1259 			? oldFrame | fPrivateData->fThumbFrame
1260 			: fPrivateData->fThumbFrame;
1261 		// account for those two dark lines
1262 		if (fOrientation == B_HORIZONTAL)
1263 			invalid.InsetBy(-2.0, 0.0);
1264 		else
1265 			invalid.InsetBy(0.0, -2.0);
1266 
1267 		Invalidate(invalid);
1268 	}
1269 }
1270 
1271 
1272 float
_ValueFor(BPoint where) const1273 BScrollBar::_ValueFor(BPoint where) const
1274 {
1275 	BRect bounds = Bounds();
1276 	bounds.InsetBy(1.0f, 1.0f);
1277 
1278 	float offset;
1279 	float thumbSize;
1280 	float maxSize;
1281 	float buttonSize;
1282 
1283 	if (fOrientation == B_VERTICAL) {
1284 		offset = where.y;
1285 		thumbSize = fPrivateData->fThumbFrame.Height();
1286 		maxSize = bounds.Height();
1287 		buttonSize = bounds.Width() + 1.0f;
1288 	} else {
1289 		offset = where.x;
1290 		thumbSize = fPrivateData->fThumbFrame.Width();
1291 		maxSize = bounds.Width();
1292 		buttonSize = bounds.Height() + 1.0f;
1293 	}
1294 
1295 	if (_DoubleArrows()) {
1296 		// subtract the size of four buttons
1297 		maxSize -= buttonSize * 4;
1298 		// convert point to inside of area between buttons
1299 		offset -= buttonSize * 2;
1300 	} else {
1301 		// subtract the size of two buttons
1302 		maxSize -= buttonSize * 2;
1303 		// convert point to inside of area between buttons
1304 		offset -= buttonSize;
1305 	}
1306 	// visual adjustments (room for darker line between thumb and buttons)
1307 	maxSize--;
1308 	offset++;
1309 
1310 	return roundf(fMin + (offset / (maxSize - thumbSize)
1311 		* (fMax - fMin + 1.0f)));
1312 }
1313 
1314 
1315 int32
_ButtonFor(BPoint where) const1316 BScrollBar::_ButtonFor(BPoint where) const
1317 {
1318 	BRect bounds = Bounds();
1319 	bounds.InsetBy(1.0f, 1.0f);
1320 
1321 	float buttonSize = fOrientation == B_VERTICAL
1322 		? bounds.Width() + 1.0f
1323 		: bounds.Height() + 1.0f;
1324 
1325 	BRect rect(bounds.left, bounds.top,
1326 		bounds.left + buttonSize, bounds.top + buttonSize);
1327 
1328 	if (fOrientation == B_VERTICAL) {
1329 		if (rect.Contains(where))
1330 			return ARROW1;
1331 
1332 		if (_DoubleArrows()) {
1333 			rect.OffsetBy(0.0, buttonSize);
1334 			if (rect.Contains(where))
1335 				return ARROW2;
1336 
1337 			rect.OffsetTo(bounds.left, bounds.bottom - 2 * buttonSize);
1338 			if (rect.Contains(where))
1339 				return ARROW3;
1340 		}
1341 		rect.OffsetTo(bounds.left, bounds.bottom - buttonSize);
1342 		if (rect.Contains(where))
1343 			return ARROW4;
1344 	} else {
1345 		if (rect.Contains(where))
1346 			return ARROW1;
1347 
1348 		if (_DoubleArrows()) {
1349 			rect.OffsetBy(buttonSize, 0.0);
1350 			if (rect.Contains(where))
1351 				return ARROW2;
1352 
1353 			rect.OffsetTo(bounds.right - 2 * buttonSize, bounds.top);
1354 			if (rect.Contains(where))
1355 				return ARROW3;
1356 		}
1357 		rect.OffsetTo(bounds.right - buttonSize, bounds.top);
1358 		if (rect.Contains(where))
1359 			return ARROW4;
1360 	}
1361 
1362 	return NOARROW;
1363 }
1364 
1365 
1366 BRect
_ButtonRectFor(int32 button) const1367 BScrollBar::_ButtonRectFor(int32 button) const
1368 {
1369 	BRect bounds = Bounds();
1370 	bounds.InsetBy(1.0f, 1.0f);
1371 
1372 	float buttonSize = fOrientation == B_VERTICAL
1373 		? bounds.Width() + 1.0f
1374 		: bounds.Height() + 1.0f;
1375 
1376 	BRect rect(bounds.left, bounds.top,
1377 		bounds.left + buttonSize - 1.0f, bounds.top + buttonSize - 1.0f);
1378 
1379 	if (fOrientation == B_VERTICAL) {
1380 		switch (button) {
1381 			case ARROW1:
1382 				break;
1383 
1384 			case ARROW2:
1385 				rect.OffsetBy(0.0, buttonSize);
1386 				break;
1387 
1388 			case ARROW3:
1389 				rect.OffsetTo(bounds.left, bounds.bottom - 2 * buttonSize + 1);
1390 				break;
1391 
1392 			case ARROW4:
1393 				rect.OffsetTo(bounds.left, bounds.bottom - buttonSize + 1);
1394 				break;
1395 		}
1396 	} else {
1397 		switch (button) {
1398 			case ARROW1:
1399 				break;
1400 
1401 			case ARROW2:
1402 				rect.OffsetBy(buttonSize, 0.0);
1403 				break;
1404 
1405 			case ARROW3:
1406 				rect.OffsetTo(bounds.right - 2 * buttonSize + 1, bounds.top);
1407 				break;
1408 
1409 			case ARROW4:
1410 				rect.OffsetTo(bounds.right - buttonSize + 1, bounds.top);
1411 				break;
1412 		}
1413 	}
1414 
1415 	return rect;
1416 }
1417 
1418 
1419 void
_UpdateTargetValue(BPoint where)1420 BScrollBar::_UpdateTargetValue(BPoint where)
1421 {
1422 	if (fOrientation == B_VERTICAL) {
1423 		fPrivateData->fStopValue = _ValueFor(BPoint(where.x, where.y
1424 			- fPrivateData->fThumbFrame.Height() / 2.0));
1425 	} else {
1426 		fPrivateData->fStopValue = _ValueFor(BPoint(where.x
1427 			- fPrivateData->fThumbFrame.Width() / 2.0, where.y));
1428 	}
1429 }
1430 
1431 
1432 void
_UpdateArrowButtons()1433 BScrollBar::_UpdateArrowButtons()
1434 {
1435 	bool upEnabled = fValue > fMin;
1436 	if (fPrivateData->fUpArrowsEnabled != upEnabled) {
1437 		fPrivateData->fUpArrowsEnabled = upEnabled;
1438 		Invalidate(_ButtonRectFor(ARROW1));
1439 		if (_DoubleArrows())
1440 			Invalidate(_ButtonRectFor(ARROW3));
1441 	}
1442 
1443 	bool downEnabled = fValue < fMax;
1444 	if (fPrivateData->fDownArrowsEnabled != downEnabled) {
1445 		fPrivateData->fDownArrowsEnabled = downEnabled;
1446 		Invalidate(_ButtonRectFor(ARROW4));
1447 		if (_DoubleArrows())
1448 			Invalidate(_ButtonRectFor(ARROW2));
1449 	}
1450 }
1451 
1452 
1453 status_t
control_scrollbar(scroll_bar_info * info,BScrollBar * bar)1454 control_scrollbar(scroll_bar_info* info, BScrollBar* bar)
1455 {
1456 	if (bar == NULL || info == NULL)
1457 		return B_BAD_VALUE;
1458 
1459 	if (bar->fPrivateData->fScrollBarInfo.double_arrows
1460 			!= info->double_arrows) {
1461 		bar->fPrivateData->fScrollBarInfo.double_arrows = info->double_arrows;
1462 
1463 		int8 multiplier = (info->double_arrows) ? 1 : -1;
1464 
1465 		if (bar->fOrientation == B_VERTICAL) {
1466 			bar->fPrivateData->fThumbFrame.OffsetBy(0, multiplier
1467 				* B_H_SCROLL_BAR_HEIGHT);
1468 		} else {
1469 			bar->fPrivateData->fThumbFrame.OffsetBy(multiplier
1470 				* B_V_SCROLL_BAR_WIDTH, 0);
1471 		}
1472 	}
1473 
1474 	bar->fPrivateData->fScrollBarInfo.proportional = info->proportional;
1475 
1476 	// TODO: Figure out how proportional relates to the size of the thumb
1477 
1478 	// TODO: Add redraw code to reflect the changes
1479 
1480 	if (info->knob >= 0 && info->knob <= 2)
1481 		bar->fPrivateData->fScrollBarInfo.knob = info->knob;
1482 	else
1483 		return B_BAD_VALUE;
1484 
1485 	if (info->min_knob_size >= SCROLL_BAR_MINIMUM_KNOB_SIZE
1486 			&& info->min_knob_size <= SCROLL_BAR_MAXIMUM_KNOB_SIZE) {
1487 		bar->fPrivateData->fScrollBarInfo.min_knob_size = info->min_knob_size;
1488 	} else
1489 		return B_BAD_VALUE;
1490 
1491 	return B_OK;
1492 }
1493 
1494 
1495 BSize
_MinSize() const1496 BScrollBar::_MinSize() const
1497 {
1498 	BSize minSize;
1499 	if (fOrientation == B_HORIZONTAL) {
1500 		minSize.width = 2 * B_V_SCROLL_BAR_WIDTH
1501 			+ 2 * fPrivateData->fScrollBarInfo.min_knob_size;
1502 		minSize.height = B_H_SCROLL_BAR_HEIGHT;
1503 	} else {
1504 		minSize.width = B_V_SCROLL_BAR_WIDTH;
1505 		minSize.height = 2 * B_H_SCROLL_BAR_HEIGHT
1506 			+ 2 * fPrivateData->fScrollBarInfo.min_knob_size;
1507 	}
1508 
1509 	return minSize;
1510 }
1511