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