xref: /haiku/src/kits/interface/Button.cpp (revision 65f5e1e1b286b19ff6f6f4a38f402b541509faf5)
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 
38 static const float kLabelMargin = 3;
39 
40 
41 BButton::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 
60 BButton::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 
73 BButton::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 
85 BButton::~BButton()
86 {
87 	SetPopUpMessage(NULL);
88 }
89 
90 
91 BButton::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 
107 BArchivable*
108 BButton::Instantiate(BMessage* data)
109 {
110 	if (validate_instantiation(data, "BButton"))
111 		return new(std::nothrow) BButton(data);
112 
113 	return NULL;
114 }
115 
116 
117 status_t
118 BButton::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 
132 void
133 BButton::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 
172 void
173 BButton::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 
229 void
230 BButton::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 
243 void
244 BButton::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 
262 void
263 BButton::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 
303 void
304 BButton::SetLabel(const char* label)
305 {
306 	BControl::SetLabel(label);
307 }
308 
309 
310 bool
311 BButton::IsDefault() const
312 {
313 	return _Flag(FLAG_DEFAULT);
314 }
315 
316 
317 bool
318 BButton::IsFlat() const
319 {
320 	return _Flag(FLAG_FLAT);
321 }
322 
323 
324 void
325 BButton::SetFlat(bool flat)
326 {
327 	if (_SetFlag(FLAG_FLAT, flat))
328 		Invalidate();
329 }
330 
331 
332 BButton::BBehavior
333 BButton::Behavior() const
334 {
335 	return fBehavior;
336 }
337 
338 
339 void
340 BButton::SetBehavior(BBehavior behavior)
341 {
342 	if (behavior != fBehavior) {
343 		fBehavior = behavior;
344 		InvalidateLayout();
345 		Invalidate();
346 	}
347 }
348 
349 
350 BMessage*
351 BButton::PopUpMessage() const
352 {
353 	return fPopUpMessage;
354 }
355 
356 
357 void
358 BButton::SetPopUpMessage(BMessage* message)
359 {
360 	delete fPopUpMessage;
361 	fPopUpMessage = message;
362 }
363 
364 
365 void
366 BButton::MessageReceived(BMessage* message)
367 {
368 	BControl::MessageReceived(message);
369 }
370 
371 
372 void
373 BButton::WindowActivated(bool active)
374 {
375 	BControl::WindowActivated(active);
376 }
377 
378 
379 void
380 BButton::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 
399 void
400 BButton::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 
417 void
418 BButton::DetachedFromWindow()
419 {
420 	BControl::DetachedFromWindow();
421 }
422 
423 
424 void
425 BButton::SetValue(int32 value)
426 {
427 	if (value != Value())
428 		BControl::SetValue(value);
429 }
430 
431 
432 void
433 BButton::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 
445 void
446 BButton::ResizeToPreferred()
447 {
448 	BControl::ResizeToPreferred();
449 }
450 
451 
452 status_t
453 BButton::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 
467 void
468 BButton::FrameMoved(BPoint newPosition)
469 {
470 	BControl::FrameMoved(newPosition);
471 }
472 
473 
474 void
475 BButton::FrameResized(float newWidth, float newHeight)
476 {
477 	BControl::FrameResized(newWidth, newHeight);
478 }
479 
480 
481 void
482 BButton::MakeFocus(bool focus)
483 {
484 	BControl::MakeFocus(focus);
485 }
486 
487 
488 void
489 BButton::AllAttached()
490 {
491 	BControl::AllAttached();
492 }
493 
494 
495 void
496 BButton::AllDetached()
497 {
498 	BControl::AllDetached();
499 }
500 
501 
502 BHandler*
503 BButton::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 
511 status_t
512 BButton::GetSupportedSuites(BMessage* message)
513 {
514 	return BControl::GetSupportedSuites(message);
515 }
516 
517 
518 status_t
519 BButton::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 
588 BSize
589 BButton::MinSize()
590 {
591 	return BLayoutUtils::ComposeSize(ExplicitMinSize(),
592 		_ValidatePreferredSize());
593 }
594 
595 
596 BSize
597 BButton::MaxSize()
598 {
599 	return BLayoutUtils::ComposeSize(ExplicitMaxSize(),
600 		_ValidatePreferredSize());
601 }
602 
603 
604 BSize
605 BButton::PreferredSize()
606 {
607 	return BLayoutUtils::ComposeSize(ExplicitPreferredSize(),
608 		_ValidatePreferredSize());
609 }
610 
611 
612 status_t
613 BButton::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 
620 void
621 BButton::LayoutInvalidated(bool descendants)
622 {
623 	// invalidate cached preferred size
624 	fPreferredSize.Set(-1, -1);
625 }
626 
627 
628 void BButton::_ReservedButton1() {}
629 void BButton::_ReservedButton2() {}
630 void BButton::_ReservedButton3() {}
631 
632 
633 BButton &
634 BButton::operator=(const BButton &)
635 {
636 	return *this;
637 }
638 
639 
640 BSize
641 BButton::_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 
701 BRect
702 BButton::_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 
719 inline bool
720 BButton::_Flag(uint32 flag) const
721 {
722 	return (fFlags & flag) != 0;
723 }
724 
725 
726 inline bool
727 BButton::_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 
741 extern "C" void
742 B_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