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 *		Mike Wilber
8 *		Stefano Ceccherini (burton666@libero.it)
9 *		Ivan Tonizza
10 *		Stephan Aßmus <superstippi@gmx.de>
11 *		Ingo Weinhold, ingo_weinhold@gmx.de
12 */
13
14
15#include <Button.h>
16
17#include <algorithm>
18#include <new>
19
20#include <Bitmap.h>
21#include <ControlLook.h>
22#include <Font.h>
23#include <LayoutUtils.h>
24#include <String.h>
25#include <Window.h>
26
27#include <binary_compatibility/Interface.h>
28
29
30enum {
31	FLAG_DEFAULT 		= 0x01,
32	FLAG_FLAT			= 0x02,
33	FLAG_INSIDE			= 0x04,
34	FLAG_WAS_PRESSED	= 0x08,
35};
36
37
38static const float kLabelMargin = 3;
39
40
41BButton::BButton(BRect frame, const char* name, const char* label,
42	BMessage* message, uint32 resizingMode, uint32 flags)
43	:
44	BControl(frame, name, label, message, resizingMode,
45		flags | B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE),
46	fPreferredSize(-1, -1),
47	fFlags(0),
48	fBehavior(B_BUTTON_BEHAVIOR),
49	fPopUpMessage(NULL)
50{
51	// Resize to minimum height if needed
52	font_height fh;
53	GetFontHeight(&fh);
54	float minHeight = 12.0f + (float)ceil(fh.ascent + fh.descent);
55	if (Bounds().Height() < minHeight)
56		ResizeTo(Bounds().Width(), minHeight);
57}
58
59
60BButton::BButton(const char* name, const char* label, BMessage* message,
61	uint32 flags)
62	:
63	BControl(name, label, message,
64		flags | B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE),
65	fPreferredSize(-1, -1),
66	fFlags(0),
67	fBehavior(B_BUTTON_BEHAVIOR),
68	fPopUpMessage(NULL)
69{
70}
71
72
73BButton::BButton(const char* label, BMessage* message)
74	:
75	BControl(NULL, label, message,
76		B_WILL_DRAW | B_NAVIGABLE | B_FULL_UPDATE_ON_RESIZE),
77	fPreferredSize(-1, -1),
78	fFlags(0),
79	fBehavior(B_BUTTON_BEHAVIOR),
80	fPopUpMessage(NULL)
81{
82}
83
84
85BButton::~BButton()
86{
87	SetPopUpMessage(NULL);
88}
89
90
91BButton::BButton(BMessage* data)
92	:
93	BControl(data),
94	fPreferredSize(-1, -1),
95	fFlags(0),
96	fBehavior(B_BUTTON_BEHAVIOR),
97	fPopUpMessage(NULL)
98{
99	bool isDefault = false;
100	if (data->FindBool("_default", &isDefault) == B_OK && isDefault)
101		_SetFlag(FLAG_DEFAULT, true);
102	// NOTE: Default button state will be synchronized with the window
103	// in AttachedToWindow().
104}
105
106
107BArchivable*
108BButton::Instantiate(BMessage* data)
109{
110	if (validate_instantiation(data, "BButton"))
111		return new(std::nothrow) BButton(data);
112
113	return NULL;
114}
115
116
117status_t
118BButton::Archive(BMessage* data, bool deep) const
119{
120	status_t err = BControl::Archive(data, deep);
121
122	if (err != B_OK)
123		return err;
124
125	if (IsDefault())
126		err = data->AddBool("_default", true);
127
128	return err;
129}
130
131
132void
133BButton::Draw(BRect updateRect)
134{
135	BRect rect(Bounds());
136	rgb_color background = ViewColor();
137	rgb_color base = LowColor();
138	rgb_color textColor = ui_color(B_CONTROL_TEXT_COLOR);
139
140	uint32 flags = be_control_look->Flags(this);
141	if (_Flag(FLAG_DEFAULT))
142		flags |= BControlLook::B_DEFAULT_BUTTON;
143	if (_Flag(FLAG_FLAT) && !IsTracking())
144		flags |= BControlLook::B_FLAT;
145	if (_Flag(FLAG_INSIDE))
146		flags |= BControlLook::B_HOVER;
147
148	be_control_look->DrawButtonFrame(this, rect, updateRect,
149		base, background, flags);
150
151	if (fBehavior == B_POP_UP_BEHAVIOR) {
152		be_control_look->DrawButtonWithPopUpBackground(this, rect, updateRect,
153			base, flags);
154	} else {
155		be_control_look->DrawButtonBackground(this, rect, updateRect,
156			base, flags);
157	}
158
159	// always leave some room around the label
160	rect.InsetBy(kLabelMargin, kLabelMargin);
161
162	const BBitmap* icon = IconBitmap(
163		(Value() == B_CONTROL_OFF
164				? B_INACTIVE_ICON_BITMAP : B_ACTIVE_ICON_BITMAP)
165			| (IsEnabled() ? 0 : B_DISABLED_ICON_BITMAP));
166
167	be_control_look->DrawLabel(this, Label(), icon, rect, updateRect, base,
168		flags, BAlignment(B_ALIGN_CENTER, B_ALIGN_MIDDLE), &textColor);
169}
170
171
172void
173BButton::MouseDown(BPoint where)
174{
175	if (!IsEnabled())
176		return;
177
178	if (fBehavior == B_POP_UP_BEHAVIOR && _PopUpRect().Contains(where)) {
179		InvokeNotify(fPopUpMessage, B_CONTROL_MODIFIED);
180		return;
181	}
182
183	bool toggleBehavior = fBehavior == B_TOGGLE_BEHAVIOR;
184
185	if (toggleBehavior) {
186		bool wasPressed = Value() == B_CONTROL_ON;
187		_SetFlag(FLAG_WAS_PRESSED, wasPressed);
188		SetValue(wasPressed ? B_CONTROL_OFF : B_CONTROL_ON);
189		Invalidate();
190	} else
191		SetValue(B_CONTROL_ON);
192
193	if (Window()->Flags() & B_ASYNCHRONOUS_CONTROLS) {
194		SetTracking(true);
195		SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS);
196	} else {
197		BRect bounds = Bounds();
198		uint32 buttons;
199		bool inside = false;
200
201		do {
202			Window()->UpdateIfNeeded();
203			snooze(40000);
204
205			GetMouse(&where, &buttons, true);
206			inside = bounds.Contains(where);
207
208			if (toggleBehavior) {
209				bool pressed = inside ^ _Flag(FLAG_WAS_PRESSED);
210				SetValue(pressed ? B_CONTROL_ON : B_CONTROL_OFF);
211			} else {
212				if ((Value() == B_CONTROL_ON) != inside)
213					SetValue(inside ? B_CONTROL_ON : B_CONTROL_OFF);
214			}
215		} while (buttons != 0);
216
217		if (inside) {
218			if (toggleBehavior) {
219				SetValue(
220					_Flag(FLAG_WAS_PRESSED) ? B_CONTROL_OFF : B_CONTROL_ON);
221			}
222
223			Invoke();
224		} else if (_Flag(FLAG_FLAT))
225			Invalidate();
226	}
227}
228
229void
230BButton::AttachedToWindow()
231{
232	BControl::AttachedToWindow();
233
234	// Tint default control background color to match default panel background.
235	SetLowUIColor(B_CONTROL_BACKGROUND_COLOR, 1.115);
236	SetHighUIColor(B_CONTROL_TEXT_COLOR);
237
238	if (IsDefault())
239		Window()->SetDefaultButton(this);
240}
241
242
243void
244BButton::KeyDown(const char* bytes, int32 numBytes)
245{
246	if (*bytes == B_ENTER || *bytes == B_SPACE) {
247		if (!IsEnabled())
248			return;
249
250		SetValue(B_CONTROL_ON);
251
252		// make sure the user saw that
253		Window()->UpdateIfNeeded();
254		snooze(25000);
255
256		Invoke();
257	} else
258		BControl::KeyDown(bytes, numBytes);
259}
260
261
262void
263BButton::MakeDefault(bool flag)
264{
265	BButton* oldDefault = NULL;
266	BWindow* window = Window();
267
268	if (window != NULL)
269		oldDefault = window->DefaultButton();
270
271	if (flag) {
272		if (_Flag(FLAG_DEFAULT) && oldDefault == this)
273			return;
274
275		if (_SetFlag(FLAG_DEFAULT, true)) {
276			if ((Flags() & B_SUPPORTS_LAYOUT) != 0)
277				InvalidateLayout();
278			else {
279				ResizeBy(6.0f, 6.0f);
280				MoveBy(-3.0f, -3.0f);
281			}
282		}
283
284		if (window && oldDefault != this)
285			window->SetDefaultButton(this);
286	} else {
287		if (!_SetFlag(FLAG_DEFAULT, false))
288			return;
289
290		if ((Flags() & B_SUPPORTS_LAYOUT) != 0)
291			InvalidateLayout();
292		else {
293			ResizeBy(-6.0f, -6.0f);
294			MoveBy(3.0f, 3.0f);
295		}
296
297		if (window && oldDefault == this)
298			window->SetDefaultButton(NULL);
299	}
300}
301
302
303void
304BButton::SetLabel(const char* label)
305{
306	BControl::SetLabel(label);
307}
308
309
310bool
311BButton::IsDefault() const
312{
313	return _Flag(FLAG_DEFAULT);
314}
315
316
317bool
318BButton::IsFlat() const
319{
320	return _Flag(FLAG_FLAT);
321}
322
323
324void
325BButton::SetFlat(bool flat)
326{
327	if (_SetFlag(FLAG_FLAT, flat))
328		Invalidate();
329}
330
331
332BButton::BBehavior
333BButton::Behavior() const
334{
335	return fBehavior;
336}
337
338
339void
340BButton::SetBehavior(BBehavior behavior)
341{
342	if (behavior != fBehavior) {
343		fBehavior = behavior;
344		InvalidateLayout();
345		Invalidate();
346	}
347}
348
349
350BMessage*
351BButton::PopUpMessage() const
352{
353	return fPopUpMessage;
354}
355
356
357void
358BButton::SetPopUpMessage(BMessage* message)
359{
360	delete fPopUpMessage;
361	fPopUpMessage = message;
362}
363
364
365void
366BButton::MessageReceived(BMessage* message)
367{
368	BControl::MessageReceived(message);
369}
370
371
372void
373BButton::WindowActivated(bool active)
374{
375	BControl::WindowActivated(active);
376}
377
378
379void
380BButton::MouseMoved(BPoint where, uint32 code, const BMessage* dragMessage)
381{
382	bool inside = (code != B_EXITED_VIEW) && Bounds().Contains(where);
383	if (_SetFlag(FLAG_INSIDE, inside))
384		Invalidate();
385
386	if (!IsTracking())
387		return;
388
389	if (fBehavior == B_TOGGLE_BEHAVIOR) {
390		bool pressed = inside ^ _Flag(FLAG_WAS_PRESSED);
391		SetValue(pressed ? B_CONTROL_ON : B_CONTROL_OFF);
392	} else {
393		if ((Value() == B_CONTROL_ON) != inside)
394			SetValue(inside ? B_CONTROL_ON : B_CONTROL_OFF);
395	}
396}
397
398
399void
400BButton::MouseUp(BPoint where)
401{
402	if (!IsTracking())
403		return;
404
405	if (Bounds().Contains(where)) {
406		if (fBehavior == B_TOGGLE_BEHAVIOR)
407			SetValue(_Flag(FLAG_WAS_PRESSED) ? B_CONTROL_OFF : B_CONTROL_ON);
408
409		Invoke();
410	} else if (_Flag(FLAG_FLAT))
411		Invalidate();
412
413	SetTracking(false);
414}
415
416
417void
418BButton::DetachedFromWindow()
419{
420	BControl::DetachedFromWindow();
421}
422
423
424void
425BButton::SetValue(int32 value)
426{
427	if (value != Value())
428		BControl::SetValue(value);
429}
430
431
432void
433BButton::GetPreferredSize(float* _width, float* _height)
434{
435	_ValidatePreferredSize();
436
437	if (_width)
438		*_width = fPreferredSize.width;
439
440	if (_height)
441		*_height = fPreferredSize.height;
442}
443
444
445void
446BButton::ResizeToPreferred()
447{
448	BControl::ResizeToPreferred();
449}
450
451
452status_t
453BButton::Invoke(BMessage* message)
454{
455	Sync();
456	snooze(50000);
457
458	status_t err = BControl::Invoke(message);
459
460	if (fBehavior != B_TOGGLE_BEHAVIOR)
461		SetValue(B_CONTROL_OFF);
462
463	return err;
464}
465
466
467void
468BButton::FrameMoved(BPoint newPosition)
469{
470	BControl::FrameMoved(newPosition);
471}
472
473
474void
475BButton::FrameResized(float newWidth, float newHeight)
476{
477	BControl::FrameResized(newWidth, newHeight);
478}
479
480
481void
482BButton::MakeFocus(bool focus)
483{
484	BControl::MakeFocus(focus);
485}
486
487
488void
489BButton::AllAttached()
490{
491	BControl::AllAttached();
492}
493
494
495void
496BButton::AllDetached()
497{
498	BControl::AllDetached();
499}
500
501
502BHandler*
503BButton::ResolveSpecifier(BMessage* message, int32 index,
504	BMessage* specifier, int32 what, const char* property)
505{
506	return BControl::ResolveSpecifier(message, index, specifier, what,
507		property);
508}
509
510
511status_t
512BButton::GetSupportedSuites(BMessage* message)
513{
514	return BControl::GetSupportedSuites(message);
515}
516
517
518status_t
519BButton::Perform(perform_code code, void* _data)
520{
521	switch (code) {
522		case PERFORM_CODE_MIN_SIZE:
523			((perform_data_min_size*)_data)->return_value
524				= BButton::MinSize();
525			return B_OK;
526
527		case PERFORM_CODE_MAX_SIZE:
528			((perform_data_max_size*)_data)->return_value
529				= BButton::MaxSize();
530			return B_OK;
531
532		case PERFORM_CODE_PREFERRED_SIZE:
533			((perform_data_preferred_size*)_data)->return_value
534				= BButton::PreferredSize();
535			return B_OK;
536
537		case PERFORM_CODE_LAYOUT_ALIGNMENT:
538			((perform_data_layout_alignment*)_data)->return_value
539				= BButton::LayoutAlignment();
540			return B_OK;
541
542		case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH:
543			((perform_data_has_height_for_width*)_data)->return_value
544				= BButton::HasHeightForWidth();
545			return B_OK;
546
547		case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH:
548		{
549			perform_data_get_height_for_width* data
550				= (perform_data_get_height_for_width*)_data;
551			BButton::GetHeightForWidth(data->width, &data->min, &data->max,
552				&data->preferred);
553			return B_OK;
554		}
555
556		case PERFORM_CODE_SET_LAYOUT:
557		{
558			perform_data_set_layout* data = (perform_data_set_layout*)_data;
559			BButton::SetLayout(data->layout);
560			return B_OK;
561		}
562
563		case PERFORM_CODE_LAYOUT_INVALIDATED:
564		{
565			perform_data_layout_invalidated* data
566				= (perform_data_layout_invalidated*)_data;
567			BButton::LayoutInvalidated(data->descendants);
568			return B_OK;
569		}
570
571		case PERFORM_CODE_DO_LAYOUT:
572		{
573			BButton::DoLayout();
574			return B_OK;
575		}
576
577		case PERFORM_CODE_SET_ICON:
578		{
579			perform_data_set_icon* data = (perform_data_set_icon*)_data;
580			return BButton::SetIcon(data->icon, data->flags);
581		}
582	}
583
584	return BControl::Perform(code, _data);
585}
586
587
588BSize
589BButton::MinSize()
590{
591	return BLayoutUtils::ComposeSize(ExplicitMinSize(),
592		_ValidatePreferredSize());
593}
594
595
596BSize
597BButton::MaxSize()
598{
599	return BLayoutUtils::ComposeSize(ExplicitMaxSize(),
600		_ValidatePreferredSize());
601}
602
603
604BSize
605BButton::PreferredSize()
606{
607	return BLayoutUtils::ComposeSize(ExplicitPreferredSize(),
608		_ValidatePreferredSize());
609}
610
611
612status_t
613BButton::SetIcon(const BBitmap* icon, uint32 flags)
614{
615	return BControl::SetIcon(icon,
616		flags | B_CREATE_ACTIVE_ICON_BITMAP | B_CREATE_DISABLED_ICON_BITMAPS);
617}
618
619
620void
621BButton::LayoutInvalidated(bool descendants)
622{
623	// invalidate cached preferred size
624	fPreferredSize.Set(-1, -1);
625}
626
627
628void BButton::_ReservedButton1() {}
629void BButton::_ReservedButton2() {}
630void BButton::_ReservedButton3() {}
631
632
633BButton &
634BButton::operator=(const BButton &)
635{
636	return *this;
637}
638
639
640BSize
641BButton::_ValidatePreferredSize()
642{
643	if (fPreferredSize.width < 0) {
644		BControlLook::background_type backgroundType
645			= fBehavior == B_POP_UP_BEHAVIOR
646				? BControlLook::B_BUTTON_WITH_POP_UP_BACKGROUND
647				: BControlLook::B_BUTTON_BACKGROUND;
648		float left, top, right, bottom;
649		be_control_look->GetInsets(BControlLook::B_BUTTON_FRAME, backgroundType,
650			IsDefault() ? BControlLook::B_DEFAULT_BUTTON : 0,
651			left, top, right, bottom);
652
653		// width
654		float width = left + right + 2 * kLabelMargin - 1;
655
656		const char* label = Label();
657		if (label != NULL) {
658			width = std::max(width, 20.0f);
659			width += (float)ceil(StringWidth(label));
660		}
661
662		const BBitmap* icon = IconBitmap(B_INACTIVE_ICON_BITMAP);
663		if (icon != NULL)
664			width += icon->Bounds().Width() + 1;
665
666		if (label != NULL && icon != NULL)
667			width += be_control_look->DefaultLabelSpacing();
668
669		// height
670		float minHorizontalMargins = top + bottom + 2 * kLabelMargin;
671		float height = -1;
672
673		if (label != NULL) {
674			font_height fontHeight;
675			GetFontHeight(&fontHeight);
676			float textHeight = fontHeight.ascent + fontHeight.descent;
677			height = ceilf(textHeight * 1.8);
678			float margins = height - ceilf(textHeight);
679			if (margins < minHorizontalMargins)
680				height += minHorizontalMargins - margins;
681		}
682
683		if (icon != NULL) {
684			height = std::max(height,
685				icon->Bounds().Height() + minHorizontalMargins);
686		}
687
688		// force some minimum width/height values
689		width = std::max(width, label != NULL ? 75.0f : 5.0f);
690		height = std::max(height, 5.0f);
691
692		fPreferredSize.Set(width, height);
693
694		ResetLayoutInvalidation();
695	}
696
697	return fPreferredSize;
698}
699
700
701BRect
702BButton::_PopUpRect() const
703{
704	if (fBehavior != B_POP_UP_BEHAVIOR)
705		return BRect();
706
707	float left, top, right, bottom;
708	be_control_look->GetInsets(BControlLook::B_BUTTON_FRAME,
709		BControlLook::B_BUTTON_WITH_POP_UP_BACKGROUND,
710		IsDefault() ? BControlLook::B_DEFAULT_BUTTON : 0,
711		left, top, right, bottom);
712
713	BRect rect(Bounds());
714	rect.left = rect.right - right + 1;
715	return rect;
716}
717
718
719inline bool
720BButton::_Flag(uint32 flag) const
721{
722	return (fFlags & flag) != 0;
723}
724
725
726inline bool
727BButton::_SetFlag(uint32 flag, bool set)
728{
729	if (((fFlags & flag) != 0) == set)
730		return false;
731
732	if (set)
733		fFlags |= flag;
734	else
735		fFlags &= ~flag;
736
737	return true;
738}
739
740
741extern "C" void
742B_IF_GCC_2(InvalidateLayout__7BButtonb, _ZN7BButton16InvalidateLayoutEb)(
743	BView* view, bool descendants)
744{
745	perform_data_layout_invalidated data;
746	data.descendants = descendants;
747
748	view->Perform(PERFORM_CODE_LAYOUT_INVALIDATED, &data);
749}
750