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