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