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