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