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