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); 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 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 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 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 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 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* 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 175 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 208 BAlert::Type() const 209 { 210 return (alert_type)fType; 211 } 212 213 214 void 215 BAlert::SetType(alert_type type) 216 { 217 fType = type; 218 } 219 220 221 void 222 BAlert::SetText(const char* text) 223 { 224 TextView()->SetText(text); 225 } 226 227 228 void 229 BAlert::SetIcon(BBitmap* bitmap) 230 { 231 fIconView->SetBitmap(bitmap); 232 } 233 234 235 void 236 BAlert::SetButtonSpacing(button_spacing spacing) 237 { 238 fButtonSpacing = spacing; 239 } 240 241 242 void 243 BAlert::SetButtonWidth(button_width width) 244 { 245 fButtonWidth = width; 246 } 247 248 249 void 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 258 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 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 314 BAlert::Go(BInvoker* invoker) 315 { 316 fInvoker = invoker; 317 _Prepare(); 318 Show(); 319 return B_OK; 320 } 321 322 323 void 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 357 BAlert::FrameResized(float newWidth, float newHeight) 358 { 359 BWindow::FrameResized(newWidth, newHeight); 360 } 361 362 363 void 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 379 BAlert::CountButtons() const 380 { 381 return (int32)fButtons.size(); 382 } 383 384 385 BButton* 386 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* 396 BAlert::TextView() const 397 { 398 return fTextView; 399 } 400 401 402 BHandler* 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 411 BAlert::GetSupportedSuites(BMessage* data) 412 { 413 return BWindow::GetSupportedSuites(data); 414 } 415 416 417 void 418 BAlert::DispatchMessage(BMessage* msg, BHandler* handler) 419 { 420 BWindow::DispatchMessage(msg, handler); 421 } 422 423 424 void 425 BAlert::Quit() 426 { 427 BWindow::Quit(); 428 } 429 430 431 bool 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 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 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 476 void BAlert::_ReservedAlert1() {} 477 void BAlert::_ReservedAlert2() {} 478 void BAlert::_ReservedAlert3() {} 479 480 481 void 482 BAlert::_Init(const char* text, const char* button0, const char* button1, 483 const char* button2, 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(button0); 518 AddButton(button1); 519 AddButton(button2); 520 521 AddCommonFilter(new(std::nothrow) _BAlertFilter_(this)); 522 } 523 524 525 BBitmap* 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 BPath path; 534 status_t status = find_directory(B_BEOS_SERVERS_DIRECTORY, &path); 535 if (status != B_OK) { 536 FTRACE((stderr, "BAlert::_CreateTypeIcon() - find_directory " 537 "failed: %s\n", strerror(status))); 538 return NULL; 539 } 540 541 path.Append("app_server"); 542 BFile file; 543 status = file.SetTo(path.Path(), B_READ_ONLY); 544 if (status != B_OK) { 545 FTRACE((stderr, "BAlert::_CreateTypeIcon() - BFile init failed: %s\n", 546 strerror(status))); 547 return NULL; 548 } 549 550 BResources resources; 551 status = resources.SetTo(&file); 552 if (status != B_OK) { 553 FTRACE((stderr, "BAlert::_CreateTypeIcon() - BResources init " 554 "failed: %s\n", strerror(status))); 555 return NULL; 556 } 557 558 // Which icon are we trying to load? 559 const char* iconName; 560 switch (fType) { 561 case B_INFO_ALERT: 562 iconName = "info"; 563 break; 564 case B_IDEA_ALERT: 565 iconName = "idea"; 566 break; 567 case B_WARNING_ALERT: 568 iconName = "warn"; 569 break; 570 case B_STOP_ALERT: 571 iconName = "stop"; 572 break; 573 574 default: 575 // Alert type is either invalid or B_EMPTY_ALERT; 576 // either way, we're not going to load an icon 577 return NULL; 578 } 579 580 // Allocate the icon bitmap 581 icon = new(std::nothrow) BBitmap(BRect(BPoint(0, 0), be_control_look->ComposeIconSize(32)), 582 0, B_RGBA32); 583 if (icon == NULL || icon->InitCheck() < B_OK) { 584 FTRACE((stderr, "BAlert::_CreateTypeIcon() - No memory for bitmap\n")); 585 delete icon; 586 return NULL; 587 } 588 589 // Load the raw icon data 590 size_t size = 0; 591 const uint8* rawIcon; 592 593 // Try to load vector icon 594 rawIcon = (const uint8*)resources.LoadResource(B_VECTOR_ICON_TYPE, 595 iconName, &size); 596 if (rawIcon != NULL 597 && BIconUtils::GetVectorIcon(rawIcon, size, icon) == B_OK) { 598 return icon; 599 } 600 601 // Fall back to bitmap icon 602 rawIcon = (const uint8*)resources.LoadResource(B_LARGE_ICON_TYPE, 603 iconName, &size); 604 if (rawIcon == NULL) { 605 FTRACE((stderr, "BAlert::_CreateTypeIcon() - Icon resource not found\n")); 606 delete icon; 607 return NULL; 608 } 609 610 // Handle color space conversion 611 if (icon->ColorSpace() != B_CMAP8) { 612 BIconUtils::ConvertFromCMAP8(rawIcon, B_LARGE_ICON, B_LARGE_ICON, 613 B_LARGE_ICON, icon); 614 } 615 616 return icon; 617 } 618 619 620 BButton* 621 BAlert::_CreateButton(int32 which, const char* label) 622 { 623 BMessage* message = new BMessage(kAlertButtonMsg); 624 if (message == NULL) 625 return NULL; 626 627 message->AddInt32("which", which); 628 629 char name[32]; 630 snprintf(name, sizeof(name), "_b%" B_PRId32 "_", which); 631 632 return new(std::nothrow) BButton(name, label, message); 633 } 634 635 636 /*! Tweaks the layout according to the configuration. 637 */ 638 void 639 BAlert::_Prepare() 640 { 641 // Must have at least one button 642 if (CountButtons() == 0) 643 debugger("BAlerts must have at least one button."); 644 645 float fontFactor = be_plain_font->Size() / 11.0f; 646 647 if (fIconView->Bitmap() == NULL) 648 fIconView->SetBitmap(_CreateTypeIcon()); 649 650 if (fButtonWidth == B_WIDTH_AS_USUAL) { 651 float usualWidth = kButtonUsualWidth * fontFactor; 652 653 for (int32 index = 0; index < CountButtons(); index++) { 654 BButton* button = ButtonAt(index); 655 if (button->MinSize().width < usualWidth) 656 button->SetExplicitSize(BSize(usualWidth, B_SIZE_UNSET)); 657 } 658 } else if (fButtonWidth == B_WIDTH_FROM_WIDEST) { 659 // Get width of widest label 660 float maxWidth = 0; 661 for (int32 index = 0; index < CountButtons(); index++) { 662 BButton* button = ButtonAt(index); 663 float width; 664 button->GetPreferredSize(&width, NULL); 665 666 if (width > maxWidth) 667 maxWidth = width; 668 } 669 for (int32 index = 0; index < CountButtons(); index++) { 670 BButton* button = ButtonAt(index); 671 button->SetExplicitSize(BSize(maxWidth, B_SIZE_UNSET)); 672 } 673 } 674 675 if (fButtonSpacing == B_OFFSET_SPACING && CountButtons() > 1) { 676 // Insert some strut 677 fButtonLayout->AddItem(1, BSpaceLayoutItem::CreateHorizontalStrut( 678 kButtonOffsetSpacing * fontFactor)); 679 } 680 681 // Position the alert so that it is centered vertically but offset a bit 682 // horizontally in the parent window's frame or, if unavailable, the 683 // screen frame. 684 float minWindowWidth = (fButtonSpacing == B_OFFSET_SPACING 685 ? kWindowOffsetMinWidth : kWindowMinWidth) * fontFactor; 686 GetLayout()->SetExplicitMinSize(BSize(minWindowWidth, B_SIZE_UNSET)); 687 688 ResizeToPreferred(); 689 690 // Return early if we've already been moved... 691 if (Frame().left != 0 && Frame().right != 0) 692 return; 693 694 // otherwise center ourselves on-top of parent window/screen 695 BWindow* parent = dynamic_cast<BWindow*>(BLooper::LooperForThread( 696 find_thread(NULL))); 697 const BRect frame = parent != NULL ? parent->Frame() 698 : BScreen(this).Frame(); 699 700 MoveTo(static_cast<BWindow*>(this)->AlertPosition(frame)); 701 // Hidden by BAlert::AlertPosition() 702 } 703 704 705 // #pragma mark - TAlertView 706 707 708 TAlertView::TAlertView() 709 : 710 BView("TAlertView", B_WILL_DRAW), 711 fIconBitmap(NULL) 712 { 713 SetViewUIColor(B_PANEL_BACKGROUND_COLOR); 714 } 715 716 717 TAlertView::TAlertView(BMessage* archive) 718 : 719 BView(archive), 720 fIconBitmap(NULL) 721 { 722 } 723 724 725 TAlertView::~TAlertView() 726 { 727 delete fIconBitmap; 728 } 729 730 731 TAlertView* 732 TAlertView::Instantiate(BMessage* archive) 733 { 734 if (!validate_instantiation(archive, "TAlertView")) 735 return NULL; 736 737 return new(std::nothrow) TAlertView(archive); 738 } 739 740 741 status_t 742 TAlertView::Archive(BMessage* archive, bool deep) const 743 { 744 return BView::Archive(archive, deep); 745 } 746 747 748 void 749 TAlertView::SetBitmap(BBitmap* icon) 750 { 751 if (icon == NULL && fIconBitmap == NULL) 752 return; 753 754 ASSERT(icon != fIconBitmap); 755 756 BBitmap* oldBitmap = fIconBitmap; 757 fIconBitmap = icon; 758 Invalidate(); 759 760 if (oldBitmap == NULL || icon == NULL || oldBitmap->Bounds() != icon->Bounds()) 761 InvalidateLayout(); 762 763 delete oldBitmap; 764 } 765 766 767 void 768 TAlertView::GetPreferredSize(float* _width, float* _height) 769 { 770 if (_width != NULL) { 771 *_width = be_control_look->DefaultLabelSpacing() * 3; 772 if (fIconBitmap != NULL) 773 *_width += fIconBitmap->Bounds().Width(); 774 else 775 *_width += be_control_look->ComposeIconSize(B_LARGE_ICON).Width(); 776 } 777 778 if (_height != NULL) { 779 *_height = be_control_look->DefaultLabelSpacing(); 780 if (fIconBitmap != NULL) 781 *_height += fIconBitmap->Bounds().Height(); 782 else 783 *_height += be_control_look->ComposeIconSize(B_LARGE_ICON).Height(); 784 } 785 } 786 787 788 BSize 789 TAlertView::MaxSize() 790 { 791 return BSize(MinSize().width, B_SIZE_UNLIMITED); 792 } 793 794 795 void 796 TAlertView::Draw(BRect updateRect) 797 { 798 // Here's the fun stuff 799 BRect stripeRect = Bounds(); 800 stripeRect.right = kIconStripeWidthFactor * be_control_look->DefaultLabelSpacing(); 801 SetHighColor(tint_color(ViewColor(), B_DARKEN_1_TINT)); 802 FillRect(stripeRect); 803 804 if (fIconBitmap == NULL) 805 return; 806 807 SetDrawingMode(B_OP_ALPHA); 808 SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_OVERLAY); 809 DrawBitmapAsync(fIconBitmap, BPoint(be_control_look->DefaultLabelSpacing() * 3, 810 be_control_look->DefaultLabelSpacing())); 811 } 812 813 814 // #pragma mark - _BAlertFilter_ 815 816 817 _BAlertFilter_::_BAlertFilter_(BAlert* alert) 818 : BMessageFilter(B_KEY_DOWN), 819 fAlert(alert) 820 { 821 } 822 823 824 _BAlertFilter_::~_BAlertFilter_() 825 { 826 } 827 828 829 filter_result 830 _BAlertFilter_::Filter(BMessage* msg, BHandler** target) 831 { 832 if (msg->what == B_KEY_DOWN) { 833 char byte; 834 if (msg->FindInt8("byte", (int8*)&byte) == B_OK) { 835 for (int i = 0; i < fAlert->CountButtons(); ++i) { 836 if (byte == fAlert->Shortcut(i) && fAlert->ButtonAt(i)) { 837 char space = ' '; 838 fAlert->ButtonAt(i)->KeyDown(&space, 1); 839 840 return B_SKIP_MESSAGE; 841 } 842 } 843 } 844 } 845 846 return B_DISPATCH_MESSAGE; 847 } 848