xref: /haiku/src/kits/interface/Button.cpp (revision 4a55cc230cf7566cadcbb23b1928eefff8aea9a2)
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 	// always leave some room around the label
159 	float labelMargin = be_control_look->DefaultLabelSpacing() / 2;
160 	rect.InsetBy(labelMargin, labelMargin);
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 		const float labelSpacing = be_control_look->DefaultLabelSpacing();
655 		float width = left + right + labelSpacing - 1;
656 
657 		const char* label = Label();
658 		if (label != NULL) {
659 			width = std::max(width, ceilf(labelSpacing * 3.3f));
660 			width += ceilf(StringWidth(label));
661 		}
662 
663 		const BBitmap* icon = IconBitmap(B_INACTIVE_ICON_BITMAP);
664 		if (icon != NULL)
665 			width += icon->Bounds().Width() + 1;
666 
667 		if (label != NULL && icon != NULL)
668 			width += labelSpacing;
669 
670 		// height
671 		float minHorizontalMargins = top + bottom + labelSpacing;
672 		float height = -1;
673 
674 		if (label != NULL) {
675 			font_height fontHeight;
676 			GetFontHeight(&fontHeight);
677 			float textHeight = fontHeight.ascent + fontHeight.descent;
678 			height = ceilf(textHeight * 1.8);
679 			float margins = height - ceilf(textHeight);
680 			if (margins < minHorizontalMargins)
681 				height += minHorizontalMargins - margins;
682 		}
683 
684 		if (icon != NULL) {
685 			height = std::max(height,
686 				icon->Bounds().Height() + minHorizontalMargins);
687 		}
688 
689 		// force some minimum width/height values
690 		width = std::max(width, label != NULL ? (labelSpacing * 12.5f) : labelSpacing);
691 		height = std::max(height, labelSpacing);
692 
693 		fPreferredSize.Set(width, height);
694 
695 		ResetLayoutInvalidation();
696 	}
697 
698 	return fPreferredSize;
699 }
700 
701 
702 BRect
703 BButton::_PopUpRect() const
704 {
705 	if (fBehavior != B_POP_UP_BEHAVIOR)
706 		return BRect();
707 
708 	float left, top, right, bottom;
709 	be_control_look->GetInsets(BControlLook::B_BUTTON_FRAME,
710 		BControlLook::B_BUTTON_WITH_POP_UP_BACKGROUND,
711 		IsDefault() ? BControlLook::B_DEFAULT_BUTTON : 0,
712 		left, top, right, bottom);
713 
714 	BRect rect(Bounds());
715 	rect.left = rect.right - right + 1;
716 	return rect;
717 }
718 
719 
720 inline bool
721 BButton::_Flag(uint32 flag) const
722 {
723 	return (fFlags & flag) != 0;
724 }
725 
726 
727 inline bool
728 BButton::_SetFlag(uint32 flag, bool set)
729 {
730 	if (((fFlags & flag) != 0) == set)
731 		return false;
732 
733 	if (set)
734 		fFlags |= flag;
735 	else
736 		fFlags &= ~flag;
737 
738 	return true;
739 }
740 
741 
742 extern "C" void
743 B_IF_GCC_2(InvalidateLayout__7BButtonb, _ZN7BButton16InvalidateLayoutEb)(
744 	BView* view, bool descendants)
745 {
746 	perform_data_layout_invalidated data;
747 	data.descendants = descendants;
748 
749 	view->Perform(PERFORM_CODE_LAYOUT_INVALIDATED, &data);
750 }
751