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