xref: /haiku/src/kits/interface/ScrollBar.cpp (revision 0b2dbe7d46ee888392907c60131b7f7652314175)
1 //------------------------------------------------------------------------------
2 //	Copyright (c) 2001-2005 Haiku
3 //
4 //	Permission is hereby granted, free of charge, to any person obtaining a
5 //	copy of this software and associated documentation files (the "Software"),
6 //	to deal in the Software without restriction, including without limitation
7 //	the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 //	and/or sell copies of the Software, and to permit persons to whom the
9 //	Software is furnished to do so, subject to the following conditions:
10 //
11 //	The above copyright notice and this permission notice shall be included in
12 //	all copies or substantial portions of the Software.
13 //
14 //	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 //	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 //	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 //	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 //	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19 //	FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20 //	DEALINGS IN THE SOFTWARE.
21 //
22 //	File Name:		ScrollBar.cpp
23 //	Author:			Marc Flerackers (mflerackers@androme.be)
24 //					DarkWyrm (bpmagic@columbus.rr.com)
25 //	Description:	Client-side class for scrolling
26 //
27 //------------------------------------------------------------------------------
28 #include <Message.h>
29 #include <OS.h>
30 #include <ScrollBar.h>
31 #include <Window.h>
32 
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h>
36 
37 //#define TEST_MODE
38 
39 typedef enum {
40 	ARROW_LEFT = 0,
41 	ARROW_RIGHT,
42 	ARROW_UP,
43 	ARROW_DOWN,
44 	ARROW_NONE
45 } arrow_direction;
46 
47 #define SBC_SCROLLBYVALUE 0
48 #define SBC_SETDOUBLE 1
49 #define SBC_SETPROPORTIONAL 2
50 #define SBC_SETSTYLE 3
51 
52 // Quick constants for determining which arrow is down and are defined with respect
53 // to double arrow mode. ARROW1 and ARROW4 refer to the outer pair of arrows and
54 // ARROW2 and ARROW3 refer to the inner ones. ARROW1 points left/up and ARROW4
55 // points right/down.
56 #define ARROW1 0
57 #define ARROW2 1
58 #define ARROW3 2
59 #define ARROW4 3
60 #define NOARROW -1
61 
62 // Because the R5 version kept a lot of data on server-side, we need to kludge our way
63 // into binary compatibility
64 class BScrollBar::Private {
65 public:
66 	Private()
67 		:
68 		fEnabled(true),
69 		fRepeaterThread(-1),
70 		fExitRepeater(false),
71 		fTracking(false),
72 		fThumbInc(1),
73 		fArrowDown(ARROW_NONE),
74 		fButtonDown(NOARROW)
75 	{
76 		fThumbFrame.Set(0, 0, B_V_SCROLL_BAR_WIDTH, B_H_SCROLL_BAR_HEIGHT);
77 		fMousePos.Set(0,0);
78 
79 		#ifdef TEST_MODE
80 			fScrollBarInfo.proportional = true;
81 			fScrollBarInfo.double_arrows = false;
82 			fScrollBarInfo.knob = 0;
83 			fScrollBarInfo.min_knob_size = 14;
84 		#else
85 			get_scroll_bar_info(&fScrollBarInfo);
86 		#endif
87 	}
88 
89 	~Private()
90 	{
91 		if (fRepeaterThread >= 0) {
92 			status_t dummy;
93 			fExitRepeater = true;
94 			wait_for_thread(fRepeaterThread, &dummy);
95 		}
96 	}
97 
98 	void DrawScrollBarButton(BScrollBar *owner, arrow_direction direction,
99 		const BPoint &offset, bool down = false);
100 
101 	static int32 ButtonRepeaterThread(void *data);
102 
103 	bool fEnabled;
104 
105 	// TODO: This should be a static, initialized by
106 	// _init_interface_kit() at application startup-time,
107 	// like BMenu::sMenuInfo
108 	scroll_bar_info fScrollBarInfo;
109 
110 	thread_id fRepeaterThread;
111 	bool fExitRepeater;
112 
113 	BRect fThumbFrame;
114 	bool fTracking;
115 	BPoint fMousePos;
116 	float fThumbInc;
117 
118 	arrow_direction fArrowDown;
119 	int8 fButtonDown;
120 };
121 
122 
123 // This thread is spawned when a button is initially pushed and repeatedly scrolls
124 // the scrollbar by a little bit after a short delay
125 int32
126 BScrollBar::Private::ButtonRepeaterThread(void *data)
127 {
128 	BScrollBar *scrollBar = static_cast<BScrollBar *>(data);
129 	BRect oldframe(scrollBar->fPrivateData->fThumbFrame);
130 //	BRect update(sb->fPrivateData->fThumbFrame);
131 
132 	snooze(250000);
133 
134 	bool exitval = false;
135 	status_t returnval;
136 
137 	scrollBar->Window()->Lock();
138 	exitval = scrollBar->fPrivateData->fExitRepeater;
139 	scrollBar->Window()->Unlock();
140 
141 	float scrollvalue = 0;
142 
143 	if (scrollBar->fPrivateData->fArrowDown == ARROW_LEFT
144 		|| scrollBar->fPrivateData->fArrowDown == ARROW_UP)
145 		scrollvalue = -scrollBar->fSmallStep;
146 	else if (scrollBar->fPrivateData->fArrowDown != ARROW_NONE)
147 		scrollvalue = scrollBar->fSmallStep;
148 	else
149 		exitval = true;
150 
151 	while (!exitval) {
152 		oldframe = scrollBar->fPrivateData->fThumbFrame;
153 
154 		scrollBar->Window()->Lock();
155 		returnval = scroll_by_value(scrollvalue, scrollBar);
156 		scrollBar->Window()->Unlock();
157 
158 		snooze(50000);
159 
160 		scrollBar->Window()->Lock();
161 		exitval = scrollBar->fPrivateData->fExitRepeater;
162 
163 		if (returnval == B_OK) {
164 			scrollBar->CopyBits(oldframe, scrollBar->fPrivateData->fThumbFrame);
165 
166 			// TODO: Redraw the old area here
167 
168 			scrollBar->ValueChanged(scrollBar->fValue);
169 		}
170 		scrollBar->Window()->Unlock();
171 	}
172 
173 	scrollBar->Window()->Lock();
174 	scrollBar->fPrivateData->fExitRepeater = false;
175 	scrollBar->fPrivateData->fRepeaterThread = -1;
176 	scrollBar->Window()->Unlock();
177 
178 	return 0;
179 }
180 
181 
182 BScrollBar::BScrollBar(BRect frame,const char *name,BView *target,float min,
183 		float max,orientation direction)
184  : BView(frame, name, B_FOLLOW_NONE, B_WILL_DRAW),
185  	fMin(min),
186 	fMax(max),
187 	fSmallStep(1),
188 	fLargeStep(10),
189 	fValue(0),
190 	fTarget(target),
191 	fOrientation(direction)
192 {
193 	SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
194 
195 	fPrivateData = new BScrollBar::Private;
196 
197 	if (fTarget) {
198 		fTargetName = strdup(fTarget->Name());
199 
200 		// TODO: theoretically, we should also set the target BView's scrollbar
201 		// pointer here
202 	}
203 	else
204 		fTargetName=NULL;
205 
206 	if (direction == B_VERTICAL) {
207 		if (frame.Width() > B_V_SCROLL_BAR_WIDTH)
208 			ResizeTo(B_V_SCROLL_BAR_WIDTH, frame.Height() + 1);
209 
210 		fPrivateData->fThumbFrame.bottom = fPrivateData->fScrollBarInfo.min_knob_size;
211 		if (fPrivateData->fScrollBarInfo.double_arrows)
212 			fPrivateData->fThumbFrame.OffsetBy(0, (B_H_SCROLL_BAR_HEIGHT + 1) * 2);
213 		else
214 			fPrivateData->fThumbFrame.OffsetBy(0, B_H_SCROLL_BAR_HEIGHT + 1);
215 	} else {
216 		if (frame.Height() > B_H_SCROLL_BAR_HEIGHT)
217 			ResizeTo(frame.Width() + 1, B_H_SCROLL_BAR_HEIGHT);
218 
219 		fPrivateData->fThumbFrame.right = fPrivateData->fScrollBarInfo.min_knob_size;
220 		if (fPrivateData->fScrollBarInfo.double_arrows)
221 			fPrivateData->fThumbFrame.OffsetBy((B_V_SCROLL_BAR_WIDTH + 1) * 2, 0);
222 		else
223 			fPrivateData->fThumbFrame.OffsetBy(B_V_SCROLL_BAR_WIDTH + 1, 0);
224 	}
225 
226 
227 	SetResizingMode((direction == B_VERTICAL) ?
228 		B_FOLLOW_TOP_BOTTOM | B_FOLLOW_RIGHT :
229 		B_FOLLOW_LEFT_RIGHT | B_FOLLOW_BOTTOM );
230 
231 }
232 
233 
234 BScrollBar::BScrollBar(BMessage *data)
235  : BView(data)
236 {
237 }
238 
239 
240 BScrollBar::~BScrollBar()
241 {
242 	delete fPrivateData;
243 	free(fTargetName);
244 
245 	// TODO: Disconnect from target
246 }
247 
248 
249 BArchivable *
250 BScrollBar::Instantiate(BMessage *data)
251 {
252 	// TODO: Implement
253 	return NULL;
254 }
255 
256 
257 status_t
258 BScrollBar::Archive(BMessage *data, bool deep) const
259 {
260 	BView::Archive(data,deep);
261 	data->AddFloat("_range",fMin);
262 	data->AddFloat("_range",fMax);
263 	data->AddFloat("_steps",fSmallStep);
264 	data->AddFloat("_steps",fLargeStep);
265 	data->AddFloat("_val",fValue);
266 	data->AddInt32("_orient",(int32)fOrientation);
267 	data->AddInt32("_prop",fProportion);
268 
269 	return B_OK;
270 }
271 
272 
273 void
274 BScrollBar::AttachedToWindow()
275 {
276 	// R5's SB contacts the server if fValue!=0. I *think* we don't need to do anything here...
277 }
278 
279 
280 void
281 BScrollBar::SetValue(float value)
282 {
283 	if(value > fMax)
284 		value = fMax;
285 	if(value < fMin)
286 		value = fMin;
287 
288 	fValue = value;
289 	if (Window())
290 		Invalidate();
291 
292 	ValueChanged(fValue);
293 }
294 
295 
296 float
297 BScrollBar::Value() const
298 {
299 	return fValue;
300 }
301 
302 
303 void
304 BScrollBar::SetProportion(float value)
305 {
306 	fProportion = value;
307 }
308 
309 
310 float
311 BScrollBar::Proportion() const
312 {
313 	return fProportion;
314 }
315 
316 
317 void
318 BScrollBar::ValueChanged(float newValue)
319 {
320 	// TODO: Implement
321 /*
322 	From the BeBook:
323 
324 Responds to a notification that the value of the scroll bar has changed to
325 newValue. For a horizontal scroll bar, this function interprets newValue
326 as the coordinate value that should be at the left side of the target
327 view's bounds rectangle. For a vertical scroll bar, it interprets
328 newValue as the coordinate value that should be at the top of the rectangle.
329 It calls ScrollTo() to scroll the target's contents into position, unless
330 they have already been scrolled.
331 
332 ValueChanged() is called as the result both of user actions
333 (B_VALUE_CHANGED messages received from the Application Server) and of
334 programmatic ones. Programmatically, scrolling can be initiated by the
335 target view (calling ScrollTo()) or by the BScrollBar
336 (calling SetValue() or SetRange()).
337 
338 In all these cases, the target view and the scroll bars need to be kept
339 in synch. This is done by a chain of function calls: ValueChanged() calls
340 ScrollTo(), which in turn calls SetValue(), which then calls
341 ValueChanged() again. It's up to ValueChanged() to get off this
342 merry-go-round, which it does by checking the target view's bounds
343 rectangle. If newValue already matches the left or top side of the
344 bounds rectangle, if forgoes calling ScrollTo().
345 
346 ValueChanged() does nothing if a target BView hasn't been set—or
347 if the target has been set by name, but the name doesn't correspond to
348 an actual BView within the scroll bar's window.
349 
350 */
351 }
352 
353 
354 void
355 BScrollBar::SetRange(float min, float max)
356 {
357 	fMin = min;
358 	fMax = max;
359 
360 	if (fValue > fMax)
361 		fValue = fMax;
362 	else if (fValue < fMin)
363 		fValue = fMin;
364 
365 	Invalidate();
366 
367 	// Just a sort-of hack for now. ValueChanged is called, but with
368 	// what value??
369 	ValueChanged(fValue);
370 }
371 
372 
373 void
374 BScrollBar::GetRange(float *min, float *max) const
375 {
376 	if (min != NULL)
377 		*min = fMin;
378 	if (max != NULL)
379 		*max = fMax;
380 }
381 
382 
383 void
384 BScrollBar::SetSteps(float smallStep, float largeStep)
385 {
386 	// Under R5, steps can be set only after being attached to a window, probably because
387 	// the data is kept server-side. We'll just remove that limitation... :P
388 
389 	// The BeBook also says that we need to specify an integer value even though the step
390 	// values are floats. For the moment, we'll just make sure that they are integers
391 	fSmallStep = (int32)smallStep;
392 	fLargeStep = (int32)largeStep;
393 
394 	// TODO: test use of fractional values and make them work properly if they don't
395 }
396 
397 
398 void
399 BScrollBar::GetSteps(float *smallStep, float *largeStep) const
400 {
401 	if (smallStep != NULL)
402 		*smallStep = fSmallStep;
403 	if (largeStep)
404 		*largeStep = fLargeStep;
405 }
406 
407 
408 void
409 BScrollBar::SetTarget(BView *target)
410 {
411 	fTarget = target;
412 	free(fTargetName);
413 
414 	if (fTarget) {
415 		fTargetName = strdup(target->Name());
416 
417 		if (Orientation() == B_VERTICAL)
418 			fTarget->fVerScroller = this;
419 		else
420 			fTarget->fHorScroller = this;
421 	} else
422 		fTargetName = NULL;
423 }
424 
425 
426 void
427 BScrollBar::SetTarget(const char *targetName)
428 {
429 	if (!targetName)
430 		return;
431 
432 	if (!Window())
433 		debugger("Method requires window and doesn't have one");
434 
435 	BView *target = Window()->FindView(targetName);
436 	if (target)
437 		SetTarget(target);
438 }
439 
440 
441 BView *
442 BScrollBar::Target() const
443 {
444 	return fTarget;
445 }
446 
447 
448 orientation
449 BScrollBar::Orientation() const
450 {
451 	return fOrientation;
452 }
453 
454 
455 void
456 BScrollBar::MessageReceived(BMessage *msg)
457 {
458 	switch(msg->what) {
459 		case B_VALUE_CHANGED:
460 		{
461 			int32 value;
462 			if (msg->FindInt32("value", &value) == B_OK)
463 				ValueChanged(value);
464 			break;
465 		}
466 		default:
467 			BView::MessageReceived(msg);
468 			break;
469 	}
470 }
471 
472 
473 void
474 BScrollBar::MouseDown(BPoint pt)
475 {
476 	if (!(fMin == 0 && fMax == 0)) { // if fEnabled
477 
478 		// Hit test for thumb
479 		if (fPrivateData->fThumbFrame.Contains(pt)) {
480 			fPrivateData->fTracking = true;
481 			fPrivateData->fMousePos = pt;
482 			// TODO: empty event mask ? Is this okay ?
483 			SetMouseEventMask(0, B_LOCK_WINDOW_FOCUS);
484 			Draw(fPrivateData->fThumbFrame);
485 			return;
486 		}
487 
488 		BRect buttonrect(0, 0, B_V_SCROLL_BAR_WIDTH, B_H_SCROLL_BAR_HEIGHT);
489 		float scrollval = 0;
490 		status_t returnval = B_ERROR;
491 
492 		// Hit test for arrow buttons
493 		if (fOrientation == B_VERTICAL) {
494 			if (buttonrect.Contains(pt)) {
495 				scrollval = -fSmallStep;
496 				fPrivateData->fArrowDown = ARROW_UP;
497 				fPrivateData->fButtonDown = ARROW1;
498 				returnval = scroll_by_value(scrollval, this);
499 
500 				if (returnval == B_OK) {
501 					Draw(buttonrect);
502 					ValueChanged(fValue);
503 
504 					if (fPrivateData->fRepeaterThread == -1) {
505 						fPrivateData->fExitRepeater = false;
506 						fPrivateData->fRepeaterThread = spawn_thread(fPrivateData->ButtonRepeaterThread,
507 							"scroll repeater", B_NORMAL_PRIORITY, this);
508 						resume_thread(fPrivateData->fRepeaterThread);
509 					}
510 				}
511 
512 				return;
513 
514 			}
515 
516 			buttonrect.OffsetTo(0, Bounds().Height() - (B_H_SCROLL_BAR_HEIGHT));
517 			if (buttonrect.Contains(pt)) {
518 				scrollval = fSmallStep;
519 				fPrivateData->fArrowDown = ARROW_DOWN;
520 				fPrivateData->fButtonDown = ARROW4;
521 				returnval = scroll_by_value(scrollval, this);
522 
523 				if (returnval == B_OK) {
524 					Draw(buttonrect);
525 					ValueChanged(fValue);
526 
527 					if (fPrivateData->fRepeaterThread == -1) {
528 						fPrivateData->fExitRepeater = false;
529 						fPrivateData->fRepeaterThread = spawn_thread(fPrivateData->ButtonRepeaterThread,
530 							"scroll repeater", B_NORMAL_PRIORITY, this);
531 						resume_thread(fPrivateData->fRepeaterThread);
532 					}
533 				}
534 				return;
535 			}
536 
537 			if (fPrivateData->fScrollBarInfo.double_arrows) {
538 				buttonrect.OffsetTo(0, B_H_SCROLL_BAR_HEIGHT + 1);
539 				if (buttonrect.Contains(pt)) {
540 					scrollval = fSmallStep;
541 					fPrivateData->fArrowDown = ARROW_DOWN;
542 					fPrivateData->fButtonDown = ARROW2;
543 					returnval = scroll_by_value(scrollval, this);
544 
545 					if (returnval == B_OK) {
546 						Draw(buttonrect);
547 						ValueChanged(fValue);
548 
549 						if (fPrivateData->fRepeaterThread == -1) {
550 							fPrivateData->fExitRepeater = false;
551 							fPrivateData->fRepeaterThread = spawn_thread(fPrivateData->ButtonRepeaterThread,
552 								"scroll repeater", B_NORMAL_PRIORITY, this);
553 							resume_thread(fPrivateData->fRepeaterThread);
554 						}
555 					}
556 					return;
557 				}
558 
559 				buttonrect.OffsetTo(0, Bounds().Height() - ((B_H_SCROLL_BAR_HEIGHT * 2) + 1));
560 				if (buttonrect.Contains(pt)) {
561 					scrollval = -fSmallStep;
562 					fPrivateData->fArrowDown = ARROW_UP;
563 					fPrivateData->fButtonDown = ARROW3;
564 					returnval = scroll_by_value(scrollval, this);
565 
566 					if (returnval == B_OK) {
567 						Draw(buttonrect);
568 						ValueChanged(fValue);
569 
570 						if (fPrivateData->fRepeaterThread == -1) {
571 							fPrivateData->fExitRepeater = false;
572 							fPrivateData->fRepeaterThread = spawn_thread(fPrivateData->ButtonRepeaterThread,
573 								"scroll repeater", B_NORMAL_PRIORITY, this);
574 							resume_thread(fPrivateData->fRepeaterThread);
575 						}
576 					}
577 					return;
578 
579 				}
580 			}
581 
582 			// TODO: add a repeater thread for large stepping and a call to it
583 
584 			if (pt.y < fPrivateData->fThumbFrame.top)
585 				scroll_by_value(-fLargeStep, this);  // do we not check the return value in these two cases like everywhere else?
586 			else
587 				scroll_by_value(fLargeStep, this);
588 		} else {
589 			if (buttonrect.Contains(pt)) {
590 				scrollval = -fSmallStep;
591 				fPrivateData->fArrowDown = ARROW_LEFT;
592 				fPrivateData->fButtonDown = ARROW1;
593 				returnval = scroll_by_value(scrollval, this);
594 
595 				if (returnval == B_OK) {
596 					Draw(buttonrect);
597 					ValueChanged(fValue);
598 
599 					if(fPrivateData->fRepeaterThread == -1) {
600 						fPrivateData->fExitRepeater = false;
601 						fPrivateData->fRepeaterThread = spawn_thread(fPrivateData->ButtonRepeaterThread,
602 							"scroll repeater", B_NORMAL_PRIORITY, this);
603 						resume_thread(fPrivateData->fRepeaterThread);
604 					}
605 				}
606 				return;
607 			}
608 
609 			buttonrect.OffsetTo(Bounds().Width() - (B_V_SCROLL_BAR_WIDTH), 0);
610 			if (buttonrect.Contains(pt)) {
611 				scrollval = fSmallStep;
612 				fPrivateData->fArrowDown = ARROW_RIGHT;
613 				fPrivateData->fButtonDown = ARROW4;
614 				returnval = scroll_by_value(scrollval, this);
615 
616 				if (returnval == B_OK) {
617 					Draw(buttonrect);
618 					ValueChanged(fValue);
619 
620 					if(fPrivateData->fRepeaterThread == -1) {
621 						fPrivateData->fExitRepeater = false;
622 						fPrivateData->fRepeaterThread = spawn_thread(fPrivateData->ButtonRepeaterThread,
623 							"scroll repeater", B_NORMAL_PRIORITY,this);
624 						resume_thread(fPrivateData->fRepeaterThread);
625 					}
626 				}
627 				return;
628 			}
629 
630 			if (fPrivateData->fScrollBarInfo.proportional) {
631 				buttonrect.OffsetTo(B_V_SCROLL_BAR_WIDTH + 1, 0);
632 				if (buttonrect.Contains(pt)) {
633 					scrollval = fSmallStep;
634 					fPrivateData->fButtonDown = ARROW2;
635 					fPrivateData->fArrowDown = ARROW_LEFT;
636 					returnval = scroll_by_value(scrollval, this);
637 
638 					if (returnval == B_OK) {
639 						Draw(buttonrect);
640 						ValueChanged(fValue);
641 
642 						if (fPrivateData->fRepeaterThread == -1) {
643 							fPrivateData->fExitRepeater = false;
644 							fPrivateData->fRepeaterThread = spawn_thread(fPrivateData->ButtonRepeaterThread,
645 								"scroll repeater", B_NORMAL_PRIORITY, this);
646 							resume_thread(fPrivateData->fRepeaterThread);
647 						}
648 					}
649 					return;
650 				}
651 
652 				buttonrect.OffsetTo(Bounds().Width() - ( (B_V_SCROLL_BAR_WIDTH * 2) + 1), 0);
653 				if (buttonrect.Contains(pt)) {
654 					scrollval = -fSmallStep;
655 					fPrivateData->fButtonDown = ARROW3;
656 					fPrivateData->fArrowDown = ARROW_RIGHT;
657 					returnval = scroll_by_value(scrollval, this);
658 
659 					if (returnval == B_OK) {
660 						Draw(buttonrect);
661 						ValueChanged(fValue);
662 
663 						if(fPrivateData->fRepeaterThread == -1) {
664 							fPrivateData->fExitRepeater = false;
665 							fPrivateData->fRepeaterThread = spawn_thread(fPrivateData->ButtonRepeaterThread,
666 								"scroll repeater", B_NORMAL_PRIORITY, this);
667 							resume_thread(fPrivateData->fRepeaterThread);
668 						}
669 					}
670 					return;
671 				}
672 			}
673 
674 			// We got this far, so apparently the user has clicked on something
675 			// that isn't the thumb or a scroll button, so scroll by a large step
676 
677 			// TODO: add a repeater thread for large stepping and a call to it
678 
679 
680 			if (pt.x < fPrivateData->fThumbFrame.left)
681 				scroll_by_value(-fLargeStep, this);  // do we not check the return value in these two cases like everywhere else?
682 			else
683 				scroll_by_value(fLargeStep, this);
684 
685 		}
686 		ValueChanged(fValue);
687 		Draw(Bounds());
688 	}
689 }
690 
691 
692 void
693 BScrollBar::MouseUp(BPoint pt)
694 {
695 	fPrivateData->fArrowDown = ARROW_NONE;
696 	fPrivateData->fButtonDown = NOARROW;
697 	fPrivateData->fExitRepeater = true;
698 
699 	// We'll be lazy here and just draw all the possible arrow regions for now...
700 	// TODO: optimize
701 
702 	BRect rect(0, 0, B_V_SCROLL_BAR_WIDTH, B_H_SCROLL_BAR_HEIGHT);
703 
704 	if (fOrientation == B_VERTICAL) {
705 		rect.bottom += B_H_SCROLL_BAR_HEIGHT + 1;
706 		Draw(rect);
707 		rect.OffsetTo(0, Bounds().Height() - ((B_H_SCROLL_BAR_HEIGHT * 2) + 1));
708 		Draw(rect);
709 	} else {
710 		rect.bottom += B_V_SCROLL_BAR_WIDTH + 1;
711 		Draw(rect);
712 		rect.OffsetTo(0, Bounds().Height() - ((B_V_SCROLL_BAR_WIDTH * 2) + 1));
713 		Draw(rect);
714 	}
715 
716 	if (fPrivateData->fTracking) {
717 		fPrivateData->fTracking = false;
718 		SetMouseEventMask(0, 0);
719 		Draw(fPrivateData->fThumbFrame);
720 	}
721 }
722 
723 
724 void
725 BScrollBar::MouseMoved(BPoint pt, uint32 transit, const BMessage *msg)
726 {
727 	if (!fPrivateData->fEnabled)
728 		return;
729 
730 	if (transit == B_EXITED_VIEW || transit == B_OUTSIDE_VIEW)
731 		MouseUp(fPrivateData->fMousePos);
732 
733 	if (fPrivateData->fTracking) {
734 		float delta;
735 		if (fOrientation == B_VERTICAL) {
736 			if( (pt.y > fPrivateData->fThumbFrame.bottom && fValue == fMax) ||
737 				(pt.y < fPrivateData->fThumbFrame.top && fValue == fMin) )
738 				return;
739 			delta = pt.y - fPrivateData->fMousePos.y;
740 		} else {
741 			if((pt.x > fPrivateData->fThumbFrame.right && fValue == fMax) ||
742 				(pt.x < fPrivateData->fThumbFrame.left && fValue == fMin))
743 				return;
744 			delta = pt.x - fPrivateData->fMousePos.x;
745 		}
746 		scroll_by_value(delta, this);  // do we not check the return value here?
747 		ValueChanged(fValue);
748 		Invalidate(Bounds());
749 		fPrivateData->fMousePos = pt;
750 	}
751 }
752 
753 
754 void
755 BScrollBar::DoScroll(float delta)
756 {
757 	if (!fTarget)
758 		return;
759 
760 	float scrollval;
761 
762 	if (delta > 0)
763 		scrollval = (fValue + delta <= fMax) ? delta : (fMax-fValue);
764 	else
765 		scrollval = (fValue - delta >= fMin) ? delta : (fValue - fMin);
766 
767 	if (fOrientation == B_VERTICAL) {
768 		fTarget->ScrollBy(0, scrollval);
769 		fPrivateData->fThumbFrame.OffsetBy(0, scrollval);
770 	} else {
771 		fTarget->ScrollBy(scrollval, 0);
772 		fPrivateData->fThumbFrame.OffsetBy(scrollval, 0);
773 	}
774 
775 	fValue += scrollval;
776 }
777 
778 
779 void
780 BScrollBar::DetachedFromWindow()
781 {
782 	fTarget = NULL;
783 	free(fTargetName);
784 	fTargetName = NULL;
785 }
786 
787 
788 void
789 BScrollBar::Draw(BRect updateRect)
790 {
791 	rgb_color light, dark, normal, panelColor;
792 	panelColor = ui_color(B_PANEL_BACKGROUND_COLOR);
793 	if (fPrivateData->fEnabled) {
794 		light = tint_color(panelColor, B_LIGHTEN_MAX_TINT);
795 		dark = tint_color(panelColor, B_DARKEN_3_TINT);
796 		normal = panelColor;
797 	} else {
798 		light = tint_color(panelColor, B_LIGHTEN_MAX_TINT);
799 		dark = tint_color(panelColor, B_DARKEN_3_TINT);
800 		normal = panelColor;
801 	}
802 
803 	// Draw main area
804 	SetHighColor(normal);
805 	FillRect(updateRect);
806 
807 	SetHighColor(dark);
808 	StrokeRect(Bounds());
809 
810 	SetDrawingMode(B_OP_OVER);
811 
812 	// Draw arrows
813 	BPoint buttonpt(0,0);
814 	if (fOrientation == B_HORIZONTAL) {
815 		fPrivateData->DrawScrollBarButton(this, ARROW_LEFT, buttonpt,
816 			fPrivateData->fButtonDown == ARROW1);
817 
818 		if (fPrivateData->fScrollBarInfo.double_arrows) {
819 			buttonpt.Set(B_V_SCROLL_BAR_WIDTH + 1, 0);
820 			fPrivateData->DrawScrollBarButton(this, ARROW_RIGHT, buttonpt,
821 				fPrivateData->fButtonDown == ARROW2);
822 
823 			buttonpt.Set(Bounds().Width() - ((B_V_SCROLL_BAR_WIDTH * 2) + 1), 0);
824 			fPrivateData->DrawScrollBarButton(this, ARROW_LEFT, buttonpt,
825 				fPrivateData->fButtonDown == ARROW3);
826 
827 		}
828 
829 		buttonpt.Set(Bounds().Width() - (B_V_SCROLL_BAR_WIDTH), 0);
830 		fPrivateData->DrawScrollBarButton(this, ARROW_RIGHT, buttonpt,
831 			fPrivateData->fButtonDown == ARROW4);
832 	} else {
833 		fPrivateData->DrawScrollBarButton(this, ARROW_UP, buttonpt,
834 			fPrivateData->fButtonDown == ARROW1);
835 
836 		if (fPrivateData->fScrollBarInfo.double_arrows) {
837 			buttonpt.Set(0,B_H_SCROLL_BAR_HEIGHT + 1);
838 			fPrivateData->DrawScrollBarButton(this, ARROW_DOWN, buttonpt,
839 				fPrivateData->fButtonDown == ARROW2);
840 
841 			buttonpt.Set(0,Bounds().Height() - ((B_H_SCROLL_BAR_HEIGHT * 2) + 1));
842 			fPrivateData->DrawScrollBarButton(this, ARROW_UP, buttonpt,
843 				fPrivateData->fButtonDown == ARROW3);
844 
845 		}
846 
847 		buttonpt.Set(0,Bounds().Height() - (B_H_SCROLL_BAR_HEIGHT));
848 		fPrivateData->DrawScrollBarButton(this, ARROW_DOWN, buttonpt,
849 			fPrivateData->fButtonDown == ARROW4);
850 	}
851 
852 	SetDrawingMode(B_OP_COPY);
853 
854 	// Draw scroll thumb
855 
856 	if (fPrivateData->fEnabled) {
857 		BRect rect(fPrivateData->fThumbFrame);
858 
859 		SetHighColor(dark);
860 		StrokeRect(fPrivateData->fThumbFrame);
861 
862 		rect.InsetBy(1,1);
863 		SetHighColor(tint_color(panelColor, B_DARKEN_2_TINT));
864 		StrokeLine(rect.LeftBottom(), rect.RightBottom());
865 		StrokeLine(rect.RightTop(), rect.RightBottom());
866 
867 		SetHighColor(light);
868 		StrokeLine(rect.LeftTop(), rect.RightTop());
869 		StrokeLine(rect.LeftTop(), rect.LeftBottom());
870 
871 		rect.InsetBy(1,1);
872 		if (fPrivateData->fTracking)
873 			SetHighColor(tint_color(normal, B_DARKEN_1_TINT));
874 		else
875 			SetHighColor(normal);
876 
877 		FillRect(rect);
878 	}
879 
880 	// TODO: Add the other thumb styles - dots and lines
881 }
882 
883 
884 void
885 BScrollBar::FrameMoved(BPoint new_position)
886 {
887 }
888 
889 
890 void
891 BScrollBar::FrameResized(float new_width, float new_height)
892 {
893 }
894 
895 
896 BHandler *
897 BScrollBar::ResolveSpecifier(BMessage *msg,int32 index,
898 		BMessage *specifier,int32 form,const char *property)
899 {
900 	// TODO: Implement
901 	return NULL;
902 }
903 
904 
905 void
906 BScrollBar::ResizeToPreferred()
907 {
908 }
909 
910 
911 void
912 BScrollBar::GetPreferredSize(float *width, float *height)
913 {
914 	if (fOrientation == B_VERTICAL)
915 		*width = B_V_SCROLL_BAR_WIDTH;
916 	else if (fOrientation == B_HORIZONTAL)
917 		*height = B_H_SCROLL_BAR_HEIGHT;
918 }
919 
920 
921 void
922 BScrollBar::MakeFocus(bool state)
923 {
924 	if (fTarget)
925 		fTarget->MakeFocus(state);
926 }
927 
928 
929 void
930 BScrollBar::AllAttached()
931 {
932 }
933 
934 
935 void
936 BScrollBar::AllDetached()
937 {
938 }
939 
940 
941 status_t
942 BScrollBar::GetSupportedSuites(BMessage *data)
943 {
944 	return B_ERROR;
945 }
946 
947 
948 status_t
949 BScrollBar::Perform(perform_code d, void *arg)
950 {
951 	return BView::Perform(d, arg);
952 }
953 
954 
955 void BScrollBar::_ReservedScrollBar1() {}
956 void BScrollBar::_ReservedScrollBar2() {}
957 void BScrollBar::_ReservedScrollBar3() {}
958 void BScrollBar::_ReservedScrollBar4() {}
959 
960 
961 BScrollBar &
962 BScrollBar::operator=(const BScrollBar &)
963 {
964 	return *this;
965 }
966 
967 /*
968 	scroll_by_value: increment or decrement scrollbar's value by a certain amount.
969 
970 	Returns B_OK if everthing went well and the caller is to redraw the scrollbar,
971 			B_ERROR if the caller doesn't need to do anything, or
972 			B_BAD_VALUE if data is NULL.
973 */
974 status_t scroll_by_value(float valueByWhichToScroll, BScrollBar *bar)
975 {
976 	if(!bar)
977 		return B_BAD_VALUE;
978 
979 	if(valueByWhichToScroll==0)
980 		return B_ERROR;
981 
982 	if(bar->fOrientation==B_VERTICAL)
983 	{
984 		if(valueByWhichToScroll<0)
985 		{
986 			if(bar->fValue + valueByWhichToScroll >= bar->fMin)
987 			{
988 				bar->fValue += valueByWhichToScroll;
989 				if(bar->fTarget)
990 					bar->fTarget->ScrollBy(0,valueByWhichToScroll);
991 				bar->fPrivateData->fThumbFrame.OffsetBy(0,valueByWhichToScroll);
992 				bar->fValue--;
993 				return B_OK;
994 			}
995 			// fall through to return B_ERROR
996 		}
997 		else
998 		{
999 			if(bar->fValue + valueByWhichToScroll <= bar->fMax)
1000 			{
1001 				bar->fValue += valueByWhichToScroll;
1002 				if(bar->fTarget)
1003 					bar->fTarget->ScrollBy(0,valueByWhichToScroll);
1004 				bar->fPrivateData->fThumbFrame.OffsetBy(0,valueByWhichToScroll);
1005 				bar->fValue++;
1006 				return B_OK;
1007 			}
1008 			// fall through to return B_ERROR
1009 		}
1010 	}
1011 	else
1012 	{
1013 		if(valueByWhichToScroll<0)
1014 		{
1015 			if(bar->fValue + valueByWhichToScroll >= bar->fMin)
1016 			{
1017 				bar->fValue += valueByWhichToScroll;
1018 				if(bar->fTarget)
1019 					bar->fTarget->ScrollBy(valueByWhichToScroll,0);
1020 				bar->fPrivateData->fThumbFrame.OffsetBy(valueByWhichToScroll,0);
1021 				bar->fValue--;
1022 				return B_OK;
1023 			}
1024 			// fall through to return B_ERROR
1025 		}
1026 		else
1027 		{
1028 			if(bar->fValue + valueByWhichToScroll <= bar->fMax)
1029 			{
1030 				bar->fValue += valueByWhichToScroll;
1031 				if(bar->fTarget)
1032 					bar->fTarget->ScrollBy(valueByWhichToScroll,0);
1033 				bar->fPrivateData->fThumbFrame.OffsetBy(valueByWhichToScroll,0);
1034 				bar->fValue++;
1035 				return B_OK;
1036 			}
1037 			// fall through to return B_ERROR
1038 		}
1039 	}
1040 	return B_ERROR;
1041 }
1042 
1043 /*
1044 	This cheat function will allow the scrollbar prefs app to act like R5's and
1045 	perform other stuff without mucking around with the virtual tables.
1046 	// TODO: Using private friend methods for this is not nice, we should use a
1047 	// custom control in the pref app instead
1048 
1049 	B_BAD_VALUE is returned when a NULL scrollbar pointer is passed to it.
1050 
1051 	The scroll_bar_info struct is read and used to re-style the given BScrollBar.
1052 */
1053 status_t
1054 control_scrollbar(scroll_bar_info *info, BScrollBar *bar)
1055 {
1056 	if (!bar || !info)
1057 		return B_BAD_VALUE;
1058 
1059 	if (bar->fPrivateData->fScrollBarInfo.double_arrows != info->double_arrows) {
1060 		bar->fPrivateData->fScrollBarInfo.double_arrows = info->double_arrows;
1061 
1062 		int8 multiplier = (info->double_arrows) ? 1 : -1;
1063 
1064 		if (bar->fOrientation == B_VERTICAL)
1065 			bar->fPrivateData->fThumbFrame.OffsetBy(0, multiplier * B_H_SCROLL_BAR_HEIGHT);
1066 		else
1067 			bar->fPrivateData->fThumbFrame.OffsetBy(multiplier * B_V_SCROLL_BAR_WIDTH, 0);
1068 	}
1069 
1070 	bar->fPrivateData->fScrollBarInfo.proportional = info->proportional;
1071 
1072 	// TODO: Figure out how proportional relates to the size of the thumb
1073 
1074 	// TODO: Add redraw code to reflect the changes
1075 
1076 	if (info->knob >= 0 && info->knob <= 2)
1077 		bar->fPrivateData->fScrollBarInfo.knob = info->knob;
1078 	else
1079 		return B_BAD_VALUE;
1080 
1081 	if (info->min_knob_size >= SCROLL_BAR_MINIMUM_KNOB_SIZE && info->min_knob_size <= SCROLL_BAR_MAXIMUM_KNOB_SIZE)
1082 		bar->fPrivateData->fScrollBarInfo.min_knob_size = info->min_knob_size;
1083 	else
1084 		return B_BAD_VALUE;
1085 
1086 	return B_OK;
1087 }
1088 
1089 
1090 void
1091 BScrollBar::Private::DrawScrollBarButton(BScrollBar *owner, arrow_direction direction,
1092 	const BPoint &offset, bool down)
1093 {
1094 	// Another hack for code size
1095 
1096 	BRect r(offset.x, offset.y, offset.x + 14,offset.y + 14);
1097 
1098 	rgb_color c = ui_color(B_PANEL_BACKGROUND_COLOR);
1099 	rgb_color light, dark, normal,arrow,arrow2;
1100 
1101 	if (down) {
1102 		light = tint_color(c, B_DARKEN_3_TINT);
1103 		arrow2 = dark = tint_color(c, B_LIGHTEN_MAX_TINT);
1104 		normal = c;
1105 		arrow = tint_color(c, B_DARKEN_MAX_TINT);
1106 
1107 	} else {
1108 		bool use_enabled_colors = fEnabled;
1109 
1110 		// Add a usability perk - disable buttons if they would not do anything -
1111 		// like a left arrow if the value==fMin
1112 		if ((direction == ARROW_LEFT || direction == ARROW_UP) &&
1113 					(owner->fValue == owner->fMin) )
1114 			use_enabled_colors = false;
1115 		else if ((direction == ARROW_RIGHT || direction == ARROW_DOWN) &&
1116 					(owner->fValue == owner->fMax) )
1117 			use_enabled_colors = false;
1118 
1119 
1120 		if (use_enabled_colors) {
1121 			arrow2 = light = tint_color(c, B_LIGHTEN_MAX_TINT);
1122 			dark = tint_color(c, B_DARKEN_3_TINT);
1123 			normal = c;
1124 			arrow = tint_color(c, B_DARKEN_MAX_TINT);
1125 		} else {
1126 			arrow2 = light = tint_color(c, B_LIGHTEN_1_TINT);
1127 			dark = tint_color(c, B_DARKEN_1_TINT);
1128 			normal = c;
1129 			arrow = tint_color(c, B_DARKEN_1_TINT);
1130 		}
1131 	}
1132 
1133 	BPoint tri1, tri2, tri3;
1134 
1135 	switch (direction) {
1136 		case ARROW_LEFT:
1137 		{
1138 			tri1.Set(r.left + 3, (r.top + r.bottom) /2 );
1139 			tri2.Set(r.right - 3, r.top + 3);
1140 			tri3.Set(r.right - 3, r.bottom - 3);
1141 			break;
1142 		}
1143 		case ARROW_RIGHT:
1144 		{
1145 			tri1.Set(r.left + 3, r.bottom - 3);
1146 			tri2.Set(r.left + 3, r.top + 3);
1147 			tri3.Set(r.right-3, (r.top + r.bottom) / 2);
1148 			break;
1149 		}
1150 		case ARROW_UP:
1151 		{
1152 			tri1.Set(r.left + 3, r.bottom - 3);
1153 			tri2.Set((r.left + r.right) / 2, r.top + 3);
1154 			tri3.Set(r.right - 3, r.bottom - 3);
1155 			break;
1156 		}
1157 		default:
1158 		{
1159 			tri1.Set(r.left + 3, r.top + 3);
1160 			tri2.Set(r.right - 3, r.top + 3);
1161 			tri3.Set((r.left + r.right) / 2, r.bottom - 3);
1162 			break;
1163 		}
1164 	}
1165 
1166 	r.InsetBy(1, 1);
1167 	owner->SetHighColor(normal);
1168 	owner->FillRect(r);
1169 
1170 	owner->SetHighColor(arrow);
1171 	owner->FillTriangle(tri1, tri2, tri3);
1172 
1173 	r.InsetBy(-1, -1);
1174 	owner->SetHighColor(dark);
1175 	owner->StrokeLine(r.LeftBottom(), r.RightBottom());
1176 	owner->StrokeLine(r.RightTop(), r.RightBottom());
1177 	owner->StrokeLine(tri2, tri3);
1178 	owner->StrokeLine(tri1, tri3);
1179 
1180 	owner->SetHighColor(light);
1181 	owner->StrokeLine(r.LeftTop(), r.RightTop());
1182 	owner->StrokeLine(r.LeftTop(), r.LeftBottom());
1183 
1184 	owner->SetHighColor(arrow2);
1185 	owner->StrokeLine(tri1, tri2);
1186 }
1187