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