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