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