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