1 /* 2 * Copyright 2001-2015 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 * Ingo Weinhold, ingo_weinhold@gmx.de 12 */ 13 14 15 #include <Button.h> 16 17 #include <algorithm> 18 #include <new> 19 20 #include <Bitmap.h> 21 #include <ControlLook.h> 22 #include <Font.h> 23 #include <LayoutUtils.h> 24 #include <String.h> 25 #include <Window.h> 26 27 #include <binary_compatibility/Interface.h> 28 29 30 enum { 31 FLAG_DEFAULT = 0x01, 32 FLAG_FLAT = 0x02, 33 FLAG_INSIDE = 0x04, 34 FLAG_WAS_PRESSED = 0x08, 35 }; 36 37 38 BButton::BButton(BRect frame, const char* name, const char* label, 39 BMessage* message, uint32 resizingMode, uint32 flags) 40 : 41 BControl(frame, name, label, message, resizingMode, 42 flags | B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE), 43 fPreferredSize(-1, -1), 44 fFlags(0), 45 fBehavior(B_BUTTON_BEHAVIOR), 46 fPopUpMessage(NULL) 47 { 48 // Resize to minimum height if needed 49 BFont font; 50 GetFont(&font); 51 font_height fh; 52 font.GetHeight(&fh); 53 float minHeight = font.Size() + (float)ceil(fh.ascent + fh.descent); 54 if (Bounds().Height() < minHeight) 55 ResizeTo(Bounds().Width(), minHeight); 56 } 57 58 59 BButton::BButton(const char* name, const char* label, BMessage* message, 60 uint32 flags) 61 : 62 BControl(name, label, message, 63 flags | B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE), 64 fPreferredSize(-1, -1), 65 fFlags(0), 66 fBehavior(B_BUTTON_BEHAVIOR), 67 fPopUpMessage(NULL) 68 { 69 } 70 71 72 BButton::BButton(const char* label, BMessage* message) 73 : 74 BControl(NULL, label, message, 75 B_WILL_DRAW | B_NAVIGABLE | B_FULL_UPDATE_ON_RESIZE), 76 fPreferredSize(-1, -1), 77 fFlags(0), 78 fBehavior(B_BUTTON_BEHAVIOR), 79 fPopUpMessage(NULL) 80 { 81 } 82 83 84 BButton::~BButton() 85 { 86 SetPopUpMessage(NULL); 87 } 88 89 90 BButton::BButton(BMessage* data) 91 : 92 BControl(data), 93 fPreferredSize(-1, -1), 94 fFlags(0), 95 fBehavior(B_BUTTON_BEHAVIOR), 96 fPopUpMessage(NULL) 97 { 98 bool isDefault = false; 99 if (data->FindBool("_default", &isDefault) == B_OK && isDefault) 100 _SetFlag(FLAG_DEFAULT, true); 101 // NOTE: Default button state will be synchronized with the window 102 // in AttachedToWindow(). 103 } 104 105 106 BArchivable* 107 BButton::Instantiate(BMessage* data) 108 { 109 if (validate_instantiation(data, "BButton")) 110 return new(std::nothrow) BButton(data); 111 112 return NULL; 113 } 114 115 116 status_t 117 BButton::Archive(BMessage* data, bool deep) const 118 { 119 status_t err = BControl::Archive(data, deep); 120 121 if (err != B_OK) 122 return err; 123 124 if (IsDefault()) 125 err = data->AddBool("_default", true); 126 127 return err; 128 } 129 130 131 void 132 BButton::Draw(BRect updateRect) 133 { 134 BRect rect(Bounds()); 135 rgb_color background = ViewColor(); 136 rgb_color base = LowColor(); 137 rgb_color textColor = ui_color(B_CONTROL_TEXT_COLOR); 138 139 uint32 flags = be_control_look->Flags(this); 140 if (_Flag(FLAG_DEFAULT)) 141 flags |= BControlLook::B_DEFAULT_BUTTON; 142 if (_Flag(FLAG_FLAT) && !IsTracking()) 143 flags |= BControlLook::B_FLAT; 144 if (_Flag(FLAG_INSIDE)) 145 flags |= BControlLook::B_HOVER; 146 147 be_control_look->DrawButtonFrame(this, rect, updateRect, 148 base, background, flags); 149 150 if (fBehavior == B_POP_UP_BEHAVIOR) { 151 be_control_look->DrawButtonWithPopUpBackground(this, rect, updateRect, 152 base, flags); 153 } else { 154 be_control_look->DrawButtonBackground(this, rect, updateRect, 155 base, flags); 156 } 157 158 // always leave some room around the label 159 float labelMargin = be_control_look->DefaultLabelSpacing() / 2; 160 rect.InsetBy(labelMargin, labelMargin); 161 162 const BBitmap* icon = IconBitmap( 163 (Value() == B_CONTROL_OFF 164 ? B_INACTIVE_ICON_BITMAP : B_ACTIVE_ICON_BITMAP) 165 | (IsEnabled() ? 0 : B_DISABLED_ICON_BITMAP)); 166 167 be_control_look->DrawLabel(this, Label(), icon, rect, updateRect, base, 168 flags, BAlignment(B_ALIGN_CENTER, B_ALIGN_MIDDLE), &textColor); 169 } 170 171 172 void 173 BButton::MouseDown(BPoint where) 174 { 175 if (!IsEnabled()) 176 return; 177 178 if (fBehavior == B_POP_UP_BEHAVIOR && _PopUpRect().Contains(where)) { 179 InvokeNotify(fPopUpMessage, B_CONTROL_MODIFIED); 180 return; 181 } 182 183 bool toggleBehavior = fBehavior == B_TOGGLE_BEHAVIOR; 184 185 if (toggleBehavior) { 186 bool wasPressed = Value() == B_CONTROL_ON; 187 _SetFlag(FLAG_WAS_PRESSED, wasPressed); 188 SetValue(wasPressed ? B_CONTROL_OFF : B_CONTROL_ON); 189 Invalidate(); 190 } else 191 SetValue(B_CONTROL_ON); 192 193 if (Window()->Flags() & B_ASYNCHRONOUS_CONTROLS) { 194 SetTracking(true); 195 SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS); 196 } else { 197 BRect bounds = Bounds(); 198 uint32 buttons; 199 bool inside = false; 200 201 do { 202 Window()->UpdateIfNeeded(); 203 snooze(40000); 204 205 GetMouse(&where, &buttons, true); 206 inside = bounds.Contains(where); 207 208 if (toggleBehavior) { 209 bool pressed = inside ^ _Flag(FLAG_WAS_PRESSED); 210 SetValue(pressed ? B_CONTROL_ON : B_CONTROL_OFF); 211 } else { 212 if ((Value() == B_CONTROL_ON) != inside) 213 SetValue(inside ? B_CONTROL_ON : B_CONTROL_OFF); 214 } 215 } while (buttons != 0); 216 217 if (inside) { 218 if (toggleBehavior) { 219 SetValue( 220 _Flag(FLAG_WAS_PRESSED) ? B_CONTROL_OFF : B_CONTROL_ON); 221 } 222 223 Invoke(); 224 } else if (_Flag(FLAG_FLAT)) 225 Invalidate(); 226 } 227 } 228 229 void 230 BButton::AttachedToWindow() 231 { 232 BControl::AttachedToWindow(); 233 234 // Tint default control background color to match default panel background. 235 SetLowUIColor(B_CONTROL_BACKGROUND_COLOR, 1.115); 236 SetHighUIColor(B_CONTROL_TEXT_COLOR); 237 238 if (IsDefault()) 239 Window()->SetDefaultButton(this); 240 } 241 242 243 void 244 BButton::KeyDown(const char* bytes, int32 numBytes) 245 { 246 if (*bytes == B_ENTER || *bytes == B_SPACE) { 247 if (!IsEnabled()) 248 return; 249 250 SetValue(B_CONTROL_ON); 251 252 // make sure the user saw that 253 Window()->UpdateIfNeeded(); 254 snooze(25000); 255 256 Invoke(); 257 } else 258 BControl::KeyDown(bytes, numBytes); 259 } 260 261 262 void 263 BButton::MakeDefault(bool flag) 264 { 265 BButton* oldDefault = NULL; 266 BWindow* window = Window(); 267 268 if (window != NULL) 269 oldDefault = window->DefaultButton(); 270 271 if (flag) { 272 if (_Flag(FLAG_DEFAULT) && oldDefault == this) 273 return; 274 275 if (_SetFlag(FLAG_DEFAULT, true)) { 276 if ((Flags() & B_SUPPORTS_LAYOUT) != 0) 277 InvalidateLayout(); 278 else { 279 ResizeBy(6.0f, 6.0f); 280 MoveBy(-3.0f, -3.0f); 281 } 282 } 283 284 if (window && oldDefault != this) 285 window->SetDefaultButton(this); 286 } else { 287 if (!_SetFlag(FLAG_DEFAULT, false)) 288 return; 289 290 if ((Flags() & B_SUPPORTS_LAYOUT) != 0) 291 InvalidateLayout(); 292 else { 293 ResizeBy(-6.0f, -6.0f); 294 MoveBy(3.0f, 3.0f); 295 } 296 297 if (window && oldDefault == this) 298 window->SetDefaultButton(NULL); 299 } 300 } 301 302 303 void 304 BButton::SetLabel(const char* label) 305 { 306 BControl::SetLabel(label); 307 } 308 309 310 bool 311 BButton::IsDefault() const 312 { 313 return _Flag(FLAG_DEFAULT); 314 } 315 316 317 bool 318 BButton::IsFlat() const 319 { 320 return _Flag(FLAG_FLAT); 321 } 322 323 324 void 325 BButton::SetFlat(bool flat) 326 { 327 if (_SetFlag(FLAG_FLAT, flat)) 328 Invalidate(); 329 } 330 331 332 BButton::BBehavior 333 BButton::Behavior() const 334 { 335 return fBehavior; 336 } 337 338 339 void 340 BButton::SetBehavior(BBehavior behavior) 341 { 342 if (behavior != fBehavior) { 343 fBehavior = behavior; 344 InvalidateLayout(); 345 Invalidate(); 346 } 347 } 348 349 350 BMessage* 351 BButton::PopUpMessage() const 352 { 353 return fPopUpMessage; 354 } 355 356 357 void 358 BButton::SetPopUpMessage(BMessage* message) 359 { 360 delete fPopUpMessage; 361 fPopUpMessage = message; 362 } 363 364 365 void 366 BButton::MessageReceived(BMessage* message) 367 { 368 BControl::MessageReceived(message); 369 } 370 371 372 void 373 BButton::WindowActivated(bool active) 374 { 375 BControl::WindowActivated(active); 376 } 377 378 379 void 380 BButton::MouseMoved(BPoint where, uint32 code, const BMessage* dragMessage) 381 { 382 bool inside = (code != B_EXITED_VIEW) && Bounds().Contains(where); 383 if (_SetFlag(FLAG_INSIDE, inside)) 384 Invalidate(); 385 386 if (!IsTracking()) 387 return; 388 389 if (fBehavior == B_TOGGLE_BEHAVIOR) { 390 bool pressed = inside ^ _Flag(FLAG_WAS_PRESSED); 391 SetValue(pressed ? B_CONTROL_ON : B_CONTROL_OFF); 392 } else { 393 if ((Value() == B_CONTROL_ON) != inside) 394 SetValue(inside ? B_CONTROL_ON : B_CONTROL_OFF); 395 } 396 } 397 398 399 void 400 BButton::MouseUp(BPoint where) 401 { 402 if (!IsTracking()) 403 return; 404 405 if (Bounds().Contains(where)) { 406 if (fBehavior == B_TOGGLE_BEHAVIOR) 407 SetValue(_Flag(FLAG_WAS_PRESSED) ? B_CONTROL_OFF : B_CONTROL_ON); 408 409 Invoke(); 410 } else if (_Flag(FLAG_FLAT)) 411 Invalidate(); 412 413 SetTracking(false); 414 } 415 416 417 void 418 BButton::DetachedFromWindow() 419 { 420 BControl::DetachedFromWindow(); 421 } 422 423 424 void 425 BButton::SetValue(int32 value) 426 { 427 if (value != Value()) 428 BControl::SetValue(value); 429 } 430 431 432 void 433 BButton::GetPreferredSize(float* _width, float* _height) 434 { 435 _ValidatePreferredSize(); 436 437 if (_width) 438 *_width = fPreferredSize.width; 439 440 if (_height) 441 *_height = fPreferredSize.height; 442 } 443 444 445 void 446 BButton::ResizeToPreferred() 447 { 448 BControl::ResizeToPreferred(); 449 } 450 451 452 status_t 453 BButton::Invoke(BMessage* message) 454 { 455 Sync(); 456 snooze(50000); 457 458 status_t err = BControl::Invoke(message); 459 460 if (fBehavior != B_TOGGLE_BEHAVIOR) 461 SetValue(B_CONTROL_OFF); 462 463 return err; 464 } 465 466 467 void 468 BButton::FrameMoved(BPoint newPosition) 469 { 470 BControl::FrameMoved(newPosition); 471 } 472 473 474 void 475 BButton::FrameResized(float newWidth, float newHeight) 476 { 477 BControl::FrameResized(newWidth, newHeight); 478 } 479 480 481 void 482 BButton::MakeFocus(bool focus) 483 { 484 BControl::MakeFocus(focus); 485 } 486 487 488 void 489 BButton::AllAttached() 490 { 491 BControl::AllAttached(); 492 } 493 494 495 void 496 BButton::AllDetached() 497 { 498 BControl::AllDetached(); 499 } 500 501 502 BHandler* 503 BButton::ResolveSpecifier(BMessage* message, int32 index, 504 BMessage* specifier, int32 what, const char* property) 505 { 506 return BControl::ResolveSpecifier(message, index, specifier, what, 507 property); 508 } 509 510 511 status_t 512 BButton::GetSupportedSuites(BMessage* message) 513 { 514 return BControl::GetSupportedSuites(message); 515 } 516 517 518 status_t 519 BButton::Perform(perform_code code, void* _data) 520 { 521 switch (code) { 522 case PERFORM_CODE_MIN_SIZE: 523 ((perform_data_min_size*)_data)->return_value 524 = BButton::MinSize(); 525 return B_OK; 526 527 case PERFORM_CODE_MAX_SIZE: 528 ((perform_data_max_size*)_data)->return_value 529 = BButton::MaxSize(); 530 return B_OK; 531 532 case PERFORM_CODE_PREFERRED_SIZE: 533 ((perform_data_preferred_size*)_data)->return_value 534 = BButton::PreferredSize(); 535 return B_OK; 536 537 case PERFORM_CODE_LAYOUT_ALIGNMENT: 538 ((perform_data_layout_alignment*)_data)->return_value 539 = BButton::LayoutAlignment(); 540 return B_OK; 541 542 case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH: 543 ((perform_data_has_height_for_width*)_data)->return_value 544 = BButton::HasHeightForWidth(); 545 return B_OK; 546 547 case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH: 548 { 549 perform_data_get_height_for_width* data 550 = (perform_data_get_height_for_width*)_data; 551 BButton::GetHeightForWidth(data->width, &data->min, &data->max, 552 &data->preferred); 553 return B_OK; 554 } 555 556 case PERFORM_CODE_SET_LAYOUT: 557 { 558 perform_data_set_layout* data = (perform_data_set_layout*)_data; 559 BButton::SetLayout(data->layout); 560 return B_OK; 561 } 562 563 case PERFORM_CODE_LAYOUT_INVALIDATED: 564 { 565 perform_data_layout_invalidated* data 566 = (perform_data_layout_invalidated*)_data; 567 BButton::LayoutInvalidated(data->descendants); 568 return B_OK; 569 } 570 571 case PERFORM_CODE_DO_LAYOUT: 572 { 573 BButton::DoLayout(); 574 return B_OK; 575 } 576 577 case PERFORM_CODE_SET_ICON: 578 { 579 perform_data_set_icon* data = (perform_data_set_icon*)_data; 580 return BButton::SetIcon(data->icon, data->flags); 581 } 582 } 583 584 return BControl::Perform(code, _data); 585 } 586 587 588 BSize 589 BButton::MinSize() 590 { 591 return BLayoutUtils::ComposeSize(ExplicitMinSize(), 592 _ValidatePreferredSize()); 593 } 594 595 596 BSize 597 BButton::MaxSize() 598 { 599 return BLayoutUtils::ComposeSize(ExplicitMaxSize(), 600 _ValidatePreferredSize()); 601 } 602 603 604 BSize 605 BButton::PreferredSize() 606 { 607 return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), 608 _ValidatePreferredSize()); 609 } 610 611 612 status_t 613 BButton::SetIcon(const BBitmap* icon, uint32 flags) 614 { 615 return BControl::SetIcon(icon, 616 flags | B_CREATE_ACTIVE_ICON_BITMAP | B_CREATE_DISABLED_ICON_BITMAPS); 617 } 618 619 620 void 621 BButton::LayoutInvalidated(bool descendants) 622 { 623 // invalidate cached preferred size 624 fPreferredSize.Set(-1, -1); 625 } 626 627 628 void BButton::_ReservedButton1() {} 629 void BButton::_ReservedButton2() {} 630 void BButton::_ReservedButton3() {} 631 632 633 BButton & 634 BButton::operator=(const BButton &) 635 { 636 return *this; 637 } 638 639 640 BSize 641 BButton::_ValidatePreferredSize() 642 { 643 if (fPreferredSize.width < 0) { 644 BControlLook::background_type backgroundType 645 = fBehavior == B_POP_UP_BEHAVIOR 646 ? BControlLook::B_BUTTON_WITH_POP_UP_BACKGROUND 647 : BControlLook::B_BUTTON_BACKGROUND; 648 float left, top, right, bottom; 649 be_control_look->GetInsets(BControlLook::B_BUTTON_FRAME, backgroundType, 650 IsDefault() ? BControlLook::B_DEFAULT_BUTTON : 0, 651 left, top, right, bottom); 652 653 // width 654 const float labelSpacing = be_control_look->DefaultLabelSpacing(); 655 float width = left + right + labelSpacing - 1; 656 657 const char* label = Label(); 658 if (label != NULL) { 659 width = std::max(width, ceilf(labelSpacing * 3.3f)); 660 width += ceilf(StringWidth(label)); 661 } 662 663 const BBitmap* icon = IconBitmap(B_INACTIVE_ICON_BITMAP); 664 if (icon != NULL) 665 width += icon->Bounds().Width() + 1; 666 667 if (label != NULL && icon != NULL) 668 width += labelSpacing; 669 670 // height 671 float minHorizontalMargins = top + bottom + labelSpacing; 672 float height = -1; 673 674 if (label != NULL) { 675 font_height fontHeight; 676 GetFontHeight(&fontHeight); 677 float textHeight = fontHeight.ascent + fontHeight.descent; 678 height = ceilf(textHeight * 1.8); 679 float margins = height - ceilf(textHeight); 680 if (margins < minHorizontalMargins) 681 height += minHorizontalMargins - margins; 682 } 683 684 if (icon != NULL) { 685 height = std::max(height, 686 icon->Bounds().Height() + minHorizontalMargins); 687 } 688 689 // force some minimum width/height values 690 width = std::max(width, label != NULL ? (labelSpacing * 12.5f) : labelSpacing); 691 height = std::max(height, labelSpacing); 692 693 fPreferredSize.Set(width, height); 694 695 ResetLayoutInvalidation(); 696 } 697 698 return fPreferredSize; 699 } 700 701 702 BRect 703 BButton::_PopUpRect() const 704 { 705 if (fBehavior != B_POP_UP_BEHAVIOR) 706 return BRect(); 707 708 float left, top, right, bottom; 709 be_control_look->GetInsets(BControlLook::B_BUTTON_FRAME, 710 BControlLook::B_BUTTON_WITH_POP_UP_BACKGROUND, 711 IsDefault() ? BControlLook::B_DEFAULT_BUTTON : 0, 712 left, top, right, bottom); 713 714 BRect rect(Bounds()); 715 rect.left = rect.right - right + 1; 716 return rect; 717 } 718 719 720 inline bool 721 BButton::_Flag(uint32 flag) const 722 { 723 return (fFlags & flag) != 0; 724 } 725 726 727 inline bool 728 BButton::_SetFlag(uint32 flag, bool set) 729 { 730 if (((fFlags & flag) != 0) == set) 731 return false; 732 733 if (set) 734 fFlags |= flag; 735 else 736 fFlags &= ~flag; 737 738 return true; 739 } 740 741 742 extern "C" void 743 B_IF_GCC_2(InvalidateLayout__7BButtonb, _ZN7BButton16InvalidateLayoutEb)( 744 BView* view, bool descendants) 745 { 746 perform_data_layout_invalidated data; 747 data.descendants = descendants; 748 749 view->Perform(PERFORM_CODE_LAYOUT_INVALIDATED, &data); 750 } 751