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