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