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