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