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