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