2 * Copyright 2001-2015 Haiku, Inc. All rights reserved.
3 * Distributed under the terms of the MIT License.
6 * Axel Dörfler, axeld@pinc-software.de
7 * Erik Jaesler, erik@cgsoftware.com
8 * John Scipione, jscipione@gmail.com
12 //! BAlert displays a modal alert window.
23 #include <ControlLook.h>
24 #include <FindDirectory.h>
25 #include <IconUtils.h>
26 #include <LayoutBuilder.h>
27 #include <MenuField.h>
28 #include <MessageFilter.h>
30 #include <Resources.h>
35 #include <binary_compatibility/Interface.h>
40 # define FTRACE(x) fprintf(x)
46 class TAlertView
: public BView
{
49 TAlertView(BMessage
* archive
);
52 static TAlertView
* Instantiate(BMessage
* archive
);
53 virtual status_t
Archive(BMessage
* archive
,
54 bool deep
= true) const;
56 virtual void GetPreferredSize(float* _width
, float* _height
);
57 virtual BSize
MaxSize();
58 virtual void Draw(BRect updateRect
);
60 void SetBitmap(BBitmap
* Icon
)
61 { fIconBitmap
= Icon
; }
63 { return fIconBitmap
; }
70 class _BAlertFilter_
: public BMessageFilter
{
72 _BAlertFilter_(BAlert
* Alert
);
75 virtual filter_result
Filter(BMessage
* msg
, BHandler
** target
);
82 static const unsigned int kAlertButtonMsg
= 'ALTB';
83 static const int kSemTimeOut
= 50000;
85 static const int kButtonOffsetSpacing
= 62;
86 static const int kButtonUsualWidth
= 55;
87 static const int kIconStripeWidth
= 30;
89 static const int kWindowMinWidth
= 310;
90 static const int kWindowOffsetMinWidth
= 335;
96 return max_c(1, ((int32
)be_plain_font
->Size() + 15) / 16);
105 BWindow(BRect(0, 0, 100, 100), "", B_MODAL_WINDOW
,
106 B_NOT_CLOSABLE
| B_NOT_RESIZABLE
| B_ASYNCHRONOUS_CONTROLS
)
108 _Init(NULL
, NULL
, NULL
, NULL
, B_WIDTH_FROM_WIDEST
, B_EVEN_SPACING
,
113 BAlert::BAlert(const char *title
, const char *text
, const char *button1
,
114 const char *button2
, const char *button3
, button_width width
,
117 BWindow(BRect(0, 0, 100, 100), title
, B_MODAL_WINDOW
,
118 B_NOT_CLOSABLE
| B_NOT_RESIZABLE
| B_ASYNCHRONOUS_CONTROLS
)
120 _Init(text
, button1
, button2
, button3
, width
, B_EVEN_SPACING
, type
);
124 BAlert::BAlert(const char *title
, const char *text
, const char *button1
,
125 const char *button2
, const char *button3
, button_width width
,
126 button_spacing spacing
, alert_type type
)
128 BWindow(BRect(0, 0, 100, 100), title
, B_MODAL_WINDOW
,
129 B_NOT_CLOSABLE
| B_NOT_RESIZABLE
| B_ASYNCHRONOUS_CONTROLS
)
131 _Init(text
, button1
, button2
, button3
, width
, spacing
, type
);
135 BAlert::BAlert(BMessage
* data
)
143 fTextView
= (BTextView
*)FindView("_tv_");
145 // TODO: window loses default button on dearchive!
146 // TODO: ButtonAt() doesn't work afterwards (also affects shortcuts)
148 TAlertView
* view
= (TAlertView
*)FindView("_master_");
150 view
->SetBitmap(_CreateTypeIcon());
154 for (int32 i
= 0; i
< 3; ++i
) {
155 if (data
->FindInt8("_but_key", i
, (int8
*)&key
) == B_OK
)
159 AddCommonFilter(new(std::nothrow
) _BAlertFilter_(this));
165 // Probably not necessary, but it makes me feel better.
166 if (fAlertSem
>= B_OK
)
167 delete_sem(fAlertSem
);
172 BAlert::Instantiate(BMessage
* data
)
174 if (!validate_instantiation(data
, "BAlert"))
177 return new(std::nothrow
) BAlert(data
);
182 BAlert::Archive(BMessage
* data
, bool deep
) const
184 status_t ret
= BWindow::Archive(data
, deep
);
188 ret
= data
->AddString("_text", fTextView
->Text());
190 // Stow the alert type
192 ret
= data
->AddInt32("_atype", fType
);
194 // Stow the button width
196 ret
= data
->AddInt32("_but_width", fButtonWidth
);
198 // Stow the shortcut keys
199 if (fKeys
[0] || fKeys
[1] || fKeys
[2]) {
200 // If we have any to save, we must save something for everyone so it
201 // doesn't get confusing on the unarchive.
203 ret
= data
->AddInt8("_but_key", fKeys
[0]);
205 ret
= data
->AddInt8("_but_key", fKeys
[1]);
207 ret
= data
->AddInt8("_but_key", fKeys
[2]);
217 return (alert_type
)fType
;
222 BAlert::SetType(alert_type type
)
229 BAlert::SetText(const char* text
)
231 TextView()->SetText(text
);
236 BAlert::SetIcon(BBitmap
* bitmap
)
238 fIconView
->SetBitmap(bitmap
);
243 BAlert::SetButtonSpacing(button_spacing spacing
)
245 fButtonSpacing
= spacing
;
250 BAlert::SetButtonWidth(button_width width
)
252 fButtonWidth
= width
;
257 BAlert::SetShortcut(int32 index
, char key
)
259 if (index
>= 0 && (size_t)index
< fKeys
.size())
265 BAlert::Shortcut(int32 index
) const
268 if (index
>= 0 && (size_t)index
< fKeys
.size())
278 fAlertSem
= create_sem(0, "AlertSem");
284 // Get the originating window, if it exists
285 BWindow
* window
= dynamic_cast<BWindow
*>(
286 BLooper::LooperForThread(find_thread(NULL
)));
291 if (window
!= NULL
) {
295 status
= acquire_sem_etc(fAlertSem
, 1, B_RELATIVE_TIMEOUT
,
297 // We've (probably) had our time slice taken away from us
298 } while (status
== B_INTERRUPTED
);
300 if (status
== B_BAD_SEM_ID
) {
301 // Semaphore was finally nuked in MessageReceived
304 window
->UpdateIfNeeded();
307 // No window to update, so just hang out until we're done.
308 while (acquire_sem(fAlertSem
) == B_INTERRUPTED
) {
312 // Have to cache the value since we delete on Quit()
313 int32 value
= fAlertValue
;
322 BAlert::Go(BInvoker
* invoker
)
332 BAlert::MessageReceived(BMessage
* msg
)
334 if (msg
->what
!= kAlertButtonMsg
)
335 return BWindow::MessageReceived(msg
);
338 if (msg
->FindInt32("which", &which
) == B_OK
) {
339 if (fAlertSem
< B_OK
) {
340 // Semaphore hasn't been created; we're running asynchronous
342 BMessage
* out
= fInvoker
->Message();
343 if (out
&& (out
->ReplaceInt32("which", which
) == B_OK
344 || out
->AddInt32("which", which
) == B_OK
))
347 PostMessage(B_QUIT_REQUESTED
);
349 // Created semaphore means were running synchronously
352 // TextAlertVar does release_sem() below, and then sets the
353 // member var. That doesn't make much sense to me, since we
354 // want to be able to clean up at some point. Better to just
355 // nuke the semaphore now; we don't need it any more and this
356 // lets synchronous Go() continue just as well.
357 delete_sem(fAlertSem
);
365 BAlert::FrameResized(float newWidth
, float newHeight
)
367 BWindow::FrameResized(newWidth
, newHeight
);
372 BAlert::AddButton(const char* label
, char key
)
374 if (label
== NULL
|| label
[0] == '\0')
377 BButton
* button
= _CreateButton(fButtons
.size(), label
);
378 fButtons
.push_back(button
);
379 fKeys
.push_back(key
);
381 fButtonLayout
->AddView(button
);
386 BAlert::CountButtons() const
388 return (int32
)fButtons
.size();
393 BAlert::ButtonAt(int32 index
) const
395 if (index
>= 0 && (size_t)index
< fButtons
.size())
396 return fButtons
[index
];
403 BAlert::TextView() const
410 BAlert::ResolveSpecifier(BMessage
* msg
, int32 index
,
411 BMessage
* specifier
, int32 form
, const char* property
)
413 return BWindow::ResolveSpecifier(msg
, index
, specifier
, form
, property
);
418 BAlert::GetSupportedSuites(BMessage
* data
)
420 return BWindow::GetSupportedSuites(data
);
425 BAlert::DispatchMessage(BMessage
* msg
, BHandler
* handler
)
427 BWindow::DispatchMessage(msg
, handler
);
439 BAlert::QuitRequested()
441 return BWindow::QuitRequested();
445 //! This method is deprecated, do not use - use BWindow::CenterIn() instead.
447 BAlert::AlertPosition(float width
, float height
)
449 BPoint
result(100, 100);
452 dynamic_cast<BWindow
*>(BLooper::LooperForThread(find_thread(NULL
)));
454 BScreen
screen(window
);
455 BRect
screenFrame(0, 0, 640, 480);
456 if (screen
.IsValid())
457 screenFrame
= screen
.Frame();
459 // Horizontally, we're smack in the middle
460 result
.x
= screenFrame
.left
+ (screenFrame
.Width() / 2.0) - (width
/ 2.0);
462 // This is probably sooo wrong, but it looks right on 1024 x 768
463 result
.y
= screenFrame
.top
+ (screenFrame
.Height() / 4.0) - ceil(height
/ 3.0);
470 BAlert::Perform(perform_code code
, void* _data
)
473 case PERFORM_CODE_SET_LAYOUT
:
474 perform_data_set_layout
* data
= (perform_data_set_layout
*)_data
;
475 BAlert::SetLayout(data
->layout
);
479 return BWindow::Perform(code
, _data
);
483 void BAlert::_ReservedAlert1() {}
484 void BAlert::_ReservedAlert2() {}
485 void BAlert::_ReservedAlert3() {}
489 BAlert::_Init(const char* text
, const char* button0
, const char* button1
,
490 const char* button2
, button_width buttonWidth
, button_spacing spacing
,
493 fIconView
= new TAlertView();
495 fTextView
= new BTextView("_tv_");
496 fTextView
->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR
));
497 rgb_color textColor
= ui_color(B_PANEL_TEXT_COLOR
);
498 fTextView
->SetFontAndColor(be_plain_font
, B_FONT_ALL
, &textColor
);
499 fTextView
->MakeEditable(false);
500 fTextView
->MakeSelectable(false);
501 fTextView
->SetWordWrap(true);
502 fTextView
->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED
, B_SIZE_UNLIMITED
));
504 fButtonLayout
= new BGroupLayout(B_HORIZONTAL
);
507 SetButtonWidth(buttonWidth
);
508 SetButtonSpacing(spacing
);
511 BLayoutBuilder::Group
<>(this, B_HORIZONTAL
, 0)
513 .AddGroup(B_VERTICAL
)
514 .SetInsets(B_USE_DEFAULT_SPACING
)
516 .AddGroup(B_HORIZONTAL
, 0)
527 BAlert::_CreateTypeIcon()
529 if (Type() == B_EMPTY_ALERT
)
532 // The icons are in the app_server resources
533 BBitmap
* icon
= NULL
;
535 status_t status
= find_directory(B_BEOS_SERVERS_DIRECTORY
, &path
);
536 if (status
!= B_OK
) {
537 FTRACE((stderr
, "BAlert::_CreateTypeIcon() - find_directory "
538 "failed: %s\n", strerror(status
)));
542 path
.Append("app_server");
544 status
= file
.SetTo(path
.Path(), B_READ_ONLY
);
545 if (status
!= B_OK
) {
546 FTRACE((stderr
, "BAlert::_CreateTypeIcon() - BFile init failed: %s\n",
551 BResources resources
;
552 status
= resources
.SetTo(&file
);
553 if (status
!= B_OK
) {
554 FTRACE((stderr
, "BAlert::_CreateTypeIcon() - BResources init "
555 "failed: %s\n", strerror(status
)));
559 // Which icon are we trying to load?
560 const char* iconName
;
568 case B_WARNING_ALERT
:
576 // Alert type is either invalid or B_EMPTY_ALERT;
577 // either way, we're not going to load an icon
581 int32 iconSize
= 32 * icon_layout_scale();
582 // Allocate the icon bitmap
583 icon
= new(std::nothrow
) BBitmap(BRect(0, 0, iconSize
- 1, iconSize
- 1),
585 if (icon
== NULL
|| icon
->InitCheck() < B_OK
) {
586 FTRACE((stderr
, "BAlert::_CreateTypeIcon() - No memory for bitmap\n"));
591 // Load the raw icon data
593 const uint8
* rawIcon
;
595 // Try to load vector icon
596 rawIcon
= (const uint8
*)resources
.LoadResource(B_VECTOR_ICON_TYPE
,
599 && BIconUtils::GetVectorIcon(rawIcon
, size
, icon
) == B_OK
) {
603 // Fall back to bitmap icon
604 rawIcon
= (const uint8
*)resources
.LoadResource(B_LARGE_ICON_TYPE
,
606 if (rawIcon
== NULL
) {
607 FTRACE((stderr
, "BAlert::_CreateTypeIcon() - Icon resource not found\n"));
612 // Handle color space conversion
613 if (icon
->ColorSpace() != B_CMAP8
) {
614 BIconUtils::ConvertFromCMAP8(rawIcon
, iconSize
, iconSize
,
623 BAlert::_CreateButton(int32 which
, const char* label
)
625 BMessage
* message
= new BMessage(kAlertButtonMsg
);
629 message
->AddInt32("which", which
);
632 snprintf(name
, sizeof(name
), "_b%" B_PRId32
"_", which
);
634 return new(std::nothrow
) BButton(name
, label
, message
);
638 /*! Tweaks the layout according to the configuration.
643 // Must have at least one button
644 if (CountButtons() == 0)
645 debugger("BAlerts must have at least one button.");
647 SetDefaultButton(ButtonAt(CountButtons() - 1));
649 float fontFactor
= be_plain_font
->Size() / 11.0f
;
651 if (fIconView
->Bitmap() == NULL
)
652 fIconView
->SetBitmap(_CreateTypeIcon());
654 if (fButtonWidth
== B_WIDTH_AS_USUAL
) {
655 float usualWidth
= kButtonUsualWidth
* fontFactor
;
657 for (int32 index
= 0; index
< CountButtons(); index
++) {
658 BButton
* button
= ButtonAt(index
);
659 if (button
->MinSize().width
< usualWidth
)
660 button
->SetExplicitSize(BSize(usualWidth
, B_SIZE_UNSET
));
662 } else if (fButtonWidth
== B_WIDTH_FROM_WIDEST
) {
663 // Get width of widest label
665 for (int32 index
= 0; index
< CountButtons(); index
++) {
666 BButton
* button
= ButtonAt(index
);
668 button
->GetPreferredSize(&width
, NULL
);
670 if (width
> maxWidth
)
673 for (int32 index
= 0; index
< CountButtons(); index
++) {
674 BButton
* button
= ButtonAt(index
);
675 button
->SetExplicitSize(BSize(maxWidth
, B_SIZE_UNSET
));
679 if (fButtonSpacing
== B_OFFSET_SPACING
&& CountButtons() > 1) {
681 fButtonLayout
->AddItem(1, BSpaceLayoutItem::CreateHorizontalStrut(
682 kButtonOffsetSpacing
* fontFactor
));
685 // Position the alert so that it is centered vertically but offset a bit
686 // horizontally in the parent window's frame or, if unavailable, the
688 float minWindowWidth
= (fButtonSpacing
== B_OFFSET_SPACING
689 ? kWindowOffsetMinWidth
: kWindowMinWidth
) * fontFactor
;
690 GetLayout()->SetExplicitMinSize(BSize(minWindowWidth
, B_SIZE_UNSET
));
694 BWindow
* parent
= dynamic_cast<BWindow
*>(BLooper::LooperForThread(
696 const BRect frame
= parent
!= NULL
? parent
->Frame()
697 : BScreen(this).Frame();
699 MoveTo(static_cast<BWindow
*>(this)->AlertPosition(frame
));
700 // Hidden by BAlert::AlertPosition()
704 // #pragma mark - TAlertView
707 TAlertView::TAlertView()
709 BView("TAlertView", B_WILL_DRAW
),
712 SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR
));
716 TAlertView::TAlertView(BMessage
* archive
)
724 TAlertView::~TAlertView()
731 TAlertView::Instantiate(BMessage
* archive
)
733 if (!validate_instantiation(archive
, "TAlertView"))
736 return new(std::nothrow
) TAlertView(archive
);
741 TAlertView::Archive(BMessage
* archive
, bool deep
) const
743 return BView::Archive(archive
, deep
);
748 TAlertView::GetPreferredSize(float* _width
, float* _height
)
750 int32 scale
= icon_layout_scale();
752 if (_width
!= NULL
) {
753 if (fIconBitmap
!= NULL
)
754 *_width
= 18 * scale
+ fIconBitmap
->Bounds().Width();
758 if (_height
!= NULL
) {
759 if (fIconBitmap
!= NULL
)
760 *_height
= 6 * scale
+ fIconBitmap
->Bounds().Height();
768 TAlertView::MaxSize()
770 return BSize(MinSize().width
, B_SIZE_UNLIMITED
);
775 TAlertView::Draw(BRect updateRect
)
777 if (fIconBitmap
== NULL
)
780 // Here's the fun stuff
781 BRect stripeRect
= Bounds();
782 int32 iconLayoutScale
= icon_layout_scale();
783 stripeRect
.right
= kIconStripeWidth
* iconLayoutScale
;
784 SetHighColor(tint_color(ViewColor(), B_DARKEN_1_TINT
));
785 FillRect(stripeRect
);
787 SetDrawingMode(B_OP_ALPHA
);
788 SetBlendingMode(B_PIXEL_ALPHA
, B_ALPHA_OVERLAY
);
789 DrawBitmapAsync(fIconBitmap
, BPoint(18 * iconLayoutScale
,
790 6 * iconLayoutScale
));
794 // #pragma mark - _BAlertFilter_
797 _BAlertFilter_::_BAlertFilter_(BAlert
* alert
)
798 : BMessageFilter(B_KEY_DOWN
),
804 _BAlertFilter_::~_BAlertFilter_()
810 _BAlertFilter_::Filter(BMessage
* msg
, BHandler
** target
)
812 if (msg
->what
== B_KEY_DOWN
) {
814 if (msg
->FindInt8("byte", (int8
*)&byte
) == B_OK
) {
815 for (int i
= 0; i
< 3; ++i
) {
816 if (byte
== fAlert
->Shortcut(i
) && fAlert
->ButtonAt(i
)) {
818 fAlert
->ButtonAt(i
)->KeyDown(&space
, 1);
820 return B_SKIP_MESSAGE
;
826 return B_DISPATCH_MESSAGE
;