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