1/*
2 * Copyright 2001-2015 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// BCheckBox displays an on/off control.
12
13
14#include <CheckBox.h>
15
16#include <algorithm>
17#include <new>
18
19#include <Bitmap.h>
20#include <ControlLook.h>
21#include <LayoutUtils.h>
22#include <Window.h>
23
24#include <binary_compatibility/Interface.h>
25
26
27BCheckBox::BCheckBox(BRect frame, const char* name, const char* label,
28	BMessage* message, uint32 resizingMode, uint32 flags)
29	:
30	BControl(frame, name, label, message, resizingMode, flags),
31	fPreferredSize(),
32	fOutlined(false),
33	fPartialToOff(false)
34{
35	// Resize to minimum height if needed
36	font_height fontHeight;
37	GetFontHeight(&fontHeight);
38	float minHeight = (float)ceil(6.0f + fontHeight.ascent
39		+ fontHeight.descent);
40	if (Bounds().Height() < minHeight)
41		ResizeTo(Bounds().Width(), minHeight);
42}
43
44
45BCheckBox::BCheckBox(const char* name, const char* label, BMessage* message,
46	uint32 flags)
47	:
48	BControl(name, label, message, flags | B_WILL_DRAW | B_NAVIGABLE),
49	fPreferredSize(),
50	fOutlined(false),
51	fPartialToOff(false)
52{
53}
54
55
56BCheckBox::BCheckBox(const char* label, BMessage* message)
57	:
58	BControl(NULL, label, message, B_WILL_DRAW | B_NAVIGABLE),
59	fPreferredSize(),
60	fOutlined(false),
61	fPartialToOff(false)
62{
63}
64
65
66BCheckBox::BCheckBox(BMessage* data)
67	:
68	BControl(data),
69	fOutlined(false),
70	fPartialToOff(false)
71{
72}
73
74
75BCheckBox::~BCheckBox()
76{
77}
78
79
80// #pragma mark - Archiving methods
81
82
83BArchivable*
84BCheckBox::Instantiate(BMessage* data)
85{
86	if (validate_instantiation(data, "BCheckBox"))
87		return new(std::nothrow) BCheckBox(data);
88
89	return NULL;
90}
91
92
93status_t
94BCheckBox::Archive(BMessage* data, bool deep) const
95{
96	return BControl::Archive(data, deep);
97}
98
99
100// #pragma mark - Hook methods
101
102
103void
104BCheckBox::Draw(BRect updateRect)
105{
106	rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
107
108	uint32 flags = be_control_look->Flags(this);
109	if (fOutlined)
110		flags |= BControlLook::B_CLICKED;
111
112	BRect checkBoxRect(_CheckBoxFrame());
113	BRect rect(checkBoxRect);
114	be_control_look->DrawCheckBox(this, rect, updateRect, base, flags);
115
116	// erase the is control flag before drawing the label so that the label
117	// will get drawn using B_PANEL_TEXT_COLOR
118	flags &= ~BControlLook::B_IS_CONTROL;
119
120	BRect labelRect(Bounds());
121	labelRect.left = checkBoxRect.right + 1
122		+ be_control_look->DefaultLabelSpacing();
123
124	const BBitmap* icon = IconBitmap(
125		B_INACTIVE_ICON_BITMAP | (IsEnabled() ? 0 : B_DISABLED_ICON_BITMAP));
126
127	be_control_look->DrawLabel(this, Label(), icon, labelRect, updateRect,
128		base, flags);
129}
130
131
132void
133BCheckBox::AttachedToWindow()
134{
135	BControl::AttachedToWindow();
136}
137
138
139void
140BCheckBox::DetachedFromWindow()
141{
142	BControl::DetachedFromWindow();
143}
144
145
146void
147BCheckBox::AllAttached()
148{
149	BControl::AllAttached();
150}
151
152
153void
154BCheckBox::AllDetached()
155{
156	BControl::AllDetached();
157}
158
159
160void
161BCheckBox::FrameMoved(BPoint newPosition)
162{
163	BControl::FrameMoved(newPosition);
164}
165
166
167void
168BCheckBox::FrameResized(float newWidth, float newHeight)
169{
170	BControl::FrameResized(newWidth, newHeight);
171}
172
173
174void
175BCheckBox::WindowActivated(bool active)
176{
177	BControl::WindowActivated(active);
178}
179
180
181void
182BCheckBox::MessageReceived(BMessage* message)
183{
184	BControl::MessageReceived(message);
185}
186
187
188void
189BCheckBox::KeyDown(const char* bytes, int32 numBytes)
190{
191	if (*bytes == B_ENTER || *bytes == B_SPACE) {
192		if (!IsEnabled())
193			return;
194
195		SetValue(_NextState());
196		Invoke();
197	} else {
198		// skip the BControl implementation
199		BView::KeyDown(bytes, numBytes);
200	}
201}
202
203
204void
205BCheckBox::MouseDown(BPoint where)
206{
207	if (!IsEnabled())
208		return;
209
210	fOutlined = true;
211
212	if (Window()->Flags() & B_ASYNCHRONOUS_CONTROLS) {
213		Invalidate();
214		SetTracking(true);
215		SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS);
216	} else {
217		BRect bounds = Bounds();
218		uint32 buttons;
219
220		Invalidate();
221		Window()->UpdateIfNeeded();
222
223		do {
224			snooze(40000);
225
226			GetMouse(&where, &buttons, true);
227
228			bool inside = bounds.Contains(where);
229			if (fOutlined != inside) {
230				fOutlined = inside;
231				Invalidate();
232				Window()->UpdateIfNeeded();
233			}
234		} while (buttons != 0);
235
236		if (fOutlined) {
237			fOutlined = false;
238			SetValue(_NextState());
239			Invoke();
240		} else {
241			Invalidate();
242			Window()->UpdateIfNeeded();
243		}
244	}
245}
246
247
248void
249BCheckBox::MouseUp(BPoint where)
250{
251	if (!IsTracking())
252		return;
253
254	bool inside = Bounds().Contains(where);
255
256	if (fOutlined != inside) {
257		fOutlined = inside;
258		Invalidate();
259	}
260
261	if (fOutlined) {
262		fOutlined = false;
263		SetValue(_NextState());
264		Invoke();
265	} else {
266		Invalidate();
267	}
268
269	SetTracking(false);
270}
271
272
273void
274BCheckBox::MouseMoved(BPoint where, uint32 code,
275	const BMessage* dragMessage)
276{
277	if (!IsTracking())
278		return;
279
280	bool inside = Bounds().Contains(where);
281
282	if (fOutlined != inside) {
283		fOutlined = inside;
284		Invalidate();
285	}
286}
287
288
289// #pragma mark -
290
291
292void
293BCheckBox::GetPreferredSize(float* _width, float* _height)
294{
295	_ValidatePreferredSize();
296
297	if (_width)
298		*_width = fPreferredSize.width;
299
300	if (_height)
301		*_height = fPreferredSize.height;
302}
303
304
305void
306BCheckBox::ResizeToPreferred()
307{
308	BControl::ResizeToPreferred();
309}
310
311
312BSize
313BCheckBox::MinSize()
314{
315	return BLayoutUtils::ComposeSize(ExplicitMinSize(),
316		_ValidatePreferredSize());
317}
318
319
320BSize
321BCheckBox::MaxSize()
322{
323	return BLayoutUtils::ComposeSize(ExplicitMaxSize(),
324		_ValidatePreferredSize());
325}
326
327
328BSize
329BCheckBox::PreferredSize()
330{
331	return BLayoutUtils::ComposeSize(ExplicitPreferredSize(),
332		_ValidatePreferredSize());
333}
334
335
336BAlignment
337BCheckBox::LayoutAlignment()
338{
339	return BLayoutUtils::ComposeAlignment(ExplicitAlignment(),
340		BAlignment(B_ALIGN_LEFT, B_ALIGN_VERTICAL_CENTER));
341}
342
343
344// #pragma mark -
345
346
347void
348BCheckBox::MakeFocus(bool focused)
349{
350	BControl::MakeFocus(focused);
351}
352
353
354void
355BCheckBox::SetValue(int32 value)
356{
357	// We only accept three possible values.
358	switch (value) {
359		case B_CONTROL_OFF:
360		case B_CONTROL_ON:
361		case B_CONTROL_PARTIALLY_ON:
362			break;
363		default:
364			value = B_CONTROL_ON;
365			break;
366	}
367
368	if (value != Value()) {
369		BControl::SetValueNoUpdate(value);
370		Invalidate(_CheckBoxFrame());
371	}
372}
373
374
375status_t
376BCheckBox::Invoke(BMessage* message)
377{
378	return BControl::Invoke(message);
379}
380
381
382BHandler*
383BCheckBox::ResolveSpecifier(BMessage* message, int32 index,
384	BMessage* specifier, int32 what, const char* property)
385{
386	return BControl::ResolveSpecifier(message, index, specifier, what,
387		property);
388}
389
390
391status_t
392BCheckBox::GetSupportedSuites(BMessage* message)
393{
394	return BControl::GetSupportedSuites(message);
395}
396
397
398status_t
399BCheckBox::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				= BCheckBox::MinSize();
405			return B_OK;
406		case PERFORM_CODE_MAX_SIZE:
407			((perform_data_max_size*)_data)->return_value
408				= BCheckBox::MaxSize();
409			return B_OK;
410		case PERFORM_CODE_PREFERRED_SIZE:
411			((perform_data_preferred_size*)_data)->return_value
412				= BCheckBox::PreferredSize();
413			return B_OK;
414		case PERFORM_CODE_LAYOUT_ALIGNMENT:
415			((perform_data_layout_alignment*)_data)->return_value
416				= BCheckBox::LayoutAlignment();
417			return B_OK;
418		case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH:
419			((perform_data_has_height_for_width*)_data)->return_value
420				= BCheckBox::HasHeightForWidth();
421			return B_OK;
422		case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH:
423		{
424			perform_data_get_height_for_width* data
425				= (perform_data_get_height_for_width*)_data;
426			BCheckBox::GetHeightForWidth(data->width, &data->min, &data->max,
427				&data->preferred);
428			return B_OK;
429		}
430		case PERFORM_CODE_SET_LAYOUT:
431		{
432			perform_data_set_layout* data = (perform_data_set_layout*)_data;
433			BCheckBox::SetLayout(data->layout);
434			return B_OK;
435		}
436		case PERFORM_CODE_LAYOUT_INVALIDATED:
437		{
438			perform_data_layout_invalidated* data
439				= (perform_data_layout_invalidated*)_data;
440			BCheckBox::LayoutInvalidated(data->descendants);
441			return B_OK;
442		}
443		case PERFORM_CODE_DO_LAYOUT:
444		{
445			BCheckBox::DoLayout();
446			return B_OK;
447		}
448		case PERFORM_CODE_SET_ICON:
449		{
450			perform_data_set_icon* data = (perform_data_set_icon*)_data;
451			return BCheckBox::SetIcon(data->icon, data->flags);
452		}
453	}
454
455	return BControl::Perform(code, _data);
456}
457
458
459status_t
460BCheckBox::SetIcon(const BBitmap* icon, uint32 flags)
461{
462	return BControl::SetIcon(icon, flags | B_CREATE_DISABLED_ICON_BITMAPS);
463}
464
465
466void
467BCheckBox::LayoutInvalidated(bool descendants)
468{
469	// invalidate cached preferred size
470	fPreferredSize.Set(B_SIZE_UNSET, B_SIZE_UNSET);
471}
472
473
474bool
475BCheckBox::IsPartialStateToOff() const
476{
477	return fPartialToOff;
478}
479
480
481void
482BCheckBox::SetPartialStateToOff(bool partialToOff)
483{
484	fPartialToOff = partialToOff;
485}
486
487
488// #pragma mark - FBC padding
489
490
491void BCheckBox::_ReservedCheckBox1() {}
492void BCheckBox::_ReservedCheckBox2() {}
493void BCheckBox::_ReservedCheckBox3() {}
494
495
496BRect
497BCheckBox::_CheckBoxFrame(const font_height& fontHeight) const
498{
499	return BRect(0.0f, 2.0f, ceilf(3.0f + fontHeight.ascent),
500		ceilf(5.0f + fontHeight.ascent));
501}
502
503
504BRect
505BCheckBox::_CheckBoxFrame() const
506{
507	font_height fontHeight;
508	GetFontHeight(&fontHeight);
509	return _CheckBoxFrame(fontHeight);
510}
511
512
513BSize
514BCheckBox::_ValidatePreferredSize()
515{
516	if (!fPreferredSize.IsWidthSet()) {
517		font_height fontHeight;
518		GetFontHeight(&fontHeight);
519
520		BRect rect(_CheckBoxFrame(fontHeight));
521		float width = rect.right + rect.left;
522		float height = rect.bottom + rect.top;
523
524		const BBitmap* icon = IconBitmap(B_INACTIVE_ICON_BITMAP);
525		if (icon != NULL) {
526			width += be_control_look->DefaultLabelSpacing()
527				+ icon->Bounds().Width() + 1;
528			height = std::max(height, icon->Bounds().Height());
529		}
530
531		if (const char* label = Label()) {
532			width += be_control_look->DefaultLabelSpacing()
533				+ ceilf(StringWidth(label));
534			height = std::max(height,
535				ceilf(6.0f + fontHeight.ascent + fontHeight.descent));
536		}
537
538		fPreferredSize.Set(width, height);
539
540		ResetLayoutInvalidation();
541	}
542
543	return fPreferredSize;
544}
545
546
547int32
548BCheckBox::_NextState() const
549{
550	switch (Value()) {
551		case B_CONTROL_OFF:
552			return B_CONTROL_ON;
553		case B_CONTROL_PARTIALLY_ON:
554			return fPartialToOff ? B_CONTROL_OFF : B_CONTROL_ON;
555		case B_CONTROL_ON:
556		default:
557			return B_CONTROL_OFF;
558	}
559}
560
561
562BCheckBox &
563BCheckBox::operator=(const BCheckBox &)
564{
565	return *this;
566}
567
568
569extern "C" void
570B_IF_GCC_2(InvalidateLayout__9BCheckBoxb, _ZN9BCheckBox16InvalidateLayoutEb)(
571	BCheckBox* box, bool descendants)
572{
573	perform_data_layout_invalidated data;
574	data.descendants = descendants;
575
576	box->Perform(PERFORM_CODE_LAYOUT_INVALIDATED, &data);
577}
578
579