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
267 if (index
>= 0 && (size_t)index
< fKeys
.size())
277 fAlertSem
= create_sem(0, "AlertSem");
283 // Get the originating window, if it exists
284 BWindow
* window
= dynamic_cast<BWindow
*>(
285 BLooper::LooperForThread(find_thread(NULL
)));
290 if (window
!= NULL
) {
294 status
= acquire_sem_etc(fAlertSem
, 1, B_RELATIVE_TIMEOUT
,
296 // We've (probably) had our time slice taken away from us
297 } while (status
== B_INTERRUPTED
);
299 if (status
== B_BAD_SEM_ID
) {
300 // Semaphore was finally nuked in MessageReceived
303 window
->UpdateIfNeeded();
306 // No window to update, so just hang out until we're done.
307 while (acquire_sem(fAlertSem
) == B_INTERRUPTED
) {
311 // Have to cache the value since we delete on Quit()
312 int32 value
= fAlertValue
;
321 BAlert::Go(BInvoker
* invoker
)
331 BAlert::MessageReceived(BMessage
* msg
)
333 if (msg
->what
!= kAlertButtonMsg
)
334 return BWindow::MessageReceived(msg
);
337 if (msg
->FindInt32("which", &which
) == B_OK
) {
339 // Semaphore hasn't been created; we're running asynchronous
340 if (fInvoker
!= NULL
) {
341 BMessage
* out
= fInvoker
->Message();
342 if (out
&& (out
->ReplaceInt32("which", which
) == B_OK
343 || out
->AddInt32("which", which
) == B_OK
))
346 PostMessage(B_QUIT_REQUESTED
);
348 // Created semaphore means were running synchronously
351 // TextAlertVar does release_sem() below, and then sets the
352 // member var. That doesn't make much sense to me, since we
353 // want to be able to clean up at some point. Better to just
354 // nuke the semaphore now; we don't need it any more and this
355 // lets synchronous Go() continue just as well.
356 delete_sem(fAlertSem
);
364 BAlert::FrameResized(float newWidth
, float newHeight
)
366 BWindow::FrameResized(newWidth
, newHeight
);
371 BAlert::AddButton(const char* label
, char key
)
373 if (label
== NULL
|| label
[0] == '\0')
376 BButton
* button
= _CreateButton(fButtons
.size(), label
);
377 fButtons
.push_back(button
);
378 fKeys
.push_back(key
);
380 SetDefaultButton(button
);
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
,
497 fIconView
= new TAlertView();
499 fTextView
= new BTextView("_tv_");
500 fTextView
->SetViewUIColor(B_PANEL_BACKGROUND_COLOR
);
501 rgb_color textColor
= ui_color(B_PANEL_TEXT_COLOR
);
502 fTextView
->SetFontAndColor(be_plain_font
, B_FONT_ALL
, &textColor
);
503 fTextView
->MakeEditable(false);
504 fTextView
->MakeSelectable(false);
505 fTextView
->SetWordWrap(true);
506 fTextView
->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED
, B_SIZE_UNLIMITED
));
508 fButtonLayout
= new BGroupLayout(B_HORIZONTAL
, B_USE_HALF_ITEM_SPACING
);
511 SetButtonWidth(buttonWidth
);
512 SetButtonSpacing(spacing
);
515 BLayoutBuilder::Group
<>(this, B_HORIZONTAL
, 0)
517 .AddGroup(B_VERTICAL
, B_USE_HALF_ITEM_SPACING
)
518 .SetInsets(B_USE_HALF_ITEM_INSETS
)
520 .AddGroup(B_HORIZONTAL
, 0)
528 AddCommonFilter(new(std::nothrow
) _BAlertFilter_(this));
533 BAlert::_CreateTypeIcon()
535 if (Type() == B_EMPTY_ALERT
)
538 // The icons are in the app_server resources
539 BBitmap
* icon
= NULL
;
541 status_t status
= find_directory(B_BEOS_SERVERS_DIRECTORY
, &path
);
542 if (status
!= B_OK
) {
543 FTRACE((stderr
, "BAlert::_CreateTypeIcon() - find_directory "
544 "failed: %s\n", strerror(status
)));
548 path
.Append("app_server");
550 status
= file
.SetTo(path
.Path(), B_READ_ONLY
);
551 if (status
!= B_OK
) {
552 FTRACE((stderr
, "BAlert::_CreateTypeIcon() - BFile init failed: %s\n",
557 BResources resources
;
558 status
= resources
.SetTo(&file
);
559 if (status
!= B_OK
) {
560 FTRACE((stderr
, "BAlert::_CreateTypeIcon() - BResources init "
561 "failed: %s\n", strerror(status
)));
565 // Which icon are we trying to load?
566 const char* iconName
;
574 case B_WARNING_ALERT
:
582 // Alert type is either invalid or B_EMPTY_ALERT;
583 // either way, we're not going to load an icon
587 int32 iconSize
= 32 * icon_layout_scale();
588 // Allocate the icon bitmap
589 icon
= new(std::nothrow
) BBitmap(BRect(0, 0, iconSize
- 1, iconSize
- 1),
591 if (icon
== NULL
|| icon
->InitCheck() < B_OK
) {
592 FTRACE((stderr
, "BAlert::_CreateTypeIcon() - No memory for bitmap\n"));
597 // Load the raw icon data
599 const uint8
* rawIcon
;
601 // Try to load vector icon
602 rawIcon
= (const uint8
*)resources
.LoadResource(B_VECTOR_ICON_TYPE
,
605 && BIconUtils::GetVectorIcon(rawIcon
, size
, icon
) == B_OK
) {
609 // Fall back to bitmap icon
610 rawIcon
= (const uint8
*)resources
.LoadResource(B_LARGE_ICON_TYPE
,
612 if (rawIcon
== NULL
) {
613 FTRACE((stderr
, "BAlert::_CreateTypeIcon() - Icon resource not found\n"));
618 // Handle color space conversion
619 if (icon
->ColorSpace() != B_CMAP8
) {
620 BIconUtils::ConvertFromCMAP8(rawIcon
, iconSize
, iconSize
,
629 BAlert::_CreateButton(int32 which
, const char* label
)
631 BMessage
* message
= new BMessage(kAlertButtonMsg
);
635 message
->AddInt32("which", which
);
638 snprintf(name
, sizeof(name
), "_b%" B_PRId32
"_", which
);
640 return new(std::nothrow
) BButton(name
, label
, message
);
644 /*! Tweaks the layout according to the configuration.
649 // Must have at least one button
650 if (CountButtons() == 0)
651 debugger("BAlerts must have at least one button.");
653 float fontFactor
= be_plain_font
->Size() / 11.0f
;
655 if (fIconView
->Bitmap() == NULL
)
656 fIconView
->SetBitmap(_CreateTypeIcon());
658 if (fButtonWidth
== B_WIDTH_AS_USUAL
) {
659 float usualWidth
= kButtonUsualWidth
* fontFactor
;
661 for (int32 index
= 0; index
< CountButtons(); index
++) {
662 BButton
* button
= ButtonAt(index
);
663 if (button
->MinSize().width
< usualWidth
)
664 button
->SetExplicitSize(BSize(usualWidth
, B_SIZE_UNSET
));
666 } else if (fButtonWidth
== B_WIDTH_FROM_WIDEST
) {
667 // Get width of widest label
669 for (int32 index
= 0; index
< CountButtons(); index
++) {
670 BButton
* button
= ButtonAt(index
);
672 button
->GetPreferredSize(&width
, NULL
);
674 if (width
> maxWidth
)
677 for (int32 index
= 0; index
< CountButtons(); index
++) {
678 BButton
* button
= ButtonAt(index
);
679 button
->SetExplicitSize(BSize(maxWidth
, B_SIZE_UNSET
));
683 if (fButtonSpacing
== B_OFFSET_SPACING
&& CountButtons() > 1) {
685 fButtonLayout
->AddItem(1, BSpaceLayoutItem::CreateHorizontalStrut(
686 kButtonOffsetSpacing
* fontFactor
));
689 // Position the alert so that it is centered vertically but offset a bit
690 // horizontally in the parent window's frame or, if unavailable, the
692 float minWindowWidth
= (fButtonSpacing
== B_OFFSET_SPACING
693 ? kWindowOffsetMinWidth
: kWindowMinWidth
) * fontFactor
;
694 GetLayout()->SetExplicitMinSize(BSize(minWindowWidth
, B_SIZE_UNSET
));
698 // Return early if we've already been moved...
699 if (Frame().left
!= 0 && Frame().right
!= 0)
702 // otherwise center ourselves on-top of parent window/screen
703 BWindow
* parent
= dynamic_cast<BWindow
*>(BLooper::LooperForThread(
705 const BRect frame
= parent
!= NULL
? parent
->Frame()
706 : BScreen(this).Frame();
708 MoveTo(static_cast<BWindow
*>(this)->AlertPosition(frame
));
709 // Hidden by BAlert::AlertPosition()
713 // #pragma mark - TAlertView
716 TAlertView::TAlertView()
718 BView("TAlertView", B_WILL_DRAW
),
721 SetViewUIColor(B_PANEL_BACKGROUND_COLOR
);
725 TAlertView::TAlertView(BMessage
* archive
)
733 TAlertView::~TAlertView()
740 TAlertView::Instantiate(BMessage
* archive
)
742 if (!validate_instantiation(archive
, "TAlertView"))
745 return new(std::nothrow
) TAlertView(archive
);
750 TAlertView::Archive(BMessage
* archive
, bool deep
) const
752 return BView::Archive(archive
, deep
);
757 TAlertView::GetPreferredSize(float* _width
, float* _height
)
759 int32 scale
= icon_layout_scale();
761 if (_width
!= NULL
) {
762 if (fIconBitmap
!= NULL
)
763 *_width
= 18 * scale
+ fIconBitmap
->Bounds().Width();
767 if (_height
!= NULL
) {
768 if (fIconBitmap
!= NULL
)
769 *_height
= 6 * scale
+ fIconBitmap
->Bounds().Height();
777 TAlertView::MaxSize()
779 return BSize(MinSize().width
, B_SIZE_UNLIMITED
);
784 TAlertView::Draw(BRect updateRect
)
786 if (fIconBitmap
== NULL
)
789 // Here's the fun stuff
790 BRect stripeRect
= Bounds();
791 int32 iconLayoutScale
= icon_layout_scale();
792 stripeRect
.right
= kIconStripeWidth
* iconLayoutScale
;
793 SetHighColor(tint_color(ViewColor(), B_DARKEN_1_TINT
));
794 FillRect(stripeRect
);
796 SetDrawingMode(B_OP_ALPHA
);
797 SetBlendingMode(B_PIXEL_ALPHA
, B_ALPHA_OVERLAY
);
798 DrawBitmapAsync(fIconBitmap
, BPoint(18 * iconLayoutScale
,
799 6 * iconLayoutScale
));
803 // #pragma mark - _BAlertFilter_
806 _BAlertFilter_::_BAlertFilter_(BAlert
* alert
)
807 : BMessageFilter(B_KEY_DOWN
),
813 _BAlertFilter_::~_BAlertFilter_()
819 _BAlertFilter_::Filter(BMessage
* msg
, BHandler
** target
)
821 if (msg
->what
== B_KEY_DOWN
) {
823 if (msg
->FindInt8("byte", (int8
*)&byte
) == B_OK
) {
824 for (int i
= 0; i
< fAlert
->CountButtons(); ++i
) {
825 if (byte
== fAlert
->Shortcut(i
) && fAlert
->ButtonAt(i
)) {
827 fAlert
->ButtonAt(i
)->KeyDown(&space
, 1);
829 return B_SKIP_MESSAGE
;
835 return B_DISPATCH_MESSAGE
;