xref: /haiku/src/kits/interface/RadioButton.cpp (revision 22440f4105cafc95cc1d49f9bc65bb395c527d86)
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 #include <RadioButton.h>
12 
13 #include <algorithm>
14 
15 #include <Box.h>
16 #include <ControlLook.h>
17 #include <Debug.h>
18 #include <LayoutUtils.h>
19 #include <Window.h>
20 
21 #include <binary_compatibility/Interface.h>
22 
23 
24 BRadioButton::BRadioButton(BRect frame, const char* name, const char* label,
25 	BMessage* message, uint32 resizingMode, uint32 flags)
26 	:
27 	BControl(frame, name, label, message, resizingMode, flags | B_FRAME_EVENTS),
28 	fOutlined(false)
29 {
30 	// Resize to minimum height if needed for BeOS compatibility
31 	float minHeight;
32 	GetPreferredSize(NULL, &minHeight);
33 	if (Bounds().Height() < minHeight)
34 		ResizeTo(Bounds().Width(), minHeight);
35 }
36 
37 
38 BRadioButton::BRadioButton(const char* name, const char* label,
39 	BMessage* message, uint32 flags)
40 	:
41 	BControl(name, label, message, flags | B_FRAME_EVENTS),
42 	fOutlined(false)
43 {
44 }
45 
46 
47 BRadioButton::BRadioButton(const char* label, BMessage* message)
48 	:
49 	BControl(NULL, label, message, B_WILL_DRAW | B_NAVIGABLE | B_FRAME_EVENTS),
50 	fOutlined(false)
51 {
52 }
53 
54 
55 BRadioButton::BRadioButton(BMessage* data)
56 	:
57 	BControl(data),
58 	fOutlined(false)
59 {
60 }
61 
62 
63 BRadioButton::~BRadioButton()
64 {
65 }
66 
67 
68 BArchivable*
69 BRadioButton::Instantiate(BMessage* data)
70 {
71 	if (validate_instantiation(data, "BRadioButton"))
72 		return new BRadioButton(data);
73 
74 	return NULL;
75 }
76 
77 
78 status_t
79 BRadioButton::Archive(BMessage* data, bool deep) const
80 {
81 	return BControl::Archive(data, 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 	rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
93 
94 	uint32 flags = be_control_look->Flags(this);
95 	if (fOutlined)
96 		flags |= BControlLook::B_CLICKED;
97 
98 	BRect knobRect(_KnobFrame(fontHeight));
99 	BRect rect(knobRect);
100 	be_control_look->DrawRadioButton(this, rect, updateRect, base, flags);
101 
102 	BRect labelRect(Bounds());
103 	labelRect.left = knobRect.right + 1
104 		+ be_control_look->DefaultLabelSpacing();
105 
106 	const BBitmap* icon = IconBitmap(
107 		B_INACTIVE_ICON_BITMAP | (IsEnabled() ? 0 : B_DISABLED_ICON_BITMAP));
108 
109 	be_control_look->DrawLabel(this, Label(), icon, labelRect, updateRect,
110 		base, flags);
111 }
112 
113 
114 void
115 BRadioButton::MouseDown(BPoint where)
116 {
117 	if (!IsEnabled())
118 		return;
119 
120 	fOutlined = true;
121 
122 	if ((Window()->Flags() & B_ASYNCHRONOUS_CONTROLS) != 0) {
123 		Invalidate();
124 		SetTracking(true);
125 		SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS);
126 	} else {
127 		_Redraw();
128 
129 		BRect bounds = Bounds();
130 		uint32 buttons;
131 
132 		do {
133 			snooze(40000);
134 			GetMouse(&where, &buttons, true);
135 			bool inside = bounds.Contains(where);
136 
137 			if (fOutlined != inside) {
138 				fOutlined = inside;
139 				_Redraw();
140 			}
141 		} while (buttons != 0);
142 
143 		if (fOutlined) {
144 			fOutlined = false;
145 			_Redraw();
146 			SetValue(B_CONTROL_ON);
147 			Invoke();
148 		} else
149 			_Redraw();
150 	}
151 }
152 
153 
154 void
155 BRadioButton::AttachedToWindow()
156 {
157 	BControl::AttachedToWindow();
158 }
159 
160 
161 void
162 BRadioButton::KeyDown(const char* bytes, int32 numBytes)
163 {
164 	// TODO: Add selecting the next button functionality (navigating radio
165 	// buttons with the cursor keys)!
166 
167 	switch (bytes[0]) {
168 		case B_RETURN:
169 			// override B_RETURN, which BControl would use to toggle the value
170 			// but we don't allow setting the control to "off", only "on"
171 		case B_SPACE: {
172 			if (IsEnabled() && !Value()) {
173 				SetValue(B_CONTROL_ON);
174 				Invoke();
175 			}
176 			break;
177 		}
178 
179 		default:
180 			BControl::KeyDown(bytes, numBytes);
181 	}
182 }
183 
184 
185 void
186 BRadioButton::SetValue(int32 value)
187 {
188 	if (value != Value()) {
189 		BControl::SetValueNoUpdate(value);
190 		Invalidate(_KnobFrame());
191 	}
192 
193 	if (value == 0)
194 		return;
195 
196 	BView* parent = Parent();
197 	BView* child = NULL;
198 
199 	if (parent != NULL) {
200 		// If the parent is a BBox, the group parent is the parent of the BBox
201 		BBox* box = dynamic_cast<BBox*>(parent);
202 
203 		if (box != NULL && box->LabelView() == this)
204 			parent = box->Parent();
205 
206 		if (parent != NULL) {
207 			BBox* box = dynamic_cast<BBox*>(parent);
208 
209 			// If the parent is a BBox, skip the label if there is one
210 			if (box != NULL && box->LabelView())
211 				child = parent->ChildAt(1);
212 			else
213 				child = parent->ChildAt(0);
214 		} else
215 			child = Window()->ChildAt(0);
216 	} else if (Window() != NULL)
217 		child = Window()->ChildAt(0);
218 
219 	while (child != NULL) {
220 		BRadioButton* radio = dynamic_cast<BRadioButton*>(child);
221 
222 		if (radio != NULL && (radio != this))
223 			radio->SetValue(B_CONTROL_OFF);
224 		else {
225 			// If the child is a BBox, check if the label is a radiobutton
226 			BBox* box = dynamic_cast<BBox*>(child);
227 
228 			if (box != NULL && box->LabelView()) {
229 				radio = dynamic_cast<BRadioButton*>(box->LabelView());
230 
231 				if (radio != NULL && (radio != this))
232 					radio->SetValue(B_CONTROL_OFF);
233 			}
234 		}
235 
236 		child = child->NextSibling();
237 	}
238 
239 	ASSERT(Value() == B_CONTROL_ON);
240 }
241 
242 
243 void
244 BRadioButton::GetPreferredSize(float* _width, float* _height)
245 {
246 	font_height fontHeight;
247 	GetFontHeight(&fontHeight);
248 
249 	BRect rect(_KnobFrame(fontHeight));
250 	float width = rect.right + rect.left;
251 	float height = rect.bottom + rect.top;
252 
253 	const BBitmap* icon = IconBitmap(B_INACTIVE_ICON_BITMAP);
254 	if (icon != NULL) {
255 		width += be_control_look->DefaultLabelSpacing()
256 			+ icon->Bounds().Width() + 1;
257 		height = std::max(height, icon->Bounds().Height());
258 	}
259 
260 	if (const char* label = Label()) {
261 		width += be_control_look->DefaultLabelSpacing()
262 			+ ceilf(StringWidth(label));
263 		height = std::max(height,
264 			ceilf(6.0f + fontHeight.ascent + fontHeight.descent));
265 	}
266 
267 	if (_width != NULL)
268 		*_width = width;
269 
270 	if (_height != NULL)
271 		*_height = height;
272 }
273 
274 
275 void
276 BRadioButton::ResizeToPreferred()
277 {
278 	BControl::ResizeToPreferred();
279 }
280 
281 
282 status_t
283 BRadioButton::Invoke(BMessage* message)
284 {
285 	return BControl::Invoke(message);
286 }
287 
288 
289 void
290 BRadioButton::MessageReceived(BMessage* message)
291 {
292 	BControl::MessageReceived(message);
293 }
294 
295 
296 void
297 BRadioButton::WindowActivated(bool active)
298 {
299 	BControl::WindowActivated(active);
300 }
301 
302 
303 void
304 BRadioButton::MouseUp(BPoint where)
305 {
306 	if (!IsTracking())
307 		return;
308 
309 	fOutlined = Bounds().Contains(where);
310 	if (fOutlined) {
311 		fOutlined = false;
312 		if (Value() != B_CONTROL_ON) {
313 			SetValue(B_CONTROL_ON);
314 			Invoke();
315 		}
316 	}
317 	Invalidate();
318 
319 	SetTracking(false);
320 }
321 
322 
323 void
324 BRadioButton::MouseMoved(BPoint where, uint32 code,
325 	const BMessage* dragMessage)
326 {
327 	if (!IsTracking())
328 		return;
329 
330 	bool inside = Bounds().Contains(where);
331 
332 	if (fOutlined != inside) {
333 		fOutlined = inside;
334 		Invalidate();
335 	}
336 }
337 
338 
339 void
340 BRadioButton::DetachedFromWindow()
341 {
342 	BControl::DetachedFromWindow();
343 }
344 
345 
346 void
347 BRadioButton::FrameMoved(BPoint newPosition)
348 {
349 	BControl::FrameMoved(newPosition);
350 }
351 
352 
353 void
354 BRadioButton::FrameResized(float newWidth, float newHeight)
355 {
356 	Invalidate();
357 	BControl::FrameResized(newWidth, newHeight);
358 }
359 
360 
361 BHandler*
362 BRadioButton::ResolveSpecifier(BMessage* message, int32 index,
363 	BMessage* specifier, int32 what, const char* property)
364 {
365 	return BControl::ResolveSpecifier(message, index, specifier, what,
366 		property);
367 }
368 
369 
370 void
371 BRadioButton::MakeFocus(bool focus)
372 {
373 	BControl::MakeFocus(focus);
374 }
375 
376 
377 void
378 BRadioButton::AllAttached()
379 {
380 	BControl::AllAttached();
381 }
382 
383 
384 void
385 BRadioButton::AllDetached()
386 {
387 	BControl::AllDetached();
388 }
389 
390 
391 status_t
392 BRadioButton::GetSupportedSuites(BMessage* message)
393 {
394 	return BControl::GetSupportedSuites(message);
395 }
396 
397 
398 status_t
399 BRadioButton::Perform(perform_code code, void* _data)
400 {
401 	switch (code) {
402 		case PERFORM_CODE_MIN_SIZE:
403 			((perform_data_min_size*)_data)->return_value
404 				= BRadioButton::MinSize();
405 			return B_OK;
406 
407 		case PERFORM_CODE_MAX_SIZE:
408 			((perform_data_max_size*)_data)->return_value
409 				= BRadioButton::MaxSize();
410 			return B_OK;
411 
412 		case PERFORM_CODE_PREFERRED_SIZE:
413 			((perform_data_preferred_size*)_data)->return_value
414 				= BRadioButton::PreferredSize();
415 			return B_OK;
416 
417 		case PERFORM_CODE_LAYOUT_ALIGNMENT:
418 			((perform_data_layout_alignment*)_data)->return_value
419 				= BRadioButton::LayoutAlignment();
420 			return B_OK;
421 
422 		case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH:
423 			((perform_data_has_height_for_width*)_data)->return_value
424 				= BRadioButton::HasHeightForWidth();
425 			return B_OK;
426 
427 		case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH:
428 		{
429 			perform_data_get_height_for_width* data
430 				= (perform_data_get_height_for_width*)_data;
431 			BRadioButton::GetHeightForWidth(data->width, &data->min, &data->max,
432 				&data->preferred);
433 			return B_OK;
434 		}
435 
436 		case PERFORM_CODE_SET_LAYOUT:
437 		{
438 			perform_data_set_layout* data = (perform_data_set_layout*)_data;
439 			BRadioButton::SetLayout(data->layout);
440 			return B_OK;
441 		}
442 
443 		case PERFORM_CODE_LAYOUT_INVALIDATED:
444 		{
445 			perform_data_layout_invalidated* data
446 				= (perform_data_layout_invalidated*)_data;
447 			BRadioButton::LayoutInvalidated(data->descendants);
448 			return B_OK;
449 		}
450 
451 		case PERFORM_CODE_DO_LAYOUT:
452 		{
453 			BRadioButton::DoLayout();
454 			return B_OK;
455 		}
456 
457 		case PERFORM_CODE_SET_ICON:
458 		{
459 			perform_data_set_icon* data = (perform_data_set_icon*)_data;
460 			return BRadioButton::SetIcon(data->icon, data->flags);
461 		}
462 	}
463 
464 	return BControl::Perform(code, _data);
465 }
466 
467 
468 BSize
469 BRadioButton::MaxSize()
470 {
471 	float width, height;
472 	GetPreferredSize(&width, &height);
473 
474 	return BLayoutUtils::ComposeSize(ExplicitMaxSize(),
475 		BSize(width, height));
476 }
477 
478 
479 BAlignment
480 BRadioButton::LayoutAlignment()
481 {
482 	return BLayoutUtils::ComposeAlignment(ExplicitAlignment(),
483 		BAlignment(B_ALIGN_LEFT, B_ALIGN_VERTICAL_UNSET));
484 }
485 
486 
487 status_t
488 BRadioButton::SetIcon(const BBitmap* icon, uint32 flags)
489 {
490 	return BControl::SetIcon(icon, flags | B_CREATE_DISABLED_ICON_BITMAPS);
491 }
492 
493 
494 void BRadioButton::_ReservedRadioButton1() {}
495 void BRadioButton::_ReservedRadioButton2() {}
496 
497 
498 BRadioButton&
499 BRadioButton::operator=(const BRadioButton &)
500 {
501 	return *this;
502 }
503 
504 
505 BRect
506 BRadioButton::_KnobFrame() const
507 {
508 	font_height fontHeight;
509 	GetFontHeight(&fontHeight);
510 	return _KnobFrame(fontHeight);
511 }
512 
513 
514 BRect
515 BRadioButton::_KnobFrame(const font_height& fontHeight) const
516 {
517 	// Same as BCheckBox...
518 	return BRect(0.0f, 2.0f, ceilf(3.0f + fontHeight.ascent),
519 		ceilf(5.0f + fontHeight.ascent));
520 }
521 
522 
523 void
524 BRadioButton::_Redraw()
525 {
526 	BRect bounds(Bounds());
527 
528 	// fill background with ViewColor()
529 	rgb_color highColor = HighColor();
530 	SetHighColor(ViewColor());
531 	FillRect(bounds);
532 
533 	// restore previous HighColor()
534 	SetHighColor(highColor);
535 	Draw(bounds);
536 	Flush();
537 }
538