1 /*
2 * Copyright 2001-2015 Haiku, Inc. All rights reserved.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 * Axel Dörfler, axeld@pinc-software.de
7 * Erik Jaesler, erik@cgsoftware.com
8 * John Scipione, jscipione@gmail.com
9 */
10
11
12 //! BAlert displays a modal alert window.
13
14
15 #include <Alert.h>
16
17 #include <new>
18
19 #include <stdio.h>
20
21 #include <Bitmap.h>
22 #include <Button.h>
23 #include <ControlLook.h>
24 #include <Debug.h>
25 #include <FindDirectory.h>
26 #include <IconUtils.h>
27 #include <LayoutBuilder.h>
28 #include <MenuField.h>
29 #include <MessageFilter.h>
30 #include <Path.h>
31 #include <Resources.h>
32 #include <Screen.h>
33 #include <String.h>
34 #include <Window.h>
35
36 #include <binary_compatibility/Interface.h>
37
38
39 //#define DEBUG_ALERT
40 #ifdef DEBUG_ALERT
41 # define FTRACE(x) fprintf(x)
42 #else
43 # define FTRACE(x) ;
44 #endif
45
46
47 class TAlertView : public BView {
48 public:
49 TAlertView();
50 TAlertView(BMessage* archive);
51 ~TAlertView();
52
53 static TAlertView* Instantiate(BMessage* archive);
54 virtual status_t Archive(BMessage* archive,
55 bool deep = true) const;
56
57 virtual void GetPreferredSize(float* _width, float* _height);
58 virtual BSize MaxSize();
59 virtual void Draw(BRect updateRect);
60
61 void SetBitmap(BBitmap* icon);
Bitmap()62 BBitmap* Bitmap()
63 { return fIconBitmap; }
64
65 private:
66 BBitmap* fIconBitmap;
67 };
68
69
70 class _BAlertFilter_ : public BMessageFilter {
71 public:
72 _BAlertFilter_(BAlert* Alert);
73 ~_BAlertFilter_();
74
75 virtual filter_result Filter(BMessage* msg, BHandler** target);
76
77 private:
78 BAlert* fAlert;
79 };
80
81
82 static const unsigned int kAlertButtonMsg = 'ALTB';
83 static const int kSemTimeOut = 50000;
84
85 static const int kButtonOffsetSpacing = 62;
86 static const int kButtonUsualWidth = 55;
87 static const int kIconStripeWidthFactor = 5;
88
89 static const int kWindowMinWidth = 310;
90 static const int kWindowOffsetMinWidth = 335;
91
92
93 // #pragma mark -
94
95
BAlert()96 BAlert::BAlert()
97 :
98 BWindow(BRect(0, 0, 100, 100), "", B_MODAL_WINDOW,
99 B_NOT_CLOSABLE | B_NOT_RESIZABLE | B_ASYNCHRONOUS_CONTROLS)
100 {
101 _Init(NULL, NULL, NULL, NULL, B_WIDTH_FROM_WIDEST, B_EVEN_SPACING,
102 B_INFO_ALERT);
103 }
104
105
BAlert(const char * title,const char * text,const char * button1,const char * button2,const char * button3,button_width width,alert_type type)106 BAlert::BAlert(const char *title, const char *text, const char *button1,
107 const char *button2, const char *button3, button_width width,
108 alert_type type)
109 :
110 BWindow(BRect(0, 0, 100, 100), title, B_MODAL_WINDOW,
111 B_NOT_CLOSABLE | B_NOT_RESIZABLE | B_ASYNCHRONOUS_CONTROLS)
112 {
113 _Init(text, button1, button2, button3, width, B_EVEN_SPACING, type);
114 }
115
116
BAlert(const char * title,const char * text,const char * button1,const char * button2,const char * button3,button_width width,button_spacing spacing,alert_type type)117 BAlert::BAlert(const char *title, const char *text, const char *button1,
118 const char *button2, const char *button3, button_width width,
119 button_spacing spacing, alert_type type)
120 :
121 BWindow(BRect(0, 0, 100, 100), title, B_MODAL_WINDOW,
122 B_NOT_CLOSABLE | B_NOT_RESIZABLE | B_ASYNCHRONOUS_CONTROLS)
123 {
124 _Init(text, button1, button2, button3, width, spacing, type);
125 }
126
127
BAlert(BMessage * data)128 BAlert::BAlert(BMessage* data)
129 :
130 BWindow(data)
131 {
132 fInvoker = NULL;
133 fAlertSem = -1;
134 fAlertValue = -1;
135
136 fTextView = (BTextView*)FindView("_tv_");
137
138 // TODO: window loses default button on dearchive!
139 // TODO: ButtonAt() doesn't work afterwards (also affects shortcuts)
140
141 TAlertView* view = (TAlertView*)FindView("_master_");
142 if (view)
143 view->SetBitmap(_CreateTypeIcon());
144
145 // Get keys
146 char key;
147 for (int32 i = 0; i < 3; ++i) {
148 if (data->FindInt8("_but_key", i, (int8*)&key) == B_OK)
149 fKeys[i] = key;
150 }
151
152 AddCommonFilter(new(std::nothrow) _BAlertFilter_(this));
153 }
154
155
~BAlert()156 BAlert::~BAlert()
157 {
158 // Probably not necessary, but it makes me feel better.
159 if (fAlertSem >= B_OK)
160 delete_sem(fAlertSem);
161 }
162
163
164 BArchivable*
Instantiate(BMessage * data)165 BAlert::Instantiate(BMessage* data)
166 {
167 if (!validate_instantiation(data, "BAlert"))
168 return NULL;
169
170 return new(std::nothrow) BAlert(data);
171 }
172
173
174 status_t
Archive(BMessage * data,bool deep) const175 BAlert::Archive(BMessage* data, bool deep) const
176 {
177 status_t ret = BWindow::Archive(data, deep);
178
179 // Stow the text
180 if (ret == B_OK)
181 ret = data->AddString("_text", fTextView->Text());
182
183 // Stow the alert type
184 if (ret == B_OK)
185 ret = data->AddInt32("_atype", fType);
186
187 // Stow the button width
188 if (ret == B_OK)
189 ret = data->AddInt32("_but_width", fButtonWidth);
190
191 // Stow the shortcut keys
192 if (fKeys[0] || fKeys[1] || fKeys[2]) {
193 // If we have any to save, we must save something for everyone so it
194 // doesn't get confusing on the unarchive.
195 if (ret == B_OK)
196 ret = data->AddInt8("_but_key", fKeys[0]);
197 if (ret == B_OK)
198 ret = data->AddInt8("_but_key", fKeys[1]);
199 if (ret == B_OK)
200 ret = data->AddInt8("_but_key", fKeys[2]);
201 }
202
203 return ret;
204 }
205
206
207 alert_type
Type() const208 BAlert::Type() const
209 {
210 return (alert_type)fType;
211 }
212
213
214 void
SetType(alert_type type)215 BAlert::SetType(alert_type type)
216 {
217 fType = type;
218 }
219
220
221 void
SetText(const char * text)222 BAlert::SetText(const char* text)
223 {
224 TextView()->SetText(text);
225 }
226
227
228 void
SetIcon(BBitmap * bitmap)229 BAlert::SetIcon(BBitmap* bitmap)
230 {
231 fIconView->SetBitmap(bitmap);
232 }
233
234
235 void
SetButtonSpacing(button_spacing spacing)236 BAlert::SetButtonSpacing(button_spacing spacing)
237 {
238 fButtonSpacing = spacing;
239 }
240
241
242 void
SetButtonWidth(button_width width)243 BAlert::SetButtonWidth(button_width width)
244 {
245 fButtonWidth = width;
246 }
247
248
249 void
SetShortcut(int32 index,char key)250 BAlert::SetShortcut(int32 index, char key)
251 {
252 if (index >= 0 && (size_t)index < fKeys.size())
253 fKeys[index] = key;
254 }
255
256
257 char
Shortcut(int32 index) const258 BAlert::Shortcut(int32 index) const
259 {
260 if (index >= 0 && (size_t)index < fKeys.size())
261 return fKeys[index];
262
263 return 0;
264 }
265
266
267 int32
Go()268 BAlert::Go()
269 {
270 fAlertSem = create_sem(0, "AlertSem");
271 if (fAlertSem < 0) {
272 Quit();
273 return -1;
274 }
275
276 // Get the originating window, if it exists
277 BWindow* window = dynamic_cast<BWindow*>(
278 BLooper::LooperForThread(find_thread(NULL)));
279
280 _Prepare();
281 Show();
282
283 if (window != NULL) {
284 status_t status;
285 for (;;) {
286 do {
287 status = acquire_sem_etc(fAlertSem, 1, B_RELATIVE_TIMEOUT,
288 kSemTimeOut);
289 // We've (probably) had our time slice taken away from us
290 } while (status == B_INTERRUPTED);
291
292 if (status == B_BAD_SEM_ID) {
293 // Semaphore was finally nuked in MessageReceived
294 break;
295 }
296 window->UpdateIfNeeded();
297 }
298 } else {
299 // No window to update, so just hang out until we're done.
300 while (acquire_sem(fAlertSem) == B_INTERRUPTED) {
301 }
302 }
303
304 // Have to cache the value since we delete on Quit()
305 int32 value = fAlertValue;
306 if (Lock())
307 Quit();
308
309 return value;
310 }
311
312
313 status_t
Go(BInvoker * invoker)314 BAlert::Go(BInvoker* invoker)
315 {
316 fInvoker = invoker;
317 _Prepare();
318 Show();
319 return B_OK;
320 }
321
322
323 void
MessageReceived(BMessage * msg)324 BAlert::MessageReceived(BMessage* msg)
325 {
326 if (msg->what != kAlertButtonMsg)
327 return BWindow::MessageReceived(msg);
328
329 int32 which;
330 if (msg->FindInt32("which", &which) == B_OK) {
331 if (fAlertSem < 0) {
332 // Semaphore hasn't been created; we're running asynchronous
333 if (fInvoker != NULL) {
334 BMessage* out = fInvoker->Message();
335 if (out && (out->ReplaceInt32("which", which) == B_OK
336 || out->AddInt32("which", which) == B_OK))
337 fInvoker->Invoke();
338 }
339 PostMessage(B_QUIT_REQUESTED);
340 } else {
341 // Created semaphore means were running synchronously
342 fAlertValue = which;
343
344 // TextAlertVar does release_sem() below, and then sets the
345 // member var. That doesn't make much sense to me, since we
346 // want to be able to clean up at some point. Better to just
347 // nuke the semaphore now; we don't need it any more and this
348 // lets synchronous Go() continue just as well.
349 delete_sem(fAlertSem);
350 fAlertSem = -1;
351 }
352 }
353 }
354
355
356 void
FrameResized(float newWidth,float newHeight)357 BAlert::FrameResized(float newWidth, float newHeight)
358 {
359 BWindow::FrameResized(newWidth, newHeight);
360 }
361
362
363 void
AddButton(const char * label,char key)364 BAlert::AddButton(const char* label, char key)
365 {
366 if (label == NULL || label[0] == '\0')
367 return;
368
369 BButton* button = _CreateButton(fButtons.size(), label);
370 fButtons.push_back(button);
371 fKeys.push_back(key);
372
373 SetDefaultButton(button);
374 fButtonLayout->AddView(button);
375 }
376
377
378 int32
CountButtons() const379 BAlert::CountButtons() const
380 {
381 return (int32)fButtons.size();
382 }
383
384
385 BButton*
ButtonAt(int32 index) const386 BAlert::ButtonAt(int32 index) const
387 {
388 if (index >= 0 && (size_t)index < fButtons.size())
389 return fButtons[index];
390
391 return NULL;
392 }
393
394
395 BTextView*
TextView() const396 BAlert::TextView() const
397 {
398 return fTextView;
399 }
400
401
402 BHandler*
ResolveSpecifier(BMessage * msg,int32 index,BMessage * specifier,int32 form,const char * property)403 BAlert::ResolveSpecifier(BMessage* msg, int32 index,
404 BMessage* specifier, int32 form, const char* property)
405 {
406 return BWindow::ResolveSpecifier(msg, index, specifier, form, property);
407 }
408
409
410 status_t
GetSupportedSuites(BMessage * data)411 BAlert::GetSupportedSuites(BMessage* data)
412 {
413 return BWindow::GetSupportedSuites(data);
414 }
415
416
417 void
DispatchMessage(BMessage * msg,BHandler * handler)418 BAlert::DispatchMessage(BMessage* msg, BHandler* handler)
419 {
420 BWindow::DispatchMessage(msg, handler);
421 }
422
423
424 void
Quit()425 BAlert::Quit()
426 {
427 BWindow::Quit();
428 }
429
430
431 bool
QuitRequested()432 BAlert::QuitRequested()
433 {
434 return BWindow::QuitRequested();
435 }
436
437
438 //! This method is deprecated, do not use - use BWindow::CenterIn() instead.
439 BPoint
AlertPosition(float width,float height)440 BAlert::AlertPosition(float width, float height)
441 {
442 BPoint result(100, 100);
443
444 BWindow* window =
445 dynamic_cast<BWindow*>(BLooper::LooperForThread(find_thread(NULL)));
446
447 BScreen screen(window);
448 BRect screenFrame(0, 0, 640, 480);
449 if (screen.IsValid())
450 screenFrame = screen.Frame();
451
452 // Horizontally, we're smack in the middle
453 result.x = screenFrame.left + (screenFrame.Width() / 2.0) - (width / 2.0);
454
455 // This is probably sooo wrong, but it looks right on 1024 x 768
456 result.y = screenFrame.top + (screenFrame.Height() / 4.0) - ceil(height / 3.0);
457
458 return result;
459 }
460
461
462 status_t
Perform(perform_code code,void * _data)463 BAlert::Perform(perform_code code, void* _data)
464 {
465 switch (code) {
466 case PERFORM_CODE_SET_LAYOUT:
467 perform_data_set_layout* data = (perform_data_set_layout*)_data;
468 BAlert::SetLayout(data->layout);
469 return B_OK;
470 }
471
472 return BWindow::Perform(code, _data);
473 }
474
475
_ReservedAlert1()476 void BAlert::_ReservedAlert1() {}
_ReservedAlert2()477 void BAlert::_ReservedAlert2() {}
_ReservedAlert3()478 void BAlert::_ReservedAlert3() {}
479
480
481 void
_Init(const char * text,const char * button1,const char * button2,const char * button3,button_width buttonWidth,button_spacing spacing,alert_type type)482 BAlert::_Init(const char* text, const char* button1, const char* button2,
483 const char* button3, button_width buttonWidth, button_spacing spacing,
484 alert_type type)
485 {
486 fInvoker = NULL;
487 fAlertSem = -1;
488 fAlertValue = -1;
489
490 fIconView = new TAlertView();
491
492 fTextView = new BTextView("_tv_");
493 fTextView->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
494 rgb_color textColor = ui_color(B_PANEL_TEXT_COLOR);
495 fTextView->SetFontAndColor(be_plain_font, B_FONT_ALL, &textColor);
496 fTextView->MakeEditable(false);
497 fTextView->MakeSelectable(false);
498 fTextView->SetWordWrap(true);
499 fTextView->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED));
500
501 fButtonLayout = new BGroupLayout(B_HORIZONTAL, B_USE_HALF_ITEM_SPACING);
502
503 SetType(type);
504 SetButtonWidth(buttonWidth);
505 SetButtonSpacing(spacing);
506 SetText(text);
507
508 BLayoutBuilder::Group<>(this, B_HORIZONTAL, 0)
509 .Add(fIconView)
510 .AddGroup(B_VERTICAL, B_USE_HALF_ITEM_SPACING)
511 .SetInsets(B_USE_HALF_ITEM_INSETS)
512 .Add(fTextView)
513 .AddGroup(B_HORIZONTAL, 0)
514 .AddGlue()
515 .Add(fButtonLayout);
516
517 AddButton(button1);
518 AddButton(button2);
519 AddButton(button3);
520
521 AddCommonFilter(new(std::nothrow) _BAlertFilter_(this));
522 }
523
524
525 BBitmap*
_CreateTypeIcon()526 BAlert::_CreateTypeIcon()
527 {
528 if (Type() == B_EMPTY_ALERT)
529 return NULL;
530
531 // The icons are in the app_server resources
532 BBitmap* icon = NULL;
533
534 // Which icon are we trying to load?
535 const char* iconName;
536 switch (fType) {
537 case B_INFO_ALERT:
538 iconName = "dialog-information";
539 break;
540 case B_IDEA_ALERT:
541 iconName = "dialog-idea";
542 break;
543 case B_WARNING_ALERT:
544 iconName = "dialog-warning";
545 break;
546 case B_STOP_ALERT:
547 iconName = "dialog-error";
548 break;
549
550 default:
551 // Alert type is either invalid or B_EMPTY_ALERT;
552 // either way, we're not going to load an icon
553 return NULL;
554 }
555
556 // Allocate the icon bitmap
557 icon = new(std::nothrow) BBitmap(BRect(BPoint(0, 0), be_control_look->ComposeIconSize(32)),
558 0, B_RGBA32);
559 if (icon == NULL || icon->InitCheck() < B_OK) {
560 FTRACE((stderr, "BAlert::_CreateTypeIcon() - No memory for bitmap\n"));
561 delete icon;
562 return NULL;
563 }
564
565 // Load the raw icon data
566 BIconUtils::GetSystemIcon(iconName, icon);
567
568 return icon;
569 }
570
571
572 BButton*
_CreateButton(int32 which,const char * label)573 BAlert::_CreateButton(int32 which, const char* label)
574 {
575 BMessage* message = new BMessage(kAlertButtonMsg);
576 if (message == NULL)
577 return NULL;
578
579 message->AddInt32("which", which);
580
581 char name[32];
582 snprintf(name, sizeof(name), "_b%" B_PRId32 "_", which);
583
584 return new(std::nothrow) BButton(name, label, message);
585 }
586
587
588 /*! Tweaks the layout according to the configuration.
589 */
590 void
_Prepare()591 BAlert::_Prepare()
592 {
593 // Must have at least one button
594 if (CountButtons() == 0)
595 debugger("BAlerts must have at least one button.");
596
597 float fontFactor = be_plain_font->Size() / 11.0f;
598
599 if (fIconView->Bitmap() == NULL)
600 fIconView->SetBitmap(_CreateTypeIcon());
601
602 if (fButtonWidth == B_WIDTH_AS_USUAL) {
603 float usualWidth = kButtonUsualWidth * fontFactor;
604
605 for (int32 index = 0; index < CountButtons(); index++) {
606 BButton* button = ButtonAt(index);
607 if (button->MinSize().width < usualWidth)
608 button->SetExplicitSize(BSize(usualWidth, B_SIZE_UNSET));
609 }
610 } else if (fButtonWidth == B_WIDTH_FROM_WIDEST) {
611 // Get width of widest label
612 float maxWidth = 0;
613 for (int32 index = 0; index < CountButtons(); index++) {
614 BButton* button = ButtonAt(index);
615 float width;
616 button->GetPreferredSize(&width, NULL);
617
618 if (width > maxWidth)
619 maxWidth = width;
620 }
621 for (int32 index = 0; index < CountButtons(); index++) {
622 BButton* button = ButtonAt(index);
623 button->SetExplicitSize(BSize(maxWidth, B_SIZE_UNSET));
624 }
625 }
626
627 if (fButtonSpacing == B_OFFSET_SPACING && CountButtons() > 1) {
628 // Insert some strut
629 fButtonLayout->AddItem(1, BSpaceLayoutItem::CreateHorizontalStrut(
630 kButtonOffsetSpacing * fontFactor));
631 }
632
633 // Position the alert so that it is centered vertically but offset a bit
634 // horizontally in the parent window's frame or, if unavailable, the
635 // screen frame.
636 float minWindowWidth = (fButtonSpacing == B_OFFSET_SPACING
637 ? kWindowOffsetMinWidth : kWindowMinWidth) * fontFactor;
638 GetLayout()->SetExplicitMinSize(BSize(minWindowWidth, B_SIZE_UNSET));
639
640 ResizeToPreferred();
641
642 // Return early if we've already been moved...
643 if (Frame().left != 0 && Frame().right != 0)
644 return;
645
646 // otherwise center ourselves on-top of parent window/screen
647 BWindow* parent = dynamic_cast<BWindow*>(BLooper::LooperForThread(
648 find_thread(NULL)));
649 const BRect frame = parent != NULL ? parent->Frame()
650 : BScreen(this).Frame();
651
652 MoveTo(static_cast<BWindow*>(this)->AlertPosition(frame));
653 // Hidden by BAlert::AlertPosition()
654 }
655
656
657 // #pragma mark - TAlertView
658
659
TAlertView()660 TAlertView::TAlertView()
661 :
662 BView("TAlertView", B_WILL_DRAW),
663 fIconBitmap(NULL)
664 {
665 SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
666 }
667
668
TAlertView(BMessage * archive)669 TAlertView::TAlertView(BMessage* archive)
670 :
671 BView(archive),
672 fIconBitmap(NULL)
673 {
674 }
675
676
~TAlertView()677 TAlertView::~TAlertView()
678 {
679 delete fIconBitmap;
680 }
681
682
683 TAlertView*
Instantiate(BMessage * archive)684 TAlertView::Instantiate(BMessage* archive)
685 {
686 if (!validate_instantiation(archive, "TAlertView"))
687 return NULL;
688
689 return new(std::nothrow) TAlertView(archive);
690 }
691
692
693 status_t
Archive(BMessage * archive,bool deep) const694 TAlertView::Archive(BMessage* archive, bool deep) const
695 {
696 return BView::Archive(archive, deep);
697 }
698
699
700 void
SetBitmap(BBitmap * icon)701 TAlertView::SetBitmap(BBitmap* icon)
702 {
703 if (icon == NULL && fIconBitmap == NULL)
704 return;
705
706 ASSERT(icon != fIconBitmap);
707
708 BBitmap* oldBitmap = fIconBitmap;
709 fIconBitmap = icon;
710 Invalidate();
711
712 if (oldBitmap == NULL || icon == NULL || oldBitmap->Bounds() != icon->Bounds())
713 InvalidateLayout();
714
715 delete oldBitmap;
716 }
717
718
719 void
GetPreferredSize(float * _width,float * _height)720 TAlertView::GetPreferredSize(float* _width, float* _height)
721 {
722 if (_width != NULL) {
723 *_width = be_control_look->DefaultLabelSpacing() * 3;
724 if (fIconBitmap != NULL)
725 *_width += fIconBitmap->Bounds().Width();
726 else
727 *_width += be_control_look->ComposeIconSize(B_LARGE_ICON).Width();
728 }
729
730 if (_height != NULL) {
731 *_height = be_control_look->DefaultLabelSpacing();
732 if (fIconBitmap != NULL)
733 *_height += fIconBitmap->Bounds().Height();
734 else
735 *_height += be_control_look->ComposeIconSize(B_LARGE_ICON).Height();
736 }
737 }
738
739
740 BSize
MaxSize()741 TAlertView::MaxSize()
742 {
743 return BSize(MinSize().width, B_SIZE_UNLIMITED);
744 }
745
746
747 void
Draw(BRect updateRect)748 TAlertView::Draw(BRect updateRect)
749 {
750 // Here's the fun stuff
751 BRect stripeRect = Bounds();
752 stripeRect.right = kIconStripeWidthFactor * be_control_look->DefaultLabelSpacing();
753 SetHighColor(tint_color(ViewColor(), B_DARKEN_1_TINT));
754 FillRect(stripeRect);
755
756 if (fIconBitmap == NULL)
757 return;
758
759 SetDrawingMode(B_OP_ALPHA);
760 SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_OVERLAY);
761 DrawBitmapAsync(fIconBitmap, BPoint(be_control_look->DefaultLabelSpacing() * 3,
762 be_control_look->DefaultLabelSpacing()));
763 }
764
765
766 // #pragma mark - _BAlertFilter_
767
768
_BAlertFilter_(BAlert * alert)769 _BAlertFilter_::_BAlertFilter_(BAlert* alert)
770 : BMessageFilter(B_KEY_DOWN),
771 fAlert(alert)
772 {
773 }
774
775
~_BAlertFilter_()776 _BAlertFilter_::~_BAlertFilter_()
777 {
778 }
779
780
781 filter_result
Filter(BMessage * msg,BHandler ** target)782 _BAlertFilter_::Filter(BMessage* msg, BHandler** target)
783 {
784 if (msg->what == B_KEY_DOWN) {
785 char byte;
786 if (msg->FindInt8("byte", (int8*)&byte) == B_OK) {
787 for (int i = 0; i < fAlert->CountButtons(); ++i) {
788 if (byte == fAlert->Shortcut(i) && fAlert->ButtonAt(i)) {
789 char space = ' ';
790 fAlert->ButtonAt(i)->KeyDown(&space, 1);
791
792 return B_SKIP_MESSAGE;
793 }
794 }
795 }
796 }
797
798 return B_DISPATCH_MESSAGE;
799 }
800