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
24BRadioButton::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
38BRadioButton::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
47BRadioButton::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
55BRadioButton::BRadioButton(BMessage* data)
56	:
57	BControl(data),
58	fOutlined(false)
59{
60}
61
62
63BRadioButton::~BRadioButton()
64{
65}
66
67
68BArchivable*
69BRadioButton::Instantiate(BMessage* data)
70{
71	if (validate_instantiation(data, "BRadioButton"))
72		return new BRadioButton(data);
73
74	return NULL;
75}
76
77
78status_t
79BRadioButton::Archive(BMessage* data, bool deep) const
80{
81	return BControl::Archive(data, deep);
82}
83
84
85void
86BRadioButton::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
118void
119BRadioButton::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
158void
159BRadioButton::AttachedToWindow()
160{
161	BControl::AttachedToWindow();
162}
163
164
165void
166BRadioButton::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
189void
190BRadioButton::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
247void
248BRadioButton::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
279void
280BRadioButton::ResizeToPreferred()
281{
282	BControl::ResizeToPreferred();
283}
284
285
286status_t
287BRadioButton::Invoke(BMessage* message)
288{
289	return BControl::Invoke(message);
290}
291
292
293void
294BRadioButton::MessageReceived(BMessage* message)
295{
296	BControl::MessageReceived(message);
297}
298
299
300void
301BRadioButton::WindowActivated(bool active)
302{
303	BControl::WindowActivated(active);
304}
305
306
307void
308BRadioButton::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
327void
328BRadioButton::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
343void
344BRadioButton::DetachedFromWindow()
345{
346	BControl::DetachedFromWindow();
347}
348
349
350void
351BRadioButton::FrameMoved(BPoint newPosition)
352{
353	BControl::FrameMoved(newPosition);
354}
355
356
357void
358BRadioButton::FrameResized(float newWidth, float newHeight)
359{
360	Invalidate();
361	BControl::FrameResized(newWidth, newHeight);
362}
363
364
365BHandler*
366BRadioButton::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
374void
375BRadioButton::MakeFocus(bool focus)
376{
377	BControl::MakeFocus(focus);
378}
379
380
381void
382BRadioButton::AllAttached()
383{
384	BControl::AllAttached();
385}
386
387
388void
389BRadioButton::AllDetached()
390{
391	BControl::AllDetached();
392}
393
394
395status_t
396BRadioButton::GetSupportedSuites(BMessage* message)
397{
398	return BControl::GetSupportedSuites(message);
399}
400
401
402status_t
403BRadioButton::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
472BSize
473BRadioButton::MaxSize()
474{
475	float width, height;
476	GetPreferredSize(&width, &height);
477
478	return BLayoutUtils::ComposeSize(ExplicitMaxSize(),
479		BSize(width, height));
480}
481
482
483BAlignment
484BRadioButton::LayoutAlignment()
485{
486	return BLayoutUtils::ComposeAlignment(ExplicitAlignment(),
487		BAlignment(B_ALIGN_LEFT, B_ALIGN_VERTICAL_UNSET));
488}
489
490
491status_t
492BRadioButton::SetIcon(const BBitmap* icon, uint32 flags)
493{
494	return BControl::SetIcon(icon, flags | B_CREATE_DISABLED_ICON_BITMAPS);
495}
496
497
498void BRadioButton::_ReservedRadioButton1() {}
499void BRadioButton::_ReservedRadioButton2() {}
500
501
502BRadioButton&
503BRadioButton::operator=(const BRadioButton &)
504{
505	return *this;
506}
507
508
509BRect
510BRadioButton::_KnobFrame() const
511{
512	font_height fontHeight;
513	GetFontHeight(&fontHeight);
514	return _KnobFrame(fontHeight);
515}
516
517
518BRect
519BRadioButton::_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
527void
528BRadioButton::_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