xref: /haiku/src/kits/interface/RadioButton.cpp (revision 50b3e74489a1a46fec88df793e4f6780e4de933c)
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