1 /* 2 * Copyright 2001-2008, Haiku, Inc. All rights reserved. 3 * Distributed under the terms of the MIT license. 4 * 5 * Authors: 6 * Marc Flerackers (mflerackers@androme.be) 7 * Stephan Aßmus <superstippi@gmx.de> 8 */ 9 10 /*! 11 BRadioButton represents a single on/off button. 12 All sibling BRadioButton objects comprise a single 13 "multiple choice" control. 14 */ 15 16 #include <RadioButton.h> 17 18 #include <ControlLook.h> 19 #include <Debug.h> 20 #include <Box.h> 21 #include <LayoutUtils.h> 22 #include <Window.h> 23 24 #include <binary_compatibility/Interface.h> 25 26 27 BRadioButton::BRadioButton(BRect frame, const char* name, const char* label, 28 BMessage* message, uint32 resizMask, uint32 flags) 29 : BControl(frame, name, label, message, resizMask, flags | B_FRAME_EVENTS), 30 fOutlined(false) 31 { 32 // Resize to minimum height if needed for BeOS compatibility 33 float minHeight; 34 GetPreferredSize(NULL, &minHeight); 35 if (Bounds().Height() < minHeight) 36 ResizeTo(Bounds().Width(), minHeight); 37 } 38 39 40 BRadioButton::BRadioButton(const char* name, const char* label, 41 BMessage* message, uint32 flags) 42 : BControl(name, label, message, flags | B_FRAME_EVENTS), 43 fOutlined(false) 44 { 45 } 46 47 48 BRadioButton::BRadioButton(const char* label, BMessage* message) 49 : BControl(NULL, label, message, 50 B_WILL_DRAW | B_NAVIGABLE | B_FRAME_EVENTS), 51 fOutlined(false) 52 { 53 } 54 55 56 BRadioButton::BRadioButton(BMessage* archive) 57 : BControl(archive), 58 fOutlined(false) 59 { 60 } 61 62 63 BRadioButton::~BRadioButton() 64 { 65 } 66 67 68 BArchivable* 69 BRadioButton::Instantiate(BMessage* archive) 70 { 71 if (validate_instantiation(archive, "BRadioButton")) 72 return new BRadioButton(archive); 73 74 return NULL; 75 } 76 77 78 status_t 79 BRadioButton::Archive(BMessage* archive, bool deep) const 80 { 81 return BControl::Archive(archive, deep); 82 } 83 84 85 void 86 BRadioButton::Draw(BRect updateRect) 87 { 88 // its size depends on the text height 89 font_height fontHeight; 90 GetFontHeight(&fontHeight); 91 92 if (be_control_look) { 93 rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR); 94 95 uint32 flags = be_control_look->Flags(this); 96 if (fOutlined) 97 flags |= BControlLook::B_CLICKED; 98 99 BRect knobRect(_KnobFrame(fontHeight)); 100 BRect rect(knobRect); 101 be_control_look->DrawRadioButton(this, rect, updateRect, base, flags); 102 103 BRect labelRect(Bounds()); 104 labelRect.left = knobRect.right 105 + be_control_look->DefaultLabelSpacing(); 106 107 be_control_look->DrawLabel(this, Label(), labelRect, updateRect, 108 base, flags); 109 return; 110 } 111 112 float textHeight = ceilf(fontHeight.ascent + fontHeight.descent); 113 114 // layout the rect for the dot 115 BRect rect = _KnobFrame(fontHeight); 116 117 BPoint labelPos(rect.right + floorf(textHeight / 2.0), 118 floorf((rect.top + rect.bottom + textHeight) / 2.0 119 - fontHeight.descent + 0.5) + 1.0); 120 121 // if the focus is changing, just redraw the focus indicator 122 if (IsFocusChanging()) { 123 if (IsFocus()) 124 SetHighColor(ui_color(B_KEYBOARD_NAVIGATION_COLOR)); 125 else 126 SetHighColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 127 128 BPoint underLine = labelPos; 129 underLine.y += fontHeight.descent; 130 StrokeLine(underLine, underLine + BPoint(StringWidth(Label()), 0.0)); 131 132 return; 133 } 134 135 // colors 136 rgb_color bg = ui_color(B_PANEL_BACKGROUND_COLOR); 137 rgb_color lightenmax; 138 rgb_color lighten1; 139 rgb_color darken1; 140 rgb_color darken2; 141 rgb_color darken3; 142 rgb_color darkenmax; 143 144 rgb_color naviColor = ui_color(B_KEYBOARD_NAVIGATION_COLOR); 145 rgb_color knob; 146 rgb_color knobDark; 147 rgb_color knobLight; 148 149 if (IsEnabled()) { 150 lightenmax = tint_color(bg, B_LIGHTEN_MAX_TINT); 151 lighten1 = tint_color(bg, B_LIGHTEN_1_TINT); 152 darken1 = tint_color(bg, B_DARKEN_1_TINT); 153 darken2 = tint_color(bg, B_DARKEN_2_TINT); 154 darken3 = tint_color(bg, B_DARKEN_3_TINT); 155 darkenmax = tint_color(bg, B_DARKEN_MAX_TINT); 156 157 knob = naviColor; 158 knobDark = tint_color(naviColor, B_DARKEN_3_TINT); 159 knobLight = tint_color(naviColor, 0.15); 160 } else { 161 lightenmax = tint_color(bg, B_LIGHTEN_2_TINT); 162 lighten1 = bg; 163 darken1 = bg; 164 darken2 = tint_color(bg, B_DARKEN_1_TINT); 165 darken3 = tint_color(bg, B_DARKEN_2_TINT); 166 darkenmax = tint_color(bg, B_DISABLED_LABEL_TINT); 167 168 knob = tint_color(naviColor, B_LIGHTEN_2_TINT); 169 knobDark = tint_color(naviColor, B_LIGHTEN_1_TINT); 170 knobLight = tint_color(naviColor, (B_LIGHTEN_2_TINT 171 + B_LIGHTEN_MAX_TINT) / 2.0); 172 } 173 174 // dot 175 if (Value() == B_CONTROL_ON) { 176 // full 177 SetHighColor(knobDark); 178 FillEllipse(rect); 179 180 SetHighColor(knob); 181 FillEllipse(BRect(rect.left + 2, rect.top + 2, rect.right - 3, 182 rect.bottom - 3)); 183 184 SetHighColor(knobLight); 185 FillEllipse(BRect(rect.left + 3, rect.top + 3, rect.right - 5, 186 rect.bottom - 5)); 187 } else { 188 // empty 189 SetHighColor(lightenmax); 190 FillEllipse(rect); 191 } 192 193 rect.InsetBy(-1.0, -1.0); 194 195 // outer circle 196 if (fOutlined) { 197 // indicating "about to change value" 198 SetHighColor(darken3); 199 StrokeEllipse(rect); 200 } else { 201 SetHighColor(darken1); 202 StrokeArc(rect, 45.0, 180.0); 203 SetHighColor(lightenmax); 204 StrokeArc(rect, 45.0, -180.0); 205 } 206 207 rect.InsetBy(1, 1); 208 209 // inner circle 210 SetHighColor(darken3); 211 StrokeArc(rect, 45.0, 180.0); 212 SetHighColor(bg); 213 StrokeArc(rect, 45.0, -180.0); 214 215 // for faster font rendering, we can restore B_OP_COPY 216 SetDrawingMode(B_OP_COPY); 217 218 // label 219 SetHighColor(darkenmax); 220 DrawString(Label(), labelPos); 221 222 // underline label if focused 223 if (IsFocus()) { 224 SetHighColor(naviColor); 225 BPoint underLine = labelPos; 226 underLine.y += fontHeight.descent; 227 StrokeLine(underLine, underLine + BPoint(StringWidth(Label()), 0.0)); 228 } 229 } 230 231 232 void 233 BRadioButton::MouseDown(BPoint point) 234 { 235 if (!IsEnabled()) 236 return; 237 238 fOutlined = true; 239 240 if (Window()->Flags() & B_ASYNCHRONOUS_CONTROLS) { 241 Invalidate(); 242 SetTracking(true); 243 SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS); 244 } else { 245 _Redraw(); 246 247 BRect bounds = Bounds(); 248 uint32 buttons; 249 250 do { 251 snooze(40000); 252 253 GetMouse(&point, &buttons, true); 254 255 bool inside = bounds.Contains(point); 256 257 if (fOutlined != inside) { 258 fOutlined = inside; 259 _Redraw(); 260 } 261 } while (buttons != 0); 262 263 if (fOutlined) { 264 fOutlined = false; 265 _Redraw(); 266 SetValue(B_CONTROL_ON); 267 Invoke(); 268 } else { 269 _Redraw(); 270 } 271 } 272 } 273 274 275 void 276 BRadioButton::AttachedToWindow() 277 { 278 BControl::AttachedToWindow(); 279 } 280 281 282 void 283 BRadioButton::KeyDown(const char* bytes, int32 numBytes) 284 { 285 // TODO: Add selecting the next button functionality (navigating radio 286 // buttons with the cursor keys)! 287 288 switch (bytes[0]) { 289 case B_RETURN: 290 // override B_RETURN, which BControl would use to toggle the value 291 // but we don't allow setting the control to "off", only "on" 292 case B_SPACE: { 293 if (IsEnabled() && !Value()) { 294 SetValue(B_CONTROL_ON); 295 Invoke(); 296 } 297 298 break; 299 } 300 default: 301 BControl::KeyDown(bytes, numBytes); 302 } 303 } 304 305 306 void 307 BRadioButton::SetValue(int32 value) 308 { 309 if (value != Value()) { 310 BControl::SetValueNoUpdate(value); 311 Invalidate(_KnobFrame()); 312 } 313 314 if (!value) 315 return; 316 317 BView* parent = Parent(); 318 BView* child = NULL; 319 320 if (parent) { 321 // If the parent is a BBox, the group parent is the parent of the BBox 322 BBox* box = dynamic_cast<BBox*>(parent); 323 324 if (box && box->LabelView() == this) 325 parent = box->Parent(); 326 327 if (parent) { 328 BBox* box = dynamic_cast<BBox*>(parent); 329 330 // If the parent is a BBox, skip the label if there is one 331 if (box && box->LabelView()) 332 child = parent->ChildAt(1); 333 else 334 child = parent->ChildAt(0); 335 } else 336 child = Window()->ChildAt(0); 337 } else if (Window()) 338 child = Window()->ChildAt(0); 339 340 while (child) { 341 BRadioButton* radio = dynamic_cast<BRadioButton*>(child); 342 343 if (radio && (radio != this)) 344 radio->SetValue(B_CONTROL_OFF); 345 else { 346 // If the child is a BBox, check if the label is a radiobutton 347 BBox* box = dynamic_cast<BBox*>(child); 348 349 if (box && box->LabelView()) { 350 radio = dynamic_cast<BRadioButton*>(box->LabelView()); 351 352 if (radio && (radio != this)) 353 radio->SetValue(B_CONTROL_OFF); 354 } 355 } 356 357 child = child->NextSibling(); 358 } 359 360 ASSERT(Value() == B_CONTROL_ON); 361 } 362 363 364 void 365 BRadioButton::GetPreferredSize(float* _width, float* _height) 366 { 367 font_height fontHeight; 368 GetFontHeight(&fontHeight); 369 370 if (_width) { 371 BRect rect = _KnobFrame(fontHeight); 372 float width = rect.right + floorf(ceilf(fontHeight.ascent 373 + fontHeight.descent) / 2.0); 374 375 if (Label()) 376 width += StringWidth(Label()); 377 378 *_width = ceilf(width); 379 } 380 381 if (_height) 382 *_height = ceilf(fontHeight.ascent + fontHeight.descent) + 6.0; 383 } 384 385 386 void 387 BRadioButton::ResizeToPreferred() 388 { 389 BControl::ResizeToPreferred(); 390 } 391 392 393 status_t 394 BRadioButton::Invoke(BMessage* message) 395 { 396 return BControl::Invoke(message); 397 } 398 399 400 void 401 BRadioButton::MessageReceived(BMessage* message) 402 { 403 BControl::MessageReceived(message); 404 } 405 406 407 void 408 BRadioButton::WindowActivated(bool active) 409 { 410 BControl::WindowActivated(active); 411 } 412 413 414 void 415 BRadioButton::MouseUp(BPoint point) 416 { 417 if (!IsTracking()) 418 return; 419 420 fOutlined = Bounds().Contains(point); 421 if (fOutlined) { 422 fOutlined = false; 423 SetValue(B_CONTROL_ON); 424 Invoke(); 425 } 426 Invalidate(); 427 428 SetTracking(false); 429 } 430 431 432 void 433 BRadioButton::MouseMoved(BPoint point, uint32 transit, const BMessage* message) 434 { 435 if (!IsTracking()) 436 return; 437 438 bool inside = Bounds().Contains(point); 439 440 if (fOutlined != inside) { 441 fOutlined = inside; 442 Invalidate(); 443 } 444 } 445 446 447 void 448 BRadioButton::DetachedFromWindow() 449 { 450 BControl::DetachedFromWindow(); 451 } 452 453 454 void 455 BRadioButton::FrameMoved(BPoint newLocation) 456 { 457 BControl::FrameMoved(newLocation); 458 } 459 460 461 void 462 BRadioButton::FrameResized(float width, float height) 463 { 464 Invalidate(); 465 BControl::FrameResized(width, height); 466 } 467 468 469 BHandler* 470 BRadioButton::ResolveSpecifier(BMessage* message, int32 index, 471 BMessage* specifier, int32 what, const char* property) 472 { 473 return BControl::ResolveSpecifier(message, index, specifier, what, 474 property); 475 } 476 477 478 void 479 BRadioButton::MakeFocus(bool focused) 480 { 481 BControl::MakeFocus(focused); 482 } 483 484 485 void 486 BRadioButton::AllAttached() 487 { 488 BControl::AllAttached(); 489 } 490 491 492 void 493 BRadioButton::AllDetached() 494 { 495 BControl::AllDetached(); 496 } 497 498 499 status_t 500 BRadioButton::GetSupportedSuites(BMessage* message) 501 { 502 return BControl::GetSupportedSuites(message); 503 } 504 505 506 status_t 507 BRadioButton::Perform(perform_code code, void* _data) 508 { 509 switch (code) { 510 case PERFORM_CODE_MIN_SIZE: 511 ((perform_data_min_size*)_data)->return_value 512 = BRadioButton::MinSize(); 513 return B_OK; 514 case PERFORM_CODE_MAX_SIZE: 515 ((perform_data_max_size*)_data)->return_value 516 = BRadioButton::MaxSize(); 517 return B_OK; 518 case PERFORM_CODE_PREFERRED_SIZE: 519 ((perform_data_preferred_size*)_data)->return_value 520 = BRadioButton::PreferredSize(); 521 return B_OK; 522 case PERFORM_CODE_LAYOUT_ALIGNMENT: 523 ((perform_data_layout_alignment*)_data)->return_value 524 = BRadioButton::LayoutAlignment(); 525 return B_OK; 526 case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH: 527 ((perform_data_has_height_for_width*)_data)->return_value 528 = BRadioButton::HasHeightForWidth(); 529 return B_OK; 530 case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH: 531 { 532 perform_data_get_height_for_width* data 533 = (perform_data_get_height_for_width*)_data; 534 BRadioButton::GetHeightForWidth(data->width, &data->min, &data->max, 535 &data->preferred); 536 return B_OK; 537 } 538 case PERFORM_CODE_SET_LAYOUT: 539 { 540 perform_data_set_layout* data = (perform_data_set_layout*)_data; 541 BRadioButton::SetLayout(data->layout); 542 return B_OK; 543 } 544 case PERFORM_CODE_INVALIDATE_LAYOUT: 545 { 546 perform_data_invalidate_layout* data 547 = (perform_data_invalidate_layout*)_data; 548 BRadioButton::InvalidateLayout(data->descendants); 549 return B_OK; 550 } 551 case PERFORM_CODE_DO_LAYOUT: 552 { 553 BRadioButton::DoLayout(); 554 return B_OK; 555 } 556 } 557 558 return BControl::Perform(code, _data); 559 } 560 561 562 BSize 563 BRadioButton::MaxSize() 564 { 565 float width, height; 566 GetPreferredSize(&width, &height); 567 568 return BLayoutUtils::ComposeSize(ExplicitMaxSize(), 569 BSize(B_SIZE_UNLIMITED, height)); 570 } 571 572 573 574 575 void BRadioButton::_ReservedRadioButton1() {} 576 void BRadioButton::_ReservedRadioButton2() {} 577 578 579 BRadioButton& 580 BRadioButton::operator=(const BRadioButton &) 581 { 582 return *this; 583 } 584 585 586 BRect 587 BRadioButton::_KnobFrame() const 588 { 589 font_height fontHeight; 590 GetFontHeight(&fontHeight); 591 return _KnobFrame(fontHeight); 592 } 593 594 595 BRect 596 BRadioButton::_KnobFrame(const font_height& fontHeight) const 597 { 598 if (be_control_look != NULL) { 599 // Same as BCheckBox... 600 return BRect(0.0f, 2.0f, ceilf(3.0f + fontHeight.ascent), 601 ceilf(5.0f + fontHeight.ascent)); 602 } 603 604 // layout the rect for the dot 605 BRect rect(Bounds()); 606 607 // its size depends on the text height 608 float textHeight = ceilf(fontHeight.ascent + fontHeight.descent); 609 float inset = -floorf(textHeight / 2 - 2); 610 611 rect.left -= (inset - 1); 612 rect.top = floorf((rect.top + rect.bottom) / 2.0); 613 rect.bottom = rect.top; 614 rect.right = rect.left; 615 rect.InsetBy(inset, inset); 616 617 return rect; 618 } 619 620 621 void 622 BRadioButton::_Redraw() 623 { 624 BRect b(Bounds()); 625 // fill background with ViewColor() 626 rgb_color highColor = HighColor(); 627 SetHighColor(ViewColor()); 628 FillRect(b); 629 // restore previous HighColor() 630 SetHighColor(highColor); 631 Draw(b); 632 Flush(); 633 } 634