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