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