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