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 181 rgb_color dark1BorderColor; 182 rgb_color dark2BorderColor; 183 184 rgb_color bevelColor1; 185 rgb_color bevelColor2; 186 rgb_color bevelColorRBCorner; 187 188 rgb_color borderBevelShadow; 189 rgb_color borderBevelLight; 190 191 if (enabled) { 192 lightColor = tint_color(panelBgColor, B_LIGHTEN_2_TINT); 193 maxLightColor = tint_color(panelBgColor, B_LIGHTEN_MAX_TINT); 194 195 dark1BorderColor = tint_color(panelBgColor, B_DARKEN_3_TINT); 196 dark2BorderColor = tint_color(panelBgColor, B_DARKEN_4_TINT); 197 198 bevelColor1 = tint_color(panelBgColor, B_DARKEN_2_TINT); 199 bevelColor2 = panelBgColor; 200 201 if (IsDefault()) { 202 borderBevelShadow = tint_color(dark1BorderColor, 203 (B_NO_TINT + B_DARKEN_1_TINT) / 2); 204 borderBevelLight = tint_color(dark1BorderColor, B_LIGHTEN_1_TINT); 205 206 borderBevelLight.red = (borderBevelLight.red + panelBgColor.red) 207 / 2; 208 borderBevelLight.green = (borderBevelLight.green 209 + panelBgColor.green) / 2; 210 borderBevelLight.blue = (borderBevelLight.blue 211 + panelBgColor.blue) / 2; 212 213 dark1BorderColor = tint_color(dark1BorderColor, B_DARKEN_3_TINT); 214 dark2BorderColor = tint_color(dark1BorderColor, B_DARKEN_4_TINT); 215 216 bevelColorRBCorner = borderBevelShadow; 217 } else { 218 borderBevelShadow = tint_color(panelBgColor, 219 (B_NO_TINT + B_DARKEN_1_TINT) / 2); 220 borderBevelLight = buttonBgColor; 221 222 bevelColorRBCorner = dark1BorderColor; 223 } 224 } else { 225 lightColor = tint_color(panelBgColor, B_LIGHTEN_2_TINT); 226 maxLightColor = tint_color(panelBgColor, B_LIGHTEN_1_TINT); 227 228 dark1BorderColor = tint_color(panelBgColor, B_DARKEN_1_TINT); 229 dark2BorderColor = tint_color(panelBgColor, B_DARKEN_2_TINT); 230 231 bevelColor1 = panelBgColor; 232 bevelColor2 = buttonBgColor; 233 234 if (IsDefault()) { 235 borderBevelShadow = dark1BorderColor; 236 borderBevelLight = panelBgColor; 237 dark1BorderColor = tint_color(dark1BorderColor, B_DARKEN_1_TINT); 238 dark2BorderColor = tint_color(dark1BorderColor, 1.16); 239 240 } else { 241 borderBevelShadow = panelBgColor; 242 borderBevelLight = panelBgColor; 243 } 244 245 bevelColorRBCorner = tint_color(panelBgColor, 1.08);; 246 } 247 248 // fill the button area 249 SetHighColor(buttonBgColor); 250 FillRect(fillArea); 251 252 BeginLineArray(22); 253 // bevel around external border 254 AddLine(BPoint(rect.left, rect.bottom), 255 BPoint(rect.left, rect.top), borderBevelShadow); 256 AddLine(BPoint(rect.left + 1, rect.top), 257 BPoint(rect.right, rect.top), borderBevelShadow); 258 259 AddLine(BPoint(rect.right, rect.top + 1), 260 BPoint(rect.right, rect.bottom), borderBevelLight); 261 AddLine(BPoint(rect.left + 1, rect.bottom), 262 BPoint(rect.right - 1, rect.bottom), borderBevelLight); 263 264 rect.InsetBy(1.0, 1.0); 265 266 // external border 267 AddLine(BPoint(rect.left, rect.bottom), 268 BPoint(rect.left, rect.top), dark1BorderColor); 269 AddLine(BPoint(rect.left + 1, rect.top), 270 BPoint(rect.right, rect.top), dark1BorderColor); 271 AddLine(BPoint(rect.right, rect.top + 1), 272 BPoint(rect.right, rect.bottom), dark2BorderColor); 273 AddLine(BPoint(rect.right - 1, rect.bottom), 274 BPoint(rect.left + 1, rect.bottom), dark2BorderColor); 275 276 rect.InsetBy(1.0, 1.0); 277 278 // Light 279 AddLine(BPoint(rect.left, rect.top), 280 BPoint(rect.left, rect.top), buttonBgColor); 281 AddLine(BPoint(rect.left, rect.top + 1), 282 BPoint(rect.left, rect.bottom - 1), lightColor); 283 AddLine(BPoint(rect.left, rect.bottom), 284 BPoint(rect.left, rect.bottom), bevelColor2); 285 AddLine(BPoint(rect.left + 1, rect.top), 286 BPoint(rect.right - 1, rect.top), lightColor); 287 AddLine(BPoint(rect.right, rect.top), 288 BPoint(rect.right, rect.top), bevelColor2); 289 // Shadow 290 AddLine(BPoint(rect.left + 1, rect.bottom), 291 BPoint(rect.right - 1, rect.bottom), bevelColor1); 292 AddLine(BPoint(rect.right, rect.bottom), 293 BPoint(rect.right, rect.bottom), bevelColorRBCorner); 294 AddLine(BPoint(rect.right, rect.bottom - 1), 295 BPoint(rect.right, rect.top + 1), bevelColor1); 296 297 rect.InsetBy(1.0, 1.0); 298 299 // Light 300 AddLine(BPoint(rect.left, rect.top), 301 BPoint(rect.left, rect.bottom - 1), maxLightColor); 302 AddLine(BPoint(rect.left, rect.bottom), 303 BPoint(rect.left, rect.bottom), buttonBgColor); 304 AddLine(BPoint(rect.left + 1, rect.top), 305 BPoint(rect.right - 1, rect.top), maxLightColor); 306 AddLine(BPoint(rect.right, rect.top), 307 BPoint(rect.right, rect.top), buttonBgColor); 308 // Shadow 309 AddLine(BPoint(rect.left + 1, rect.bottom), 310 BPoint(rect.right, rect.bottom), bevelColor2); 311 AddLine(BPoint(rect.right, rect.bottom - 1), 312 BPoint(rect.right, rect.top + 1), bevelColor2); 313 314 rect.InsetBy(1.0,1.0); 315 316 EndLineArray(); 317 318 // Invert if clicked 319 if (enabled && pushed) { 320 rect.InsetBy(-2.0, -2.0); 321 InvertRect(rect); 322 } 323 324 // Label color 325 if (enabled) { 326 if (pushed) { 327 SetHighColor(maxLightColor); 328 SetLowColor(255 - buttonBgColor.red, 329 255 - buttonBgColor.green, 330 255 - buttonBgColor.blue); 331 } else { 332 SetHighColor(ui_color(B_CONTROL_TEXT_COLOR)); 333 SetLowColor(buttonBgColor); 334 } 335 } else { 336 SetHighColor(tint_color(panelBgColor, B_DISABLED_LABEL_TINT)); 337 SetLowColor(buttonBgColor); 338 } 339 340 // Draw the label 341 DrawString(text.String(), BPoint(x, labelY)); 342 343 // Focus line 344 if (enabled && IsFocus() && Window()->IsActive() && !pushed) 345 _DrawFocusLine(x, focusLineY, stringWidth, true); 346 } 347 348 349 void 350 BButton::MouseDown(BPoint point) 351 { 352 if (!IsEnabled()) 353 return; 354 355 SetValue(B_CONTROL_ON); 356 357 if (Window()->Flags() & B_ASYNCHRONOUS_CONTROLS) { 358 SetTracking(true); 359 SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS); 360 } else { 361 BRect bounds = Bounds(); 362 uint32 buttons; 363 364 do { 365 Window()->UpdateIfNeeded(); 366 snooze(40000); 367 368 GetMouse(&point, &buttons, true); 369 370 bool inside = bounds.Contains(point); 371 372 if ((Value() == B_CONTROL_ON) != inside) 373 SetValue(inside ? B_CONTROL_ON : B_CONTROL_OFF); 374 } while (buttons != 0); 375 376 if (Value() == B_CONTROL_ON) 377 Invoke(); 378 } 379 } 380 381 382 void 383 BButton::AttachedToWindow() 384 { 385 BControl::AttachedToWindow(); 386 // low color will now be the parents view color 387 388 if (IsDefault()) 389 Window()->SetDefaultButton(this); 390 391 SetViewColor(B_TRANSPARENT_COLOR); 392 } 393 394 395 void 396 BButton::KeyDown(const char *bytes, int32 numBytes) 397 { 398 if (*bytes == B_ENTER || *bytes == B_SPACE) { 399 if (!IsEnabled()) 400 return; 401 402 SetValue(B_CONTROL_ON); 403 404 // make sure the user saw that 405 Window()->UpdateIfNeeded(); 406 snooze(25000); 407 408 Invoke(); 409 410 } else 411 BControl::KeyDown(bytes, numBytes); 412 } 413 414 415 void 416 BButton::MakeDefault(bool flag) 417 { 418 BButton *oldDefault = NULL; 419 BWindow *window = Window(); 420 421 if (window) 422 oldDefault = window->DefaultButton(); 423 424 if (flag) { 425 if (fDrawAsDefault && oldDefault == this) 426 return; 427 428 if (!fDrawAsDefault) { 429 fDrawAsDefault = true; 430 431 if ((Flags() & B_SUPPORTS_LAYOUT) != 0) 432 InvalidateLayout(); 433 else { 434 ResizeBy(6.0f, 6.0f); 435 MoveBy(-3.0f, -3.0f); 436 } 437 } 438 439 if (window && oldDefault != this) 440 window->SetDefaultButton(this); 441 } else { 442 if (!fDrawAsDefault) 443 return; 444 445 fDrawAsDefault = false; 446 447 if ((Flags() & B_SUPPORTS_LAYOUT) != 0) 448 InvalidateLayout(); 449 else { 450 ResizeBy(-6.0f, -6.0f); 451 MoveBy(3.0f, 3.0f); 452 } 453 454 if (window && oldDefault == this) 455 window->SetDefaultButton(NULL); 456 } 457 } 458 459 460 void 461 BButton::SetLabel(const char *string) 462 { 463 BControl::SetLabel(string); 464 } 465 466 467 bool 468 BButton::IsDefault() const 469 { 470 return fDrawAsDefault; 471 } 472 473 474 void 475 BButton::MessageReceived(BMessage *message) 476 { 477 BControl::MessageReceived(message); 478 } 479 480 481 void 482 BButton::WindowActivated(bool active) 483 { 484 BControl::WindowActivated(active); 485 } 486 487 488 void 489 BButton::MouseMoved(BPoint point, uint32 transit, const BMessage *message) 490 { 491 if (!IsTracking()) 492 return; 493 494 bool inside = Bounds().Contains(point); 495 496 if ((Value() == B_CONTROL_ON) != inside) 497 SetValue(inside ? B_CONTROL_ON : B_CONTROL_OFF); 498 } 499 500 501 void 502 BButton::MouseUp(BPoint point) 503 { 504 if (!IsTracking()) 505 return; 506 507 if (Bounds().Contains(point)) 508 Invoke(); 509 510 SetTracking(false); 511 } 512 513 514 void 515 BButton::DetachedFromWindow() 516 { 517 BControl::DetachedFromWindow(); 518 } 519 520 521 void 522 BButton::SetValue(int32 value) 523 { 524 if (value != Value()) 525 BControl::SetValue(value); 526 } 527 528 529 void 530 BButton::GetPreferredSize(float *_width, float *_height) 531 { 532 _ValidatePreferredSize(); 533 534 if (_width) 535 *_width = fPreferredSize.width; 536 537 if (_height) 538 *_height = fPreferredSize.height; 539 } 540 541 542 void 543 BButton::ResizeToPreferred() 544 { 545 BControl::ResizeToPreferred(); 546 } 547 548 549 status_t 550 BButton::Invoke(BMessage *message) 551 { 552 Sync(); 553 snooze(50000); 554 555 status_t err = BControl::Invoke(message); 556 557 SetValue(B_CONTROL_OFF); 558 559 return err; 560 } 561 562 563 void 564 BButton::FrameMoved(BPoint newLocation) 565 { 566 BControl::FrameMoved(newLocation); 567 } 568 569 570 void 571 BButton::FrameResized(float width, float height) 572 { 573 BControl::FrameResized(width, height); 574 } 575 576 577 void 578 BButton::MakeFocus(bool focused) 579 { 580 BControl::MakeFocus(focused); 581 } 582 583 584 void 585 BButton::AllAttached() 586 { 587 BControl::AllAttached(); 588 } 589 590 591 void 592 BButton::AllDetached() 593 { 594 BControl::AllDetached(); 595 } 596 597 598 BHandler * 599 BButton::ResolveSpecifier(BMessage *message, int32 index, 600 BMessage *specifier, int32 what, 601 const char *property) 602 { 603 return BControl::ResolveSpecifier(message, index, specifier, what, property); 604 } 605 606 607 status_t 608 BButton::GetSupportedSuites(BMessage *message) 609 { 610 return BControl::GetSupportedSuites(message); 611 } 612 613 614 status_t 615 BButton::Perform(perform_code code, void* _data) 616 { 617 switch (code) { 618 case PERFORM_CODE_MIN_SIZE: 619 ((perform_data_min_size*)_data)->return_value 620 = BButton::MinSize(); 621 return B_OK; 622 case PERFORM_CODE_MAX_SIZE: 623 ((perform_data_max_size*)_data)->return_value 624 = BButton::MaxSize(); 625 return B_OK; 626 case PERFORM_CODE_PREFERRED_SIZE: 627 ((perform_data_preferred_size*)_data)->return_value 628 = BButton::PreferredSize(); 629 return B_OK; 630 case PERFORM_CODE_LAYOUT_ALIGNMENT: 631 ((perform_data_layout_alignment*)_data)->return_value 632 = BButton::LayoutAlignment(); 633 return B_OK; 634 case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH: 635 ((perform_data_has_height_for_width*)_data)->return_value 636 = BButton::HasHeightForWidth(); 637 return B_OK; 638 case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH: 639 { 640 perform_data_get_height_for_width* data 641 = (perform_data_get_height_for_width*)_data; 642 BButton::GetHeightForWidth(data->width, &data->min, &data->max, 643 &data->preferred); 644 return B_OK; 645 } 646 case PERFORM_CODE_SET_LAYOUT: 647 { 648 perform_data_set_layout* data = (perform_data_set_layout*)_data; 649 BButton::SetLayout(data->layout); 650 return B_OK; 651 } 652 case PERFORM_CODE_LAYOUT_INVALIDATED: 653 { 654 perform_data_layout_invalidated* data 655 = (perform_data_layout_invalidated*)_data; 656 BButton::LayoutInvalidated(data->descendants); 657 return B_OK; 658 } 659 case PERFORM_CODE_DO_LAYOUT: 660 { 661 BButton::DoLayout(); 662 return B_OK; 663 } 664 } 665 666 return BControl::Perform(code, _data); 667 } 668 669 670 BSize 671 BButton::MinSize() 672 { 673 return BLayoutUtils::ComposeSize(ExplicitMinSize(), 674 _ValidatePreferredSize()); 675 } 676 677 678 BSize 679 BButton::MaxSize() 680 { 681 return BLayoutUtils::ComposeSize(ExplicitMaxSize(), 682 _ValidatePreferredSize()); 683 } 684 685 686 BSize 687 BButton::PreferredSize() 688 { 689 return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), 690 _ValidatePreferredSize()); 691 } 692 693 694 void 695 BButton::LayoutInvalidated(bool descendants) 696 { 697 // invalidate cached preferred size 698 fPreferredSize.Set(-1, -1); 699 } 700 701 702 void BButton::_ReservedButton1() {} 703 void BButton::_ReservedButton2() {} 704 void BButton::_ReservedButton3() {} 705 706 707 BButton & 708 BButton::operator=(const BButton &) 709 { 710 return *this; 711 } 712 713 714 BSize 715 BButton::_ValidatePreferredSize() 716 { 717 if (fPreferredSize.width < 0) { 718 // width 719 float width = 20.0f + (float)ceil(StringWidth(Label())); 720 if (width < 75.0f) 721 width = 75.0f; 722 723 if (fDrawAsDefault) 724 width += 6.0f; 725 726 fPreferredSize.width = width; 727 728 // height 729 font_height fontHeight; 730 GetFontHeight(&fontHeight); 731 732 fPreferredSize.height 733 = ceilf((fontHeight.ascent + fontHeight.descent) * 1.8) 734 + (fDrawAsDefault ? 6.0f : 0); 735 736 ResetLayoutInvalidation(); 737 } 738 739 return fPreferredSize; 740 } 741 742 743 BRect 744 BButton::_DrawDefault(BRect bounds, bool enabled) 745 { 746 rgb_color low = LowColor(); 747 rgb_color focusColor = tint_color(low, enabled ? 748 (B_DARKEN_1_TINT + B_DARKEN_2_TINT) / 2 749 : (B_NO_TINT + B_DARKEN_1_TINT) / 2); 750 751 SetHighColor(focusColor); 752 753 StrokeRect(bounds, B_SOLID_LOW); 754 bounds.InsetBy(1.0, 1.0); 755 StrokeRect(bounds); 756 bounds.InsetBy(1.0, 1.0); 757 StrokeRect(bounds); 758 bounds.InsetBy(1.0, 1.0); 759 760 return bounds; 761 } 762 763 764 void 765 BButton::_DrawFocusLine(float x, float y, float width, bool visible) 766 { 767 if (visible) 768 SetHighColor(ui_color(B_KEYBOARD_NAVIGATION_COLOR)); 769 else { 770 SetHighColor(tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), 771 B_LIGHTEN_1_TINT)); 772 } 773 774 // Blue Line 775 StrokeLine(BPoint(x, y), BPoint(x + width, y)); 776 777 if (visible) 778 SetHighColor(255, 255, 255); 779 // White Line 780 StrokeLine(BPoint(x, y + 1.0f), BPoint(x + width, y + 1.0f)); 781 } 782 783 784 extern "C" void 785 B_IF_GCC_2(InvalidateLayout__7BButtonb, _ZN7BButton16InvalidateLayoutEb)( 786 BView* view, bool descendants) 787 { 788 perform_data_layout_invalidated data; 789 data.descendants = descendants; 790 791 view->Perform(PERFORM_CODE_LAYOUT_INVALIDATED, &data); 792 } 793 794