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