btrfs: Attempt to fix GCC2 build.
[haiku.git] / src / kits / interface / Alert.cpp
blobf924604184a8a230e6e4b62b492ead916b6a177d
1 /*
2 * Copyright 2001-2015 Haiku, Inc. All rights reserved.
3 * Distributed under the terms of the MIT License.
5 * Authors:
6 * Axel Dörfler, axeld@pinc-software.de
7 * Erik Jaesler, erik@cgsoftware.com
8 * John Scipione, jscipione@gmail.com
9 */
12 //! BAlert displays a modal alert window.
15 #include <Alert.h>
17 #include <new>
19 #include <stdio.h>
21 #include <Bitmap.h>
22 #include <Button.h>
23 #include <ControlLook.h>
24 #include <FindDirectory.h>
25 #include <IconUtils.h>
26 #include <LayoutBuilder.h>
27 #include <MenuField.h>
28 #include <MessageFilter.h>
29 #include <Path.h>
30 #include <Resources.h>
31 #include <Screen.h>
32 #include <String.h>
33 #include <Window.h>
35 #include <binary_compatibility/Interface.h>
38 //#define DEBUG_ALERT
39 #ifdef DEBUG_ALERT
40 # define FTRACE(x) fprintf(x)
41 #else
42 # define FTRACE(x) ;
43 #endif
46 class TAlertView : public BView {
47 public:
48 TAlertView();
49 TAlertView(BMessage* archive);
50 ~TAlertView();
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; }
62 BBitmap* Bitmap()
63 { return fIconBitmap; }
65 private:
66 BBitmap* fIconBitmap;
70 class _BAlertFilter_ : public BMessageFilter {
71 public:
72 _BAlertFilter_(BAlert* Alert);
73 ~_BAlertFilter_();
75 virtual filter_result Filter(BMessage* msg, BHandler** target);
77 private:
78 BAlert* fAlert;
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;
93 static inline int32
94 icon_layout_scale()
96 return max_c(1, ((int32)be_plain_font->Size() + 15) / 16);
100 // #pragma mark -
103 BAlert::BAlert()
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,
109 B_INFO_ALERT);
113 BAlert::BAlert(const char *title, const char *text, const char *button1,
114 const char *button2, const char *button3, button_width width,
115 alert_type type)
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)
137 BWindow(data)
139 fInvoker = NULL;
140 fAlertSem = -1;
141 fAlertValue = -1;
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_");
149 if (view)
150 view->SetBitmap(_CreateTypeIcon());
152 // Get keys
153 char key;
154 for (int32 i = 0; i < 3; ++i) {
155 if (data->FindInt8("_but_key", i, (int8*)&key) == B_OK)
156 fKeys[i] = key;
159 AddCommonFilter(new(std::nothrow) _BAlertFilter_(this));
163 BAlert::~BAlert()
165 // Probably not necessary, but it makes me feel better.
166 if (fAlertSem >= B_OK)
167 delete_sem(fAlertSem);
171 BArchivable*
172 BAlert::Instantiate(BMessage* data)
174 if (!validate_instantiation(data, "BAlert"))
175 return NULL;
177 return new(std::nothrow) BAlert(data);
181 status_t
182 BAlert::Archive(BMessage* data, bool deep) const
184 status_t ret = BWindow::Archive(data, deep);
186 // Stow the text
187 if (ret == B_OK)
188 ret = data->AddString("_text", fTextView->Text());
190 // Stow the alert type
191 if (ret == B_OK)
192 ret = data->AddInt32("_atype", fType);
194 // Stow the button width
195 if (ret == B_OK)
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.
202 if (ret == B_OK)
203 ret = data->AddInt8("_but_key", fKeys[0]);
204 if (ret == B_OK)
205 ret = data->AddInt8("_but_key", fKeys[1]);
206 if (ret == B_OK)
207 ret = data->AddInt8("_but_key", fKeys[2]);
210 return ret;
214 alert_type
215 BAlert::Type() const
217 return (alert_type)fType;
221 void
222 BAlert::SetType(alert_type type)
224 fType = type;
228 void
229 BAlert::SetText(const char* text)
231 TextView()->SetText(text);
235 void
236 BAlert::SetIcon(BBitmap* bitmap)
238 fIconView->SetBitmap(bitmap);
242 void
243 BAlert::SetButtonSpacing(button_spacing spacing)
245 fButtonSpacing = spacing;
249 void
250 BAlert::SetButtonWidth(button_width width)
252 fButtonWidth = width;
256 void
257 BAlert::SetShortcut(int32 index, char key)
259 if (index >= 0 && (size_t)index < fKeys.size())
260 fKeys[index] = key;
264 char
265 BAlert::Shortcut(int32 index) const
267 if (index >= 0 && (size_t)index < fKeys.size())
268 return fKeys[index];
270 return 0;
274 int32
275 BAlert::Go()
277 fAlertSem = create_sem(0, "AlertSem");
278 if (fAlertSem < 0) {
279 Quit();
280 return -1;
283 // Get the originating window, if it exists
284 BWindow* window = dynamic_cast<BWindow*>(
285 BLooper::LooperForThread(find_thread(NULL)));
287 _Prepare();
288 Show();
290 if (window != NULL) {
291 status_t status;
292 for (;;) {
293 do {
294 status = acquire_sem_etc(fAlertSem, 1, B_RELATIVE_TIMEOUT,
295 kSemTimeOut);
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
301 break;
303 window->UpdateIfNeeded();
305 } else {
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;
313 if (Lock())
314 Quit();
316 return value;
320 status_t
321 BAlert::Go(BInvoker* invoker)
323 fInvoker = invoker;
324 _Prepare();
325 Show();
326 return B_OK;
330 void
331 BAlert::MessageReceived(BMessage* msg)
333 if (msg->what != kAlertButtonMsg)
334 return BWindow::MessageReceived(msg);
336 int32 which;
337 if (msg->FindInt32("which", &which) == B_OK) {
338 if (fAlertSem < 0) {
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))
344 fInvoker->Invoke();
346 PostMessage(B_QUIT_REQUESTED);
347 } else {
348 // Created semaphore means were running synchronously
349 fAlertValue = which;
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);
357 fAlertSem = -1;
363 void
364 BAlert::FrameResized(float newWidth, float newHeight)
366 BWindow::FrameResized(newWidth, newHeight);
370 void
371 BAlert::AddButton(const char* label, char key)
373 if (label == NULL || label[0] == '\0')
374 return;
376 BButton* button = _CreateButton(fButtons.size(), label);
377 fButtons.push_back(button);
378 fKeys.push_back(key);
380 SetDefaultButton(button);
381 fButtonLayout->AddView(button);
385 int32
386 BAlert::CountButtons() const
388 return (int32)fButtons.size();
392 BButton*
393 BAlert::ButtonAt(int32 index) const
395 if (index >= 0 && (size_t)index < fButtons.size())
396 return fButtons[index];
398 return NULL;
402 BTextView*
403 BAlert::TextView() const
405 return fTextView;
409 BHandler*
410 BAlert::ResolveSpecifier(BMessage* msg, int32 index,
411 BMessage* specifier, int32 form, const char* property)
413 return BWindow::ResolveSpecifier(msg, index, specifier, form, property);
417 status_t
418 BAlert::GetSupportedSuites(BMessage* data)
420 return BWindow::GetSupportedSuites(data);
424 void
425 BAlert::DispatchMessage(BMessage* msg, BHandler* handler)
427 BWindow::DispatchMessage(msg, handler);
431 void
432 BAlert::Quit()
434 BWindow::Quit();
438 bool
439 BAlert::QuitRequested()
441 return BWindow::QuitRequested();
445 //! This method is deprecated, do not use - use BWindow::CenterIn() instead.
446 BPoint
447 BAlert::AlertPosition(float width, float height)
449 BPoint result(100, 100);
451 BWindow* window =
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);
465 return result;
469 status_t
470 BAlert::Perform(perform_code code, void* _data)
472 switch (code) {
473 case PERFORM_CODE_SET_LAYOUT:
474 perform_data_set_layout* data = (perform_data_set_layout*)_data;
475 BAlert::SetLayout(data->layout);
476 return B_OK;
479 return BWindow::Perform(code, _data);
483 void BAlert::_ReservedAlert1() {}
484 void BAlert::_ReservedAlert2() {}
485 void BAlert::_ReservedAlert3() {}
488 void
489 BAlert::_Init(const char* text, const char* button0, const char* button1,
490 const char* button2, button_width buttonWidth, button_spacing spacing,
491 alert_type type)
493 fInvoker = NULL;
494 fAlertSem = -1;
495 fAlertValue = -1;
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);
510 SetType(type);
511 SetButtonWidth(buttonWidth);
512 SetButtonSpacing(spacing);
513 SetText(text);
515 BLayoutBuilder::Group<>(this, B_HORIZONTAL, 0)
516 .Add(fIconView)
517 .AddGroup(B_VERTICAL, B_USE_HALF_ITEM_SPACING)
518 .SetInsets(B_USE_HALF_ITEM_INSETS)
519 .Add(fTextView)
520 .AddGroup(B_HORIZONTAL, 0)
521 .AddGlue()
522 .Add(fButtonLayout);
524 AddButton(button0);
525 AddButton(button1);
526 AddButton(button2);
528 AddCommonFilter(new(std::nothrow) _BAlertFilter_(this));
532 BBitmap*
533 BAlert::_CreateTypeIcon()
535 if (Type() == B_EMPTY_ALERT)
536 return NULL;
538 // The icons are in the app_server resources
539 BBitmap* icon = NULL;
540 BPath path;
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)));
545 return NULL;
548 path.Append("app_server");
549 BFile file;
550 status = file.SetTo(path.Path(), B_READ_ONLY);
551 if (status != B_OK) {
552 FTRACE((stderr, "BAlert::_CreateTypeIcon() - BFile init failed: %s\n",
553 strerror(status)));
554 return NULL;
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)));
562 return NULL;
565 // Which icon are we trying to load?
566 const char* iconName;
567 switch (fType) {
568 case B_INFO_ALERT:
569 iconName = "info";
570 break;
571 case B_IDEA_ALERT:
572 iconName = "idea";
573 break;
574 case B_WARNING_ALERT:
575 iconName = "warn";
576 break;
577 case B_STOP_ALERT:
578 iconName = "stop";
579 break;
581 default:
582 // Alert type is either invalid or B_EMPTY_ALERT;
583 // either way, we're not going to load an icon
584 return NULL;
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),
590 0, B_RGBA32);
591 if (icon == NULL || icon->InitCheck() < B_OK) {
592 FTRACE((stderr, "BAlert::_CreateTypeIcon() - No memory for bitmap\n"));
593 delete icon;
594 return NULL;
597 // Load the raw icon data
598 size_t size = 0;
599 const uint8* rawIcon;
601 // Try to load vector icon
602 rawIcon = (const uint8*)resources.LoadResource(B_VECTOR_ICON_TYPE,
603 iconName, &size);
604 if (rawIcon != NULL
605 && BIconUtils::GetVectorIcon(rawIcon, size, icon) == B_OK) {
606 return icon;
609 // Fall back to bitmap icon
610 rawIcon = (const uint8*)resources.LoadResource(B_LARGE_ICON_TYPE,
611 iconName, &size);
612 if (rawIcon == NULL) {
613 FTRACE((stderr, "BAlert::_CreateTypeIcon() - Icon resource not found\n"));
614 delete icon;
615 return NULL;
618 // Handle color space conversion
619 if (icon->ColorSpace() != B_CMAP8) {
620 BIconUtils::ConvertFromCMAP8(rawIcon, iconSize, iconSize,
621 iconSize, icon);
624 return icon;
628 BButton*
629 BAlert::_CreateButton(int32 which, const char* label)
631 BMessage* message = new BMessage(kAlertButtonMsg);
632 if (message == NULL)
633 return NULL;
635 message->AddInt32("which", which);
637 char name[32];
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.
646 void
647 BAlert::_Prepare()
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
668 float maxWidth = 0;
669 for (int32 index = 0; index < CountButtons(); index++) {
670 BButton* button = ButtonAt(index);
671 float width;
672 button->GetPreferredSize(&width, NULL);
674 if (width > maxWidth)
675 maxWidth = width;
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) {
684 // Insert some strut
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
691 // screen frame.
692 float minWindowWidth = (fButtonSpacing == B_OFFSET_SPACING
693 ? kWindowOffsetMinWidth : kWindowMinWidth) * fontFactor;
694 GetLayout()->SetExplicitMinSize(BSize(minWindowWidth, B_SIZE_UNSET));
696 ResizeToPreferred();
698 // Return early if we've already been moved...
699 if (Frame().left != 0 && Frame().right != 0)
700 return;
702 // otherwise center ourselves on-top of parent window/screen
703 BWindow* parent = dynamic_cast<BWindow*>(BLooper::LooperForThread(
704 find_thread(NULL)));
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),
719 fIconBitmap(NULL)
721 SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
725 TAlertView::TAlertView(BMessage* archive)
727 BView(archive),
728 fIconBitmap(NULL)
733 TAlertView::~TAlertView()
735 delete fIconBitmap;
739 TAlertView*
740 TAlertView::Instantiate(BMessage* archive)
742 if (!validate_instantiation(archive, "TAlertView"))
743 return NULL;
745 return new(std::nothrow) TAlertView(archive);
749 status_t
750 TAlertView::Archive(BMessage* archive, bool deep) const
752 return BView::Archive(archive, deep);
756 void
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();
764 else
765 *_width = 0;
767 if (_height != NULL) {
768 if (fIconBitmap != NULL)
769 *_height = 6 * scale + fIconBitmap->Bounds().Height();
770 else
771 *_height = 0;
776 BSize
777 TAlertView::MaxSize()
779 return BSize(MinSize().width, B_SIZE_UNLIMITED);
783 void
784 TAlertView::Draw(BRect updateRect)
786 if (fIconBitmap == NULL)
787 return;
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),
808 fAlert(alert)
813 _BAlertFilter_::~_BAlertFilter_()
818 filter_result
819 _BAlertFilter_::Filter(BMessage* msg, BHandler** target)
821 if (msg->what == B_KEY_DOWN) {
822 char byte;
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)) {
826 char space = ' ';
827 fAlert->ButtonAt(i)->KeyDown(&space, 1);
829 return B_SKIP_MESSAGE;
835 return B_DISPATCH_MESSAGE;