xref: /haiku/src/kits/interface/RadioButton.cpp (revision 0b2dbe7d46ee888392907c60131b7f7652314175)
1 //------------------------------------------------------------------------------
2 //	Copyright (c) 2001-2005, Haiku, Inc.
3 //
4 //	Distributed under the terms of the MIT license.
5 //
6 //	File Name:		RadioButton.cpp
7 //	Authors:		Marc Flerackers (mflerackers@androme.be)
8 //					Stephan Aßmus <superstippi@gmx.de>
9 //	Description:	BRadioButton represents a single on/off button.  All
10 //                  sibling BRadioButton objects comprise a single
11 //                  "multiple choice" control.
12 //------------------------------------------------------------------------------
13 
14 // Standard Includes -----------------------------------------------------------
15 
16 // System Includes -------------------------------------------------------------
17 #include <RadioButton.h>
18 #include <Errors.h>
19 #include <Box.h>
20 #include <Window.h>
21 
22 // Project Includes ------------------------------------------------------------
23 
24 // Local Includes --------------------------------------------------------------
25 
26 // Local Defines ---------------------------------------------------------------
27 
28 // Globals ---------------------------------------------------------------------
29 
30 //------------------------------------------------------------------------------
31 BRadioButton::BRadioButton(BRect frame, const char *name, const char *label,
32 						   BMessage *message, uint32 resizMask, uint32 flags)
33 	:	BControl(frame, name, label, message, resizMask, flags),
34 		fOutlined(false)
35 {
36 	// Resize to minimum height if needed
37 	font_height fh;
38 	GetFontHeight(&fh);
39 	float minHeight = (float)ceil(6.0f + fh.ascent + fh.descent);
40 	if (Bounds().Height() < minHeight)
41 		ResizeTo(Bounds().Width(), minHeight);
42 }
43 //------------------------------------------------------------------------------
44 BRadioButton::BRadioButton(BMessage *archive)
45 	:	BControl(archive),
46 		fOutlined(false)
47 {
48 }
49 //------------------------------------------------------------------------------
50 BRadioButton::~BRadioButton()
51 {
52 }
53 //------------------------------------------------------------------------------
54 BArchivable *BRadioButton::Instantiate(BMessage *archive)
55 {
56 	if (validate_instantiation(archive, "BRadioButton"))
57 		return new BRadioButton(archive);
58 	else
59 		return NULL;
60 }
61 //------------------------------------------------------------------------------
62 status_t BRadioButton::Archive(BMessage *archive, bool deep) const
63 {
64 	return BControl::Archive(archive, deep);
65 }
66 //------------------------------------------------------------------------------
67 void BRadioButton::Draw(BRect updateRect)
68 {
69 	// NOTE: the commented out StrokeLine() calls appearently tweak the
70 	// rendering on R5, but they are unnecessary under Haiku
71 
72 	font_height fh;
73 	GetFontHeight(&fh);
74 
75 	// layout the rect for the dot
76 	BRect rect = _KnobFrame();
77 
78 	// its size depends on the text height
79 	float textHeight = floorf(fh.ascent + fh.descent + 0.5);
80 
81 	BPoint labelPos(rect.right + floorf(textHeight / 2.0),
82 					floorf((rect.top + rect.bottom + textHeight) / 2.0 - fh.descent + 0.5) + 1.0);
83 
84 	// If the focus is changing, just redraw the focus indicator
85 	if (IsFocusChanging()) {
86 		if (IsFocus())
87 			SetHighColor(ui_color(B_KEYBOARD_NAVIGATION_COLOR));
88 		else
89 			SetHighColor(ui_color(B_PANEL_BACKGROUND_COLOR));
90 
91 		BPoint underLine = labelPos;
92 		underLine.y += fh.descent;
93 		StrokeLine(underLine, underLine + BPoint(StringWidth(Label()), 0.0));
94 
95 		return;
96 	}
97 
98 	// Placeholder until sBitmaps is filled in
99 	rgb_color no_tint = ui_color(B_PANEL_BACKGROUND_COLOR),
100 	lighten1 = tint_color(no_tint, B_LIGHTEN_1_TINT),
101 	lightenmax = tint_color(no_tint, B_LIGHTEN_MAX_TINT),
102 	darken1 = tint_color(no_tint, B_DARKEN_1_TINT),
103 	darken2 = tint_color(no_tint, B_DARKEN_2_TINT),
104 	darken3 = tint_color(no_tint, B_DARKEN_3_TINT),
105 	//darken4 = tint_color(no_tint, B_DARKEN_4_TINT),
106 	darkenmax = tint_color(no_tint, B_DARKEN_MAX_TINT);
107 
108 	// NOTE: this improves drawing a lot because of
109 	// anti-aliased circles and arcs in Haiku
110 	SetDrawingMode(B_OP_OVER);
111 
112 	if (IsEnabled()) {
113 		// Dot
114 		if (Value() == B_CONTROL_ON) {
115 			rgb_color kb_color = ui_color(B_KEYBOARD_NAVIGATION_COLOR);
116 
117 			SetHighColor(tint_color(kb_color, B_DARKEN_3_TINT));
118 			FillEllipse(rect);
119 			SetHighColor(kb_color);
120 			FillEllipse(BRect(rect.left + 3, rect.top + 3, rect.right - 4, rect.bottom - 4));
121 			SetHighColor(tint_color(kb_color, B_DARKEN_3_TINT));
122 			StrokeLine(BPoint(rect.right - 5, rect.bottom - 4),
123 					   BPoint(rect.right - 4, rect.bottom - 5));
124 			SetHighColor(tint_color(kb_color, B_LIGHTEN_MAX_TINT));
125 			StrokeLine(BPoint(rect.left + 4, rect.top + 5),
126 					   BPoint(rect.left + 5, rect.top + 4));
127 
128 		} else {
129 			SetHighColor(lightenmax);
130 			FillEllipse(rect);
131 		}
132 
133 		rect.InsetBy(-1.0, -1.0);
134 
135 		// Outer circle
136 		if (fOutlined) {
137 			SetHighColor(darken3);
138 			StrokeEllipse(rect);
139 		} else {
140 			SetHighColor(darken1);
141 			StrokeArc(rect, 45.0f, 180.0f);
142 			SetHighColor(lightenmax);
143 			StrokeArc(rect, 45.0f, -180.0f);
144 		}
145 
146 		rect.InsetBy(1, 1);
147 
148 		// Inner circle
149 		SetHighColor(darken3);
150 		StrokeArc(rect, 45.0f, 180.0f);
151 //		StrokeLine(BPoint(rect.left + 1, rect.top + 1),
152 //			BPoint(rect.left + 1, rect.top + 1));
153 		SetHighColor(no_tint);
154 		StrokeArc(rect, 45.0f, -180.0f);
155 //		StrokeLine(BPoint(rect.left + 1, rect.bottom - 1),
156 //			BPoint(rect.left + 1, rect.bottom - 1));
157 //		StrokeLine(BPoint(rect.right - 1, rect.bottom - 1),
158 //			BPoint(rect.right - 1, rect.bottom - 1));
159 //		StrokeLine(BPoint(rect.right - 1, rect.top + 1),
160 //			BPoint(rect.right - 1, rect.top + 1));
161 
162 		// For faster font rendering, we can restore B_OP_COPY
163 		SetDrawingMode(B_OP_COPY);
164 
165 		// Label
166 		SetHighColor(darkenmax);
167 		DrawString(Label(), labelPos);
168 
169 		// Focus
170 		if (IsFocus()) {
171 			SetHighColor(ui_color(B_KEYBOARD_NAVIGATION_COLOR));
172 			BPoint underLine = labelPos;
173 			underLine.y += fh.descent;
174 			StrokeLine(underLine, underLine + BPoint(StringWidth(Label()), 0.0));
175 		}
176 	} else {
177 		// Dot
178 		if (Value() == B_CONTROL_ON) {
179 			rgb_color kb_color = ui_color(B_KEYBOARD_NAVIGATION_COLOR);
180 
181 			SetHighColor(tint_color(kb_color, B_LIGHTEN_2_TINT));
182 			FillEllipse(rect);
183 			SetHighColor(tint_color(kb_color, B_LIGHTEN_MAX_TINT));
184 			StrokeLine(BPoint(rect.left + 4, rect.top + 5),
185 				BPoint(rect.left + 5, rect.top + 4));
186 			SetHighColor(tint_color(kb_color, B_DARKEN_3_TINT));
187 			StrokeArc(BRect(rect.left + 2, rect.top + 2, rect.right - 2, rect.bottom - 2),
188 				45.0f, -180.0f);
189 		} else {
190 			SetHighColor(lighten1);
191 			FillEllipse(rect);
192 		}
193 
194 		rect.InsetBy(-1.0, -1.0);
195 
196 		// Outer circle
197 		SetHighColor(no_tint);
198 		StrokeArc(rect, 45.0f, 180.0f);
199 		SetHighColor(lighten1);
200 		StrokeArc(rect, 45.0f, -180.0f);
201 
202 		rect.InsetBy(1, 1);
203 
204 		// Inner circle
205 		SetHighColor(darken2);
206 		StrokeArc(rect, 45.0f, 180.0f);
207 //		StrokeLine(BPoint(rect.left + 1, rect.top + 1),
208 //			BPoint(rect.left + 1, rect.top + 1));
209 		SetHighColor(no_tint);
210 		StrokeArc(rect, 45.0f, -180.0f);
211 //		StrokeLine(BPoint(rect.left + 1, rect.bottom - 1),
212 //			BPoint(rect.left + 1, rect.bottom - 1));
213 //		StrokeLine(BPoint(rect.right - 1, rect.bottom - 1),
214 //			BPoint(rect.right - 1, rect.bottom - 1));
215 //		StrokeLine(BPoint(rect.right - 1, rect.top + 1),
216 //			BPoint(rect.right - 1, rect.top + 1));
217 
218 		// Label
219 		SetHighColor(tint_color(no_tint, B_DISABLED_LABEL_TINT));
220 		DrawString(Label(), labelPos);
221 	}
222 }
223 //------------------------------------------------------------------------------
224 void BRadioButton::MouseDown(BPoint point)
225 {
226 	if (!IsEnabled())
227 		return;
228 
229 	fOutlined = true;
230 
231 	if (Window()->Flags() & B_ASYNCHRONOUS_CONTROLS) {
232 		Invalidate();
233 		SetTracking(true);
234 		SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS);
235 	} else {
236 		Draw(Bounds());
237 		Flush();
238 
239 		BRect bounds = Bounds();
240 		uint32 buttons;
241 
242 		do {
243 			snooze(40000);
244 
245 			GetMouse(&point, &buttons, true);
246 
247 			bool inside = bounds.Contains(point);
248 
249 			if (fOutlined != inside) {
250 				fOutlined = inside;
251 				Draw(Bounds());
252 				Flush();
253 			}
254 		} while (buttons != 0);
255 
256 		if (fOutlined) {
257 			fOutlined = false;
258 			Draw(Bounds());
259 			Flush();
260 			SetValue(B_CONTROL_ON);
261 			Invoke();
262 		} else {
263 			Draw(Bounds());
264 			Flush();
265 		}
266 	}
267 
268 }
269 //------------------------------------------------------------------------------
270 void BRadioButton::AttachedToWindow()
271 {
272 	BControl::AttachedToWindow();
273 }
274 //------------------------------------------------------------------------------
275 void BRadioButton::KeyDown(const char *bytes, int32 numBytes)
276 {
277 	// TODO add select_next_button functionality
278 
279 	switch (bytes[0])
280 	{
281 		case ' ':
282 		{
283 			if (IsEnabled() && !Value())
284 			{
285 				SetValue(B_CONTROL_ON);
286 				Invoke();
287 			}
288 
289 			break;
290 		}
291 		default:
292 			BControl::KeyDown(bytes, numBytes);
293 	}
294 }
295 //------------------------------------------------------------------------------
296 void BRadioButton::SetValue(int32 value)
297 {
298 	if (value != Value()) {
299 		BControl::SetValue(value);
300 		Invalidate(_KnobFrame());
301 	}
302 
303 	if (!value)
304 		return;
305 
306 	BView *parent = Parent();
307 	BView *child = NULL;
308 
309 	if (parent)
310 	{
311 		// If the parent is a BBox, the group parent is the parent of the BBox
312 		BBox *box = dynamic_cast<BBox*>(parent);
313 
314 		if (box && box->LabelView() == this)
315 			parent = box->Parent();
316 
317 		if (parent)
318 		{
319 			BBox *box = dynamic_cast<BBox*>(parent);
320 
321 			// If the parent is a BBox, skip the label if there is one
322 			if (box && box->LabelView())
323 				child = parent->ChildAt(1);
324 			else
325 				child = parent->ChildAt(0);
326 		}
327 		else
328 			child = Window()->ChildAt(0);
329 	}
330 	else if (Window())
331 		child = Window()->ChildAt(0);
332 
333 	while (child)
334 	{
335 		BRadioButton *radio = dynamic_cast<BRadioButton*>(child);
336 
337 		if (child != this && radio)
338 			radio->SetValue(B_CONTROL_OFF);
339 		else
340 		{
341 			// If the child is a BBox, check if the label is a radiobutton
342 			BBox *box = dynamic_cast<BBox*>(child);
343 
344 			if (box && box->LabelView())
345 			{
346 				radio = dynamic_cast<BRadioButton*>(box->LabelView());
347 
348 				if (radio)
349 					radio->SetValue(B_CONTROL_OFF);
350 			}
351 		}
352 
353 		child = child->NextSibling();
354 	}
355 }
356 //------------------------------------------------------------------------------
357 void BRadioButton::GetPreferredSize(float *width, float *height)
358 {
359 	font_height fh;
360 	GetFontHeight(&fh);
361 
362 	*height = (float)ceil(fh.ascent + fh.descent) + 6.0f;
363 	*width = 22.0f; // TODO: check if ascent is included
364 
365 	if (Label())
366 		*width += StringWidth(Label());
367 
368 	*width = (float)ceil(*width);
369 }
370 //------------------------------------------------------------------------------
371 void BRadioButton::ResizeToPreferred()
372 {
373 	BControl::ResizeToPreferred();
374 }
375 //------------------------------------------------------------------------------
376 status_t BRadioButton::Invoke(BMessage *message)
377 {
378 	return BControl::Invoke(message);
379 }
380 //------------------------------------------------------------------------------
381 void BRadioButton::MessageReceived(BMessage *message)
382 {
383 	BControl::MessageReceived(message);
384 }
385 //------------------------------------------------------------------------------
386 void BRadioButton::WindowActivated(bool active)
387 {
388 	BControl::WindowActivated(active);
389 }
390 //------------------------------------------------------------------------------
391 void BRadioButton::MouseUp(BPoint point)
392 {
393 	if (!IsTracking())
394 		return;
395 
396 	fOutlined = Bounds().Contains(point);
397 	if (fOutlined) {
398 		fOutlined = false;
399 		SetValue(B_CONTROL_ON);
400 		Invoke();
401 	}
402 	Invalidate();
403 
404 	SetTracking(false);
405 }
406 //------------------------------------------------------------------------------
407 void BRadioButton::MouseMoved(BPoint point, uint32 transit, const BMessage *message)
408 {
409 	if (!IsTracking())
410 		return;
411 
412 	bool inside = Bounds().Contains(point);
413 
414 	if (fOutlined != inside) {
415 		fOutlined = inside;
416 		Invalidate();
417 	}
418 }
419 //------------------------------------------------------------------------------
420 void BRadioButton::DetachedFromWindow()
421 {
422 	BControl::DetachedFromWindow();
423 }
424 //------------------------------------------------------------------------------
425 void BRadioButton::FrameMoved(BPoint newLocation)
426 {
427 	BControl::FrameMoved(newLocation);
428 }
429 //------------------------------------------------------------------------------
430 void BRadioButton::FrameResized(float width, float height)
431 {
432 	BControl::FrameResized(width, height);
433 }
434 //------------------------------------------------------------------------------
435 BHandler *BRadioButton::ResolveSpecifier(BMessage *message, int32 index,
436 										 BMessage *specifier, int32 what,
437 										 const char *property)
438 {
439 	return BControl::ResolveSpecifier(message, index, specifier, what,
440 		property);
441 }
442 //------------------------------------------------------------------------------
443 void BRadioButton::MakeFocus(bool focused)
444 {
445 	BControl::MakeFocus(focused);
446 }
447 //------------------------------------------------------------------------------
448 void BRadioButton::AllAttached()
449 {
450 	BControl::AllAttached();
451 }
452 //------------------------------------------------------------------------------
453 void BRadioButton::AllDetached()
454 {
455 	BControl::AllDetached();
456 }
457 //------------------------------------------------------------------------------
458 status_t BRadioButton::GetSupportedSuites(BMessage *message)
459 {
460 	return BControl::GetSupportedSuites(message);
461 }
462 //------------------------------------------------------------------------------
463 status_t BRadioButton::Perform(perform_code d, void *arg)
464 {
465 	return BControl::Perform(d, arg);
466 }
467 //------------------------------------------------------------------------------
468 void BRadioButton::_ReservedRadioButton1() {}
469 void BRadioButton::_ReservedRadioButton2() {}
470 //------------------------------------------------------------------------------
471 BRadioButton &BRadioButton::operator=(const BRadioButton &)
472 {
473 	return *this;
474 }
475 //------------------------------------------------------------------------------
476 BRect
477 BRadioButton::_KnobFrame() const
478 {
479 	font_height fh;
480 	GetFontHeight(&fh);
481 
482 	// layout the rect for the dot
483 	BRect rect(Bounds());
484 
485 	// its size depends on the text height
486 	float textHeight = floorf(fh.ascent + fh.descent + 0.5);
487 	float inset = -floorf(textHeight / 2 - 2);
488 
489 	rect.left -= (inset - 1);
490 	rect.top = floorf((rect.top + rect.bottom) / 2.0);
491 	rect.bottom = rect.top;
492 	rect.right = rect.left;
493 	rect.InsetBy(inset, inset);
494 
495 	return rect;
496 }
497 
498 
499 /*
500  * $Log $
501  *
502  * $Id  $
503  *
504  */
505