1 /* 2 * Copyright 2001-2005, Haiku. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Marc Flerackers (mflerackers@androme.be) 7 * Mike Wilber 8 * Stefano Ceccherini (burton666@libero.it) 9 * Ivan Tonizza 10 * Stephan Aßmus, <superstippi@gmx.de> 11 */ 12 13 #include <Button.h> 14 #include <Font.h> 15 #include <String.h> 16 #include <Window.h> 17 18 BButton::BButton(BRect frame, const char *name, const char *label, BMessage *message, 19 uint32 resizingMode, uint32 flags) 20 : BControl(frame, name, label, message, resizingMode, flags |= B_WILL_DRAW), 21 fDrawAsDefault(false) 22 { 23 // Resize to minimum height if needed 24 font_height fh; 25 GetFontHeight(&fh); 26 float minHeight = 12.0f + (float)ceil(fh.ascent + fh.descent); 27 if (Bounds().Height() < minHeight) 28 ResizeTo(Bounds().Width(), minHeight); 29 } 30 31 32 BButton::~BButton() 33 { 34 } 35 36 37 BButton::BButton(BMessage *archive) 38 : BControl (archive) 39 { 40 if (archive->FindBool("_default", &fDrawAsDefault) != B_OK) 41 fDrawAsDefault = false; 42 } 43 44 45 BArchivable * 46 BButton::Instantiate(BMessage *archive) 47 { 48 if (validate_instantiation(archive, "BButton")) 49 return new BButton(archive); 50 else 51 return NULL; 52 } 53 54 55 status_t 56 BButton::Archive(BMessage* archive, bool deep) const 57 { 58 status_t err = BControl::Archive(archive, deep); 59 60 if (err != B_OK) 61 return err; 62 63 if (IsDefault()) 64 err = archive->AddBool("_default", true); 65 66 return err; 67 } 68 69 70 void 71 BButton::Draw(BRect updateRect) 72 { 73 font_height fh; 74 GetFontHeight(&fh); 75 76 const BRect bounds = Bounds(); 77 BRect rect = bounds; 78 79 const bool enabled = IsEnabled(); 80 const bool pushed = Value() == B_CONTROL_ON; 81 #if 0 82 // Default indicator 83 if (IsDefault()) 84 rect = DrawDefault(rect, enabled); 85 else 86 rect.InsetBy(1.0f, 1.0f); 87 88 BRect fillArea = rect; 89 fillArea.InsetBy(3.0f, 3.0f); 90 91 BString text = Label(); 92 93 #if 1 94 // Label truncation 95 BFont font; 96 GetFont(&font); 97 font.TruncateString(&text, B_TRUNCATE_END, fillArea.Width()); 98 #endif 99 100 // Label position 101 const float stringWidth = StringWidth(text.String()); 102 const float x = (bounds.right - stringWidth) / 2.0f; 103 const float labelY = bounds.top 104 + ((bounds.Height() - fh.ascent - fh.descent) / 2.0f) 105 + fh.ascent + 1.0f; 106 const float focusLineY = labelY + fh.descent; 107 108 /* speed trick: 109 if the focus changes but the button is not pressed then we can 110 redraw only the focus line, 111 if the focus changes and the button is pressed invert the internal rect 112 this block takes care of all the focus changes 113 */ 114 if (IsFocusChanging()) { 115 if (pushed) { 116 rect.InsetBy(2.0, 2.0); 117 InvertRect(rect); 118 } else 119 DrawFocusLine(x, focusLineY, stringWidth, IsFocus() && Window()->IsActive()); 120 121 return; 122 } 123 124 // Colors 125 const rgb_color panelBgColor = ui_color(B_PANEL_BACKGROUND_COLOR); 126 const rgb_color buttonBgColor=tint_color(panelBgColor, B_LIGHTEN_1_TINT); 127 const rgb_color maxLightColor=tint_color(panelBgColor, B_LIGHTEN_MAX_TINT); 128 const rgb_color maxShadowColor=tint_color(panelBgColor, B_DARKEN_MAX_TINT); 129 const rgb_color darkBorderColor = tint_color(panelBgColor, 130 enabled ? B_DARKEN_4_TINT : B_DARKEN_2_TINT); 131 const rgb_color firstBevelColor = enabled ? tint_color(panelBgColor, B_DARKEN_2_TINT) 132 : panelBgColor; 133 const rgb_color cornerColor = IsDefault() ? firstBevelColor : panelBgColor; 134 135 // Fill the button area 136 SetHighColor(buttonBgColor); 137 FillRect(fillArea); 138 139 // external border 140 SetHighColor(darkBorderColor); 141 StrokeRect(rect); 142 143 BeginLineArray(14); 144 145 // Corners 146 AddLine(rect.LeftTop(), rect.LeftTop(), cornerColor); 147 AddLine(rect.LeftBottom(), rect.LeftBottom(), cornerColor); 148 AddLine(rect.RightTop(), rect.RightTop(), cornerColor); 149 AddLine(rect.RightBottom(), rect.RightBottom(), cornerColor); 150 151 rect.InsetBy(1.0f,1.0f); 152 153 // Shadow 154 AddLine(rect.LeftBottom(), rect.RightBottom(), firstBevelColor); 155 AddLine(rect.RightBottom(), rect.RightTop(), firstBevelColor); 156 // Light 157 AddLine(rect.LeftTop(), rect.LeftBottom(),buttonBgColor); 158 AddLine(rect.LeftTop(), rect.RightTop(), buttonBgColor); 159 160 rect.InsetBy(1.0f, 1.0f); 161 162 // Shadow 163 AddLine(rect.LeftBottom(), rect.RightBottom(), panelBgColor); 164 AddLine(rect.RightBottom(), rect.RightTop(), panelBgColor); 165 // Light 166 AddLine(rect.LeftTop(), rect.LeftBottom(),maxLightColor); 167 AddLine(rect.LeftTop(), rect.RightTop(), maxLightColor); 168 169 rect.InsetBy(1.0f,1.0f); 170 171 // Light 172 AddLine(rect.LeftTop(), rect.LeftBottom(),maxLightColor); 173 AddLine(rect.LeftTop(), rect.RightTop(), maxLightColor); 174 175 EndLineArray(); 176 177 // Invert if clicked 178 if (enabled && pushed) { 179 rect.InsetBy(-2.0f, -2.0f); 180 InvertRect(rect); 181 } 182 183 // Label color 184 if (enabled) { 185 if (pushed) { 186 SetHighColor(maxLightColor); 187 SetLowColor(maxShadowColor); 188 } else { 189 SetHighColor(maxShadowColor); 190 SetLowColor(tint_color(panelBgColor, B_LIGHTEN_2_TINT)); 191 } 192 } else { 193 SetHighColor(tint_color(panelBgColor, B_DISABLED_LABEL_TINT)); 194 SetLowColor(tint_color(panelBgColor, B_LIGHTEN_2_TINT)); 195 } 196 197 // Draw the label 198 DrawString(text.String(), BPoint(x, labelY)); 199 200 // Focus line 201 if (enabled && IsFocus() && Window()->IsActive() && !pushed) 202 DrawFocusLine(x,focusLineY,stringWidth,true); 203 #else 204 // Default indicator 205 if (IsDefault()) 206 rect = DrawDefault(rect, enabled); 207 208 BRect fillArea = rect; 209 fillArea.InsetBy(3.0, 3.0); 210 211 BString text = Label(); 212 213 #if 1 214 // Label truncation 215 BFont font; 216 GetFont(&font); 217 font.TruncateString(&text, B_TRUNCATE_END, fillArea.Width() - 4); 218 #endif 219 220 // Label position 221 const float stringWidth = StringWidth(text.String()); 222 const float x = (rect.right - stringWidth) / 2.0; 223 const float labelY = bounds.top 224 + ((bounds.Height() - fh.ascent - fh.descent) / 2.0) 225 + fh.ascent + 1.0; 226 const float focusLineY = labelY + fh.descent; 227 228 /* speed trick: 229 if the focus changes but the button is not pressed then we can 230 redraw only the focus line, 231 if the focus changes and the button is pressed invert the internal rect 232 this block takes care of all the focus changes 233 */ 234 if (IsFocusChanging()) { 235 if (pushed) { 236 rect.InsetBy(2.0, 2.0); 237 InvertRect(rect); 238 } else 239 DrawFocusLine(x, focusLineY, stringWidth, IsFocus() && Window()->IsActive()); 240 241 return; 242 } 243 244 // colors 245 const rgb_color panelBgColor = ui_color(B_PANEL_BACKGROUND_COLOR); 246 const rgb_color buttonBgColor = tint_color(panelBgColor, B_LIGHTEN_1_TINT); 247 const rgb_color lightColor = tint_color(panelBgColor, enabled ? B_LIGHTEN_2_TINT 248 : B_LIGHTEN_1_TINT); 249 const rgb_color maxLightColor = tint_color(panelBgColor, B_LIGHTEN_MAX_TINT); 250 const rgb_color maxShadowColor = tint_color(panelBgColor, B_DARKEN_MAX_TINT); 251 252 rgb_color dark1BorderColor = tint_color(panelBgColor, enabled ? B_DARKEN_3_TINT 253 : B_DARKEN_1_TINT); 254 rgb_color dark2BorderColor = tint_color(panelBgColor, enabled ? B_DARKEN_4_TINT 255 : B_DARKEN_2_TINT); 256 257 rgb_color bevelColor1 = enabled ? tint_color(panelBgColor, B_DARKEN_2_TINT) 258 : panelBgColor; 259 rgb_color bevelColor2 = enabled ? panelBgColor : buttonBgColor; 260 261 rgb_color borderBevelShadow; 262 rgb_color borderBevelLight; 263 if (IsDefault()) { 264 rgb_color focusColor = dark1BorderColor; 265 266 borderBevelShadow = enabled ? tint_color(focusColor, (B_NO_TINT + B_DARKEN_1_TINT) / 2) 267 : focusColor; 268 borderBevelLight = enabled ? tint_color(focusColor, B_LIGHTEN_1_TINT) : focusColor; 269 270 borderBevelLight.red = (borderBevelLight.red + panelBgColor.red) / 2; 271 borderBevelLight.green = (borderBevelLight.green + panelBgColor.green) / 2; 272 borderBevelLight.blue = (borderBevelLight.blue + panelBgColor.blue) / 2; 273 274 dark1BorderColor = tint_color(focusColor, enabled ? B_DARKEN_3_TINT 275 : B_DARKEN_1_TINT); 276 dark2BorderColor = tint_color(focusColor, enabled ? B_DARKEN_4_TINT 277 : B_DARKEN_2_TINT); 278 } else { 279 borderBevelShadow = enabled ? tint_color(panelBgColor, (B_NO_TINT + B_DARKEN_1_TINT) / 2) 280 : panelBgColor; 281 borderBevelLight = enabled ? buttonBgColor : panelBgColor; 282 } 283 284 // fill the button area 285 SetHighColor(buttonBgColor); 286 FillRect(fillArea); 287 288 BeginLineArray(16); 289 // bevel around external border 290 AddLine(BPoint(rect.left, rect.bottom), 291 BPoint(rect.left, rect.top), borderBevelShadow); 292 AddLine(BPoint(rect.left + 1, rect.top), 293 BPoint(rect.right, rect.top), borderBevelShadow); 294 295 AddLine(BPoint(rect.right, rect.top + 1), 296 BPoint(rect.right, rect.bottom), borderBevelLight); 297 AddLine(BPoint(rect.left + 1, rect.bottom), 298 BPoint(rect.right - 1, rect.bottom), borderBevelLight); 299 300 rect.InsetBy(1.0, 1.0); 301 302 // external border 303 AddLine(BPoint(rect.left, rect.bottom), 304 BPoint(rect.left, rect.top), dark1BorderColor); 305 AddLine(BPoint(rect.left + 1, rect.top), 306 BPoint(rect.right, rect.top), dark1BorderColor); 307 AddLine(BPoint(rect.right, rect.top + 1), 308 BPoint(rect.right, rect.bottom), dark2BorderColor); 309 AddLine(BPoint(rect.right - 1, rect.bottom), 310 BPoint(rect.left + 1, rect.bottom), dark2BorderColor); 311 312 rect.InsetBy(1.0, 1.0); 313 314 // Light 315 AddLine(BPoint(rect.left, rect.top), 316 BPoint(rect.left, rect.bottom), lightColor); 317 AddLine(BPoint(rect.left + 1, rect.top), 318 BPoint(rect.right, rect.top), lightColor); 319 // Shadow 320 AddLine(BPoint(rect.left + 1, rect.bottom), 321 BPoint(rect.right, rect.bottom), bevelColor1); 322 AddLine(BPoint(rect.right, rect.bottom - 1), 323 BPoint(rect.right, rect.top + 1), bevelColor1); 324 325 rect.InsetBy(1.0, 1.0); 326 327 // Light 328 AddLine(BPoint(rect.left, rect.top), 329 BPoint(rect.left, rect.bottom), maxLightColor); 330 AddLine(BPoint(rect.left + 1, rect.top), 331 BPoint(rect.right, rect.top), maxLightColor); 332 // Shadow 333 AddLine(BPoint(rect.left + 1, rect.bottom), 334 BPoint(rect.right, rect.bottom), bevelColor2); 335 AddLine(BPoint(rect.right, rect.bottom - 1), 336 BPoint(rect.right, rect.top + 1), bevelColor2); 337 338 rect.InsetBy(1.0,1.0); 339 340 EndLineArray(); 341 342 // Invert if clicked 343 if (enabled && pushed) { 344 rect.InsetBy(-2.0, -2.0); 345 InvertRect(rect); 346 } 347 348 // Label color 349 if (enabled) { 350 if (pushed) { 351 SetHighColor(maxLightColor); 352 SetLowColor(255 - buttonBgColor.red, 353 255 - buttonBgColor.green, 354 255 - buttonBgColor.blue); 355 } else { 356 SetHighColor(maxShadowColor); 357 SetLowColor(buttonBgColor); 358 } 359 } else { 360 SetHighColor(tint_color(panelBgColor, B_DISABLED_LABEL_TINT)); 361 SetLowColor(buttonBgColor); 362 } 363 364 // Draw the label 365 DrawString(text.String(), BPoint(x, labelY)); 366 367 // Focus line 368 if (enabled && IsFocus() && Window()->IsActive() && !pushed) 369 DrawFocusLine(x, focusLineY, stringWidth, true); 370 #endif 371 } 372 373 374 void 375 BButton::MouseDown(BPoint point) 376 { 377 if (!IsEnabled()) 378 return; 379 380 SetValue(B_CONTROL_ON); 381 382 if (Window()->Flags() & B_ASYNCHRONOUS_CONTROLS) { 383 SetTracking(true); 384 SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS); 385 386 } else { 387 BRect bounds = Bounds(); 388 uint32 buttons; 389 390 do { 391 Window()->UpdateIfNeeded(); 392 393 snooze(40000); 394 395 GetMouse(&point, &buttons, true); 396 397 bool inside = bounds.Contains(point); 398 399 if ((Value() == B_CONTROL_ON) != inside) 400 SetValue(inside ? B_CONTROL_ON : B_CONTROL_OFF); 401 } while (buttons != 0); 402 403 if (Value() == B_CONTROL_ON) 404 Invoke(); 405 } 406 } 407 408 409 void 410 BButton::AttachedToWindow() 411 { 412 BControl::AttachedToWindow(); 413 // low color will now be the parents view color 414 415 if (IsDefault()) 416 Window()->SetDefaultButton(this); 417 418 SetViewColor(B_TRANSPARENT_COLOR); 419 } 420 421 422 void 423 BButton::KeyDown(const char *bytes, int32 numBytes) 424 { 425 if (*bytes == B_ENTER || *bytes == B_SPACE) { 426 if (!IsEnabled()) 427 return; 428 429 SetValue(B_CONTROL_ON); 430 431 // make sure the user saw that 432 Window()->UpdateIfNeeded(); 433 snooze(25000); 434 435 Invoke(); 436 437 } else 438 BControl::KeyDown(bytes, numBytes); 439 } 440 441 442 void 443 BButton::MakeDefault(bool flag) 444 { 445 BButton *oldDefault = NULL; 446 BWindow *window = Window(); 447 448 if (window) 449 oldDefault = window->DefaultButton(); 450 451 if (flag) { 452 if (fDrawAsDefault && oldDefault == this) 453 return; 454 455 if (!fDrawAsDefault) { 456 fDrawAsDefault = true; 457 458 ResizeBy(6.0f, 6.0f); 459 MoveBy(-3.0f, -3.0f); 460 } 461 462 if (window && oldDefault != this) 463 window->SetDefaultButton(this); 464 } else { 465 if (!fDrawAsDefault) 466 return; 467 468 fDrawAsDefault = false; 469 470 ResizeBy(-6.0f, -6.0f); 471 MoveBy(3.0f, 3.0f); 472 473 if (window && oldDefault == this) 474 window->SetDefaultButton(NULL); 475 } 476 } 477 478 479 void 480 BButton::SetLabel(const char *string) 481 { 482 BControl::SetLabel(string); 483 } 484 485 486 bool 487 BButton::IsDefault() const 488 { 489 return fDrawAsDefault; 490 } 491 492 493 void 494 BButton::MessageReceived(BMessage *message) 495 { 496 BControl::MessageReceived(message); 497 } 498 499 500 void 501 BButton::WindowActivated(bool active) 502 { 503 BControl::WindowActivated(active); 504 } 505 506 507 void 508 BButton::MouseMoved(BPoint point, uint32 transit, const BMessage *message) 509 { 510 if (!IsTracking()) 511 return; 512 513 bool inside = Bounds().Contains(point); 514 515 if ((Value() == B_CONTROL_ON) != inside) 516 SetValue(inside ? B_CONTROL_ON : B_CONTROL_OFF); 517 } 518 519 520 void 521 BButton::MouseUp(BPoint point) 522 { 523 if (!IsTracking()) 524 return; 525 526 if (Bounds().Contains(point)) 527 Invoke(); 528 529 SetTracking(false); 530 } 531 532 533 void 534 BButton::DetachedFromWindow() 535 { 536 BControl::DetachedFromWindow(); 537 } 538 539 540 void 541 BButton::SetValue(int32 value) 542 { 543 if (value != Value()) { 544 BControl::SetValue(value); 545 Invalidate(); 546 } 547 } 548 549 550 void 551 BButton::GetPreferredSize(float *width, float *height) 552 { 553 font_height fh; 554 GetFontHeight(&fh); 555 556 *height = 12.0f + (float)ceil(fh.ascent + fh.descent); 557 *width = 20.0f + (float)ceil(StringWidth(Label())); 558 559 if (*width < 75.0f) 560 *width = 75.0f; 561 562 if (fDrawAsDefault) { 563 *width += 6.0f; 564 *height += 6.0f; 565 } 566 } 567 568 569 void 570 BButton::ResizeToPreferred() 571 { 572 BControl::ResizeToPreferred(); 573 } 574 575 576 status_t 577 BButton::Invoke(BMessage *message) 578 { 579 Sync(); 580 snooze(50000); 581 582 status_t err = BControl::Invoke(message); 583 584 SetValue(B_CONTROL_OFF); 585 586 return err; 587 } 588 589 590 void 591 BButton::FrameMoved(BPoint newLocation) 592 { 593 BControl::FrameMoved(newLocation); 594 } 595 596 597 void 598 BButton::FrameResized(float width, float height) 599 { 600 BControl::FrameResized(width, height); 601 } 602 603 604 void 605 BButton::MakeFocus(bool focused) 606 { 607 BControl::MakeFocus(focused); 608 } 609 610 611 void 612 BButton::AllAttached() 613 { 614 BControl::AllAttached(); 615 } 616 617 618 void 619 BButton::AllDetached() 620 { 621 BControl::AllDetached(); 622 } 623 624 625 BHandler * 626 BButton::ResolveSpecifier(BMessage *message, int32 index, 627 BMessage *specifier, int32 what, 628 const char *property) 629 { 630 return BControl::ResolveSpecifier(message, index, specifier, what, property); 631 } 632 633 634 status_t 635 BButton::GetSupportedSuites(BMessage *message) 636 { 637 return BControl::GetSupportedSuites(message); 638 } 639 640 641 status_t 642 BButton::Perform(perform_code d, void *arg) 643 { 644 return BControl::Perform(d, arg); 645 } 646 647 648 void BButton::_ReservedButton1() {} 649 void BButton::_ReservedButton2() {} 650 void BButton::_ReservedButton3() {} 651 652 653 BButton & 654 BButton::operator=(const BButton &) 655 { 656 return *this; 657 } 658 659 660 BRect 661 BButton::DrawDefault(BRect bounds, bool enabled) 662 { 663 #if 0 664 rgb_color no_tint = ui_color(B_PANEL_BACKGROUND_COLOR), 665 lighten1 = tint_color(no_tint, B_LIGHTEN_1_TINT), 666 darken1 = tint_color(no_tint, B_DARKEN_1_TINT); 667 668 rgb_color borderColor; 669 if (enabled) 670 borderColor = tint_color(no_tint, B_DARKEN_4_TINT); 671 else 672 borderColor = darken1; 673 674 // Dark border 675 BeginLineArray(4); 676 AddLine(BPoint(bounds.left, bounds.bottom - 1.0f), 677 BPoint(bounds.left, bounds.top + 1.0f), borderColor); 678 AddLine(BPoint(bounds.left + 1.0f, bounds.top), 679 BPoint(bounds.right - 1.0f, bounds.top), borderColor); 680 AddLine(BPoint(bounds.right, bounds.top + 1.0f), 681 BPoint(bounds.right, bounds.bottom - 1.0f), borderColor); 682 AddLine(BPoint(bounds.left + 1.0f, bounds.bottom), 683 BPoint(bounds.right - 1.0f, bounds.bottom), borderColor); 684 EndLineArray(); 685 686 if (enabled) { 687 // Bevel 688 bounds.InsetBy(1.0f, 1.0f); 689 SetHighColor(darken1); 690 StrokeRect(bounds); 691 } 692 693 bounds.InsetBy(1.0f, 1.0f); 694 695 // Filling 696 float inset = enabled? 2.0f : 3.0f; 697 SetHighColor(lighten1); 698 699 FillRect(BRect(bounds.left, bounds.top, 700 bounds.right, bounds.top+inset-1.0f)); 701 FillRect(BRect(bounds.left, bounds.bottom-inset+1.0f, 702 bounds.right, bounds.bottom)); 703 FillRect(BRect(bounds.left, bounds.top+inset-1.0f, 704 bounds.left+inset-1.0f, bounds.bottom-inset+1.0f)); 705 FillRect(BRect(bounds.right-inset+1.0f, bounds.top+inset-1.0f, 706 bounds.right, bounds.bottom-inset+1.0f)); 707 708 bounds.InsetBy(inset,inset); 709 710 return bounds; 711 #else 712 rgb_color low = LowColor(); 713 rgb_color focusColor = tint_color(low, enabled ? (B_DARKEN_1_TINT + B_DARKEN_2_TINT) / 2 714 : (B_NO_TINT + B_DARKEN_1_TINT) / 2); 715 716 SetHighColor(focusColor); 717 718 StrokeRect(bounds, B_SOLID_LOW); 719 bounds.InsetBy(1.0, 1.0); 720 StrokeRect(bounds); 721 bounds.InsetBy(1.0, 1.0); 722 StrokeRect(bounds); 723 bounds.InsetBy(1.0, 1.0); 724 725 return bounds; 726 #endif 727 } 728 729 730 status_t 731 BButton::Execute() 732 { 733 if (!IsEnabled()) 734 return B_ERROR; 735 736 SetValue(B_CONTROL_ON); 737 return Invoke(); 738 } 739 740 741 void 742 BButton::DrawFocusLine(float x, float y, float width, bool visible) 743 { 744 if (visible) 745 SetHighColor(ui_color(B_KEYBOARD_NAVIGATION_COLOR)); 746 else 747 SetHighColor(tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), 748 B_LIGHTEN_1_TINT)); 749 // Blue Line 750 StrokeLine(BPoint(x, y), BPoint(x + width, y)); 751 752 if (visible) 753 SetHighColor(255, 255, 255); 754 // White Line 755 StrokeLine(BPoint(x, y + 1.0f), BPoint(x + width, y + 1.0f)); 756 } 757