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