1 //------------------------------------------------------------------------------ 2 // Copyright (c) 2001-2002, OpenBeOS 3 // 4 // Permission is hereby granted, free of charge, to any person obtaining a 5 // copy of this software and associated documentation files (the "Software"), 6 // to deal in the Software without restriction, including without limitation 7 // the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 // and/or sell copies of the Software, and to permit persons to whom the 9 // Software is furnished to do so, subject to the following conditions: 10 // 11 // The above copyright notice and this permission notice shall be included in 12 // all copies or substantial portions of the Software. 13 // 14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 // DEALINGS IN THE SOFTWARE. 21 // 22 // File Name: Alert.cpp 23 // Author: Erik Jaesler (erik@cgsoftware.com) 24 // Description: BAlert displays a modal alert window. 25 //------------------------------------------------------------------------------ 26 27 // Standard Includes ----------------------------------------------------------- 28 #include <string.h> 29 30 // TODO: remove 31 #include <stdio.h> 32 33 // System Includes ------------------------------------------------------------- 34 #include <Invoker.h> 35 #include <Looper.h> 36 #include <Message.h> 37 #include <MessageFilter.h> 38 39 #include <Alert.h> 40 #include <Bitmap.h> 41 #include <Button.h> 42 #include <Screen.h> 43 #include <TextView.h> 44 #include <View.h> 45 46 #include <File.h> 47 #include <FindDirectory.h> 48 #include <Path.h> 49 #include <Resources.h> 50 #include <Beep.h> 51 52 #include <Autolock.h> 53 54 55 // Default size of the Alert window. 56 #define DEFAULT_RECT BRect(0, 0, 310, 75) 57 #define max(LHS, RHS) ((LHS) > (RHS) ? (LHS) : (RHS)) 58 59 // Globals --------------------------------------------------------------------- 60 static const unsigned int kAlertButtonMsg = 'ALTB'; 61 static const int kSemTimeOut = 50000; 62 63 static const int kButtonBottomOffset = 9; 64 static const int kDefButtonBottomOffset = 6; 65 static const int kButtonRightOffset = 6; 66 static const int kButtonSpaceOffset = 6; 67 static const int kButtonOffsetSpaceOffset = 26; 68 static const int kButtonMinOffsetSpaceOffset = kButtonOffsetSpaceOffset / 2; 69 static const int kButtonLeftOffset = 62; 70 static const int kButtonUsualWidth = 75; 71 72 static const int kWindowIconOffset = 27; 73 static const int kWindowMinWidth = 310; 74 static const int kWindowMinOffset = 12; 75 static const int kWindowOffsetMinWidth = 335; 76 77 static const int kIconStripeWidth = 30; 78 79 static const int kTextLeftOffset = 10; 80 static const int kTextIconOffset = kWindowIconOffset + kIconStripeWidth - 2; 81 static const int kTextTopOffset = 6; 82 static const int kTextRightOffset = 10; 83 static const int kTextBottomOffset = 45; 84 85 //------------------------------------------------------------------------------ 86 class TAlertView : public BView { 87 public: 88 TAlertView(BRect frame); 89 TAlertView(BMessage* archive); 90 ~TAlertView(); 91 92 static TAlertView* Instantiate(BMessage* archive); 93 status_t Archive(BMessage* archive, bool deep = true); 94 95 virtual void Draw(BRect updateRect); 96 97 // These functions (or something analogous) are missing from libbe.so's 98 // dump. I can only assume that the bitmap is a public var in the 99 // original implementation -- or BAlert is a friend of TAlertView. 100 // Neither one is necessary, since I can just add these. 101 void SetBitmap(BBitmap* Icon) { fIconBitmap = Icon; } 102 BBitmap* Bitmap() { return fIconBitmap; } 103 104 private: 105 BBitmap* fIconBitmap; 106 }; 107 108 //------------------------------------------------------------------------------ 109 // I'm making a guess based on the name and TextEntryAlert's implementation that 110 // this is a BMessageFilter. I'm not sure, but I think I actually prefer how 111 // TextEntryAlert does it, but there are clearly no message filtering functions 112 // on BAlert so here we go. 113 class _BAlertFilter_ : public BMessageFilter { 114 public: 115 _BAlertFilter_(BAlert* Alert); 116 ~_BAlertFilter_(); 117 118 virtual filter_result Filter(BMessage* msg, BHandler** target); 119 120 private: 121 BAlert* fAlert; 122 }; 123 124 125 static float 126 width_from_label(BButton *button) 127 { 128 // BButton::GetPreferredSize() does not return the minimum width 129 // required to fit the label. Thus, the width is computed here. 130 return button->StringWidth(button->Label()) + 20.0f; 131 } 132 133 134 // #pragma mark - BAlert 135 136 137 BAlert::BAlert(const char *title, const char *text, const char *button1, 138 const char *button2, const char *button3, button_width width, 139 alert_type type) 140 : BWindow(DEFAULT_RECT, title, B_MODAL_WINDOW, 141 B_NOT_CLOSABLE | B_NOT_RESIZABLE) 142 { 143 InitObject(text, button1, button2, button3, width, B_EVEN_SPACING, type); 144 } 145 146 147 BAlert::BAlert(const char *title, const char *text, const char *button1, 148 const char *button2, const char *button3, button_width width, 149 button_spacing spacing, alert_type type) 150 : BWindow(DEFAULT_RECT, title, B_MODAL_WINDOW, 151 B_NOT_CLOSABLE | B_NOT_RESIZABLE) 152 { 153 InitObject(text, button1, button2, button3, width, spacing, type); 154 } 155 156 157 BAlert::~BAlert() 158 { 159 // Probably not necessary, but it makes me feel better. 160 if (fAlertSem >= B_OK) 161 delete_sem(fAlertSem); 162 } 163 164 165 BAlert::BAlert(BMessage* data) 166 : BWindow(data) 167 { 168 fInvoker = NULL; 169 fAlertSem = -1; 170 fAlertVal = -1; 171 172 fTextView = (BTextView*)FindView("_tv_"); 173 174 fButtons[0] = (BButton*)FindView("_b0_"); 175 fButtons[1] = (BButton*)FindView("_b1_"); 176 fButtons[2] = (BButton*)FindView("_b2_"); 177 178 if (fButtons[2]) 179 SetDefaultButton(fButtons[2]); 180 else if (fButtons[1]) 181 SetDefaultButton(fButtons[1]); 182 else if (fButtons[0]) 183 SetDefaultButton(fButtons[0]); 184 185 TAlertView* master = (TAlertView*)FindView("_master_"); 186 if (master) 187 master->SetBitmap(InitIcon()); 188 189 // Get keys 190 char key; 191 for (int32 i = 0; i < 3; ++i) { 192 if (data->FindInt8("_but_key", i, (int8*)&key) == B_OK) 193 fKeys[i] = key; 194 } 195 196 int32 temp; 197 // Get alert type 198 if (data->FindInt32("_atype", &temp) == B_OK) 199 fMsgType = (alert_type)temp; 200 201 // Get button width 202 if (data->FindInt32("_but_width", &temp) == B_OK) 203 fButtonWidth = (button_width)temp; 204 205 AddCommonFilter(new _BAlertFilter_(this)); 206 } 207 208 209 BArchivable* 210 BAlert::Instantiate(BMessage* data) 211 { 212 if (!validate_instantiation(data, "BAlert")) 213 return NULL; 214 215 return new BAlert(data); 216 } 217 218 219 status_t 220 BAlert::Archive(BMessage* data, bool deep) const 221 { 222 BWindow::Archive(data, deep); 223 224 // Stow the text 225 data->AddString("_text", fTextView->Text()); 226 227 // Stow the alert type 228 data->AddInt32("_atype", fMsgType); 229 230 // Stow the button width 231 data->AddInt32("_but_width", fButtonWidth); 232 233 // Stow the shortcut keys 234 if (fKeys[0] || fKeys[1] || fKeys[2]) { 235 // If we have any to save, we must save something for everyone so it 236 // doesn't get confusing on the unarchive. 237 data->AddInt8("_but_key", fKeys[0]); 238 data->AddInt8("_but_key", fKeys[1]); 239 data->AddInt8("_but_key", fKeys[2]); 240 } 241 242 return B_OK; 243 } 244 245 246 void 247 BAlert::SetShortcut(int32 index, char key) 248 { 249 if (index >= 0 && index < 3) 250 fKeys[index] = key; 251 } 252 253 254 char 255 BAlert::Shortcut(int32 index) const 256 { 257 if (index >= 0 && index < 3) 258 return fKeys[index]; 259 260 return 0; 261 } 262 263 264 int32 265 BAlert::Go() 266 { 267 fAlertSem = create_sem(0, "AlertSem"); 268 if (fAlertSem < B_OK) { 269 Quit(); 270 return -1; 271 } 272 273 // Get the originating window, if it exists 274 BWindow* window = 275 dynamic_cast<BWindow*>(BLooper::LooperForThread(find_thread(NULL))); 276 277 Show(); 278 279 // Heavily modified from TextEntryAlert code; the original didn't let the 280 // blocked window ever draw. 281 if (window) { 282 status_t err; 283 for (;;) { 284 do { 285 err = acquire_sem_etc(fAlertSem, 1, B_RELATIVE_TIMEOUT, 286 kSemTimeOut); 287 // We've (probably) had our time slice taken away from us 288 } while (err == B_INTERRUPTED); 289 290 if (err == B_BAD_SEM_ID) { 291 // Semaphore was finally nuked in MessageReceived 292 break; 293 } 294 window->UpdateIfNeeded(); 295 } 296 } else { 297 // No window to update, so just hang out until we're done. 298 while (acquire_sem(fAlertSem) == B_INTERRUPTED) { 299 } 300 } 301 302 // Have to cache the value since we delete on Quit() 303 int32 value = fAlertVal; 304 if (Lock()) 305 Quit(); 306 307 return value; 308 } 309 310 311 status_t 312 BAlert::Go(BInvoker* invoker) 313 { 314 // TODO: Add sound? 315 // It would be cool if we triggered a system sound depending on the type of 316 // alert. 317 fInvoker = invoker; 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 < B_OK) { 332 // Semaphore hasn't been created; we're running asynchronous 333 if (fInvoker) { 334 BMessage* out = fInvoker->Message(); 335 if (out && (out->AddInt32("which", which) == B_OK 336 || out->ReplaceInt32("which", which) == B_OK)) 337 fInvoker->Invoke(); 338 } 339 } else { 340 // Created semaphore means were running synchronously 341 fAlertVal = which; 342 343 // TextAlertVar does release_sem() below, and then sets the 344 // member var. That doesn't make much sense to me, since we 345 // want to be able to clean up at some point. Better to just 346 // nuke the semaphore now; we don't need it any more and this 347 // lets synchronous Go() continue just as well. 348 delete_sem(fAlertSem); 349 fAlertSem = -1; 350 } 351 } 352 } 353 354 355 void 356 BAlert::FrameResized(float newWidth, float newHeight) 357 { 358 BWindow::FrameResized(newWidth, newHeight); 359 } 360 361 362 BButton* 363 BAlert::ButtonAt(int32 index) const 364 { 365 if (index >= 0 && index < 3) 366 return fButtons[index]; 367 368 return NULL; 369 } 370 371 372 BTextView* 373 BAlert::TextView() const 374 { 375 return fTextView; 376 } 377 378 379 BHandler* 380 BAlert::ResolveSpecifier(BMessage* msg, int32 index, 381 BMessage* specifier, int32 form, const char* property) 382 { 383 return BWindow::ResolveSpecifier(msg, index, specifier, form, property); 384 } 385 386 387 status_t 388 BAlert::GetSupportedSuites(BMessage* data) 389 { 390 return BWindow::GetSupportedSuites(data); 391 } 392 393 394 void 395 BAlert::DispatchMessage(BMessage* msg, BHandler* handler) 396 { 397 BWindow::DispatchMessage(msg, handler); 398 } 399 400 401 void 402 BAlert::Quit() 403 { 404 BWindow::Quit(); 405 } 406 407 408 bool 409 BAlert::QuitRequested() 410 { 411 return BWindow::QuitRequested(); 412 } 413 414 415 BPoint 416 BAlert::AlertPosition(float width, float height) 417 { 418 BPoint result(100, 100); 419 420 BWindow* window = 421 dynamic_cast<BWindow*>(BLooper::LooperForThread(find_thread(NULL))); 422 423 BScreen screen(window); 424 BRect screenRect(0, 0, 640, 480); 425 if (screen.IsValid()) 426 screenRect = screen.Frame(); 427 428 // Horizontally, we're smack in the middle 429 result.x = (screenRect.Width() / 2.0) - (width / 2.0); 430 431 // This is probably sooo wrong, but it looks right on 1024 x 768 432 result.y = (screenRect.Height() / 4.0) - ceil(height / 3.0); 433 434 return result; 435 } 436 437 438 status_t 439 BAlert::Perform(perform_code d, void* arg) 440 { 441 return BWindow::Perform(d, arg); 442 } 443 444 445 void BAlert::_ReservedAlert1() {} 446 void BAlert::_ReservedAlert2() {} 447 void BAlert::_ReservedAlert3() {} 448 449 450 void 451 BAlert::InitObject(const char* text, const char* button0, const char* button1, 452 const char* button2, button_width width, button_spacing spacing, 453 alert_type type) 454 { 455 fInvoker = NULL; 456 fAlertSem = -1; 457 fAlertVal = -1; 458 fButtons[0] = fButtons[1] = fButtons[2] = NULL; 459 fTextView = NULL; 460 fKeys[0] = fKeys[1] = fKeys[2] = 0; 461 fMsgType = type; 462 fButtonWidth = width; 463 464 // Set up the "_master_" view 465 TAlertView* masterView = new TAlertView(Bounds()); 466 AddChild(masterView); 467 masterView->SetBitmap(InitIcon()); 468 469 // Must have at least one button 470 if (button0 == NULL) { 471 debugger("BAlert's must have at least one button."); 472 button0 = ""; 473 } 474 475 BMessage ProtoMsg(kAlertButtonMsg); 476 ProtoMsg.AddInt32("which", 0); 477 // Set up the buttons 478 int buttonCount = 0; 479 fButtons[buttonCount] = new BButton(BRect(0, 0, 0, 0), "_b0_", button0, 480 new BMessage(ProtoMsg), B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM); 481 masterView->AddChild(fButtons[buttonCount]); 482 ++buttonCount; 483 484 if (button1) { 485 ProtoMsg.ReplaceInt32("which", 1); 486 fButtons[buttonCount] = new BButton(BRect(0, 0, 0, 0), "_b1_", button1, 487 new BMessage(ProtoMsg), B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM); 488 489 masterView->AddChild(fButtons[buttonCount]); 490 ++buttonCount; 491 } 492 if (button2) { 493 ProtoMsg.ReplaceInt32("which", 2); 494 fButtons[buttonCount] = new BButton(BRect(0, 0, 0, 0), "_b2_", button2, 495 new BMessage(ProtoMsg), B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM); 496 497 masterView->AddChild(fButtons[buttonCount]); 498 ++buttonCount; 499 } 500 501 SetDefaultButton(fButtons[buttonCount - 1]); 502 503 // Find the widest button only if the widest value needs to be known. 504 float maxWidth = 0; 505 if (fButtonWidth == B_WIDTH_FROM_WIDEST) { 506 for (int i = 0; i < buttonCount; ++i) { 507 float temp = width_from_label(fButtons[i]); 508 maxWidth = max(maxWidth, temp); 509 } 510 } 511 512 for (int i = buttonCount - 1; i >= 0; --i) { 513 // Determine the button's size 514 float buttonWidth = 0, buttonHeight = 0; 515 fButtons[i]->GetPreferredSize(&buttonWidth, &buttonHeight); 516 if (fButtonWidth == B_WIDTH_FROM_WIDEST) 517 buttonWidth = maxWidth; 518 else if (fButtonWidth == B_WIDTH_FROM_LABEL) 519 buttonWidth = width_from_label(fButtons[i]); 520 else // B_WIDTH_AS_USUAL 521 buttonWidth = max(buttonWidth, kButtonUsualWidth); 522 fButtons[i]->ResizeTo(buttonWidth, buttonHeight); 523 524 // Determine the button's placement 525 float buttonX, buttonY; 526 buttonY = Bounds().bottom - buttonHeight; 527 if (i == buttonCount - 1) { 528 // The right-most button 529 buttonX = Bounds().right - fButtons[i]->Frame().Width() - 530 kButtonRightOffset; 531 buttonY -= kDefButtonBottomOffset; 532 } else { 533 buttonX = fButtons[i + 1]->Frame().left - 534 fButtons[i]->Frame().Width() - kButtonSpaceOffset; 535 buttonY -= kButtonBottomOffset; 536 if (i == 0) { 537 if (spacing == B_OFFSET_SPACING) { 538 if (buttonCount == 3) 539 buttonX -= kButtonOffsetSpaceOffset; 540 else { 541 // If there are two buttons, the left wall of 542 // button0 needs to line up with the left wall 543 // of the TextView. 544 buttonX = (masterView->Bitmap()) ? 545 kTextIconOffset : kTextLeftOffset; 546 if (fButtons[i + 1]->Frame().left - 547 (buttonX + fButtons[i]->Frame().Width()) < 548 kButtonMinOffsetSpaceOffset) { 549 // Recompute buttonX using min offset space 550 // if using the current buttonX would not 551 // provide enough space or cause an overlap. 552 buttonX = fButtons[i + 1]->Frame().left - 553 fButtons[i]->Frame().Width() - 554 kButtonMinOffsetSpaceOffset; 555 } 556 } 557 } else if (buttonCount == 3) 558 buttonX -= 3; 559 } 560 } 561 fButtons[i]->MoveTo(buttonX, buttonY); 562 } // for (int i = buttonCount - 1; i >= 0; --i) 563 564 // Adjust the window's width, if necessary 565 float totalWidth = kButtonRightOffset; 566 totalWidth += fButtons[buttonCount - 1]->Frame().right - 567 fButtons[0]->Frame().left; 568 if (masterView->Bitmap()) 569 totalWidth += kIconStripeWidth + kWindowIconOffset; 570 else 571 totalWidth += kWindowMinOffset; 572 573 if (spacing == B_OFFSET_SPACING) { 574 totalWidth -= 2; 575 if (buttonCount == 3) 576 totalWidth = max(kWindowOffsetMinWidth, totalWidth); 577 else 578 totalWidth = max(kWindowMinWidth, totalWidth); 579 } else { 580 totalWidth += 5; 581 totalWidth = max(kWindowMinWidth, totalWidth); 582 } 583 ResizeTo(totalWidth, Bounds().Height()); 584 585 // Set up the text view 586 BRect TextViewRect(kTextLeftOffset, kTextTopOffset, 587 Bounds().right - kTextRightOffset, 588 Bounds().bottom - kTextBottomOffset); 589 if (masterView->Bitmap()) 590 TextViewRect.left = kTextIconOffset; 591 592 fTextView = new BTextView(TextViewRect, "_tv_", TextViewRect, 593 B_FOLLOW_LEFT | B_FOLLOW_TOP, B_WILL_DRAW); 594 masterView->AddChild(fTextView); 595 596 fTextView->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 597 fTextView->SetText(text, strlen(text)); 598 fTextView->MakeEditable(false); 599 fTextView->MakeSelectable(false); 600 fTextView->SetWordWrap(true); 601 602 // Now resize the TextView vertically so that all the text is visible 603 float textHeight = fTextView->TextHeight(0, fTextView->CountLines()); 604 TextViewRect.OffsetTo(0, 0); 605 textHeight -= TextViewRect.Height(); 606 ResizeBy(0, textHeight); 607 fTextView->ResizeBy(0, textHeight); 608 TextViewRect.bottom += textHeight; 609 fTextView->SetTextRect(TextViewRect); 610 611 AddCommonFilter(new _BAlertFilter_(this)); 612 613 MoveTo(AlertPosition(Frame().Width(), Frame().Height())); 614 } 615 616 617 BBitmap* 618 BAlert::InitIcon() 619 { 620 // After a bit of a search, I found the icons in app_server. =P 621 BBitmap* icon = NULL; 622 BPath path; 623 if (find_directory(B_BEOS_SERVERS_DIRECTORY, &path) == B_OK) { 624 path.Append("app_server"); 625 BFile file; 626 if (file.SetTo(path.Path(), B_READ_ONLY) == B_OK) { 627 BResources resources; 628 if (resources.SetTo(&file) == B_OK) { 629 // Which icon are we trying to load? 630 const char* iconName = ""; // Don't want any seg faults 631 switch (fMsgType) { 632 case B_INFO_ALERT: 633 iconName = "info"; 634 break; 635 case B_IDEA_ALERT: 636 iconName = "idea"; 637 break; 638 case B_WARNING_ALERT: 639 iconName = "warn"; 640 break; 641 case B_STOP_ALERT: 642 iconName = "stop"; 643 break; 644 645 default: 646 // Alert type is either invalid or B_EMPTY_ALERT; 647 // either way, we're not going to load an icon 648 return NULL; 649 } 650 651 // Load the raw icon data 652 size_t size; 653 const void* rawIcon = 654 resources.LoadResource('ICON', iconName, &size); 655 656 if (rawIcon) { 657 // Now build the bitmap 658 icon = new BBitmap(BRect(0, 0, 31, 31), 0, B_CMAP8); 659 icon->SetBits(rawIcon, size, 0, B_CMAP8); 660 } 661 } 662 } 663 } 664 665 if (!icon) { 666 // If there's no icon, it's an empty alert indeed. 667 fMsgType = B_EMPTY_ALERT; 668 } 669 670 return icon; 671 } 672 673 674 //------------------------------------------------------------------------------ 675 // #pragma mark - TAlertView 676 677 678 TAlertView::TAlertView(BRect frame) 679 : BView(frame, "TAlertView", B_FOLLOW_ALL_SIDES, B_WILL_DRAW), 680 fIconBitmap(NULL) 681 { 682 SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 683 } 684 685 686 TAlertView::TAlertView(BMessage* archive) 687 : BView(archive), 688 fIconBitmap(NULL) 689 { 690 } 691 692 693 TAlertView::~TAlertView() 694 { 695 delete fIconBitmap; 696 } 697 698 699 TAlertView* 700 TAlertView::Instantiate(BMessage* archive) 701 { 702 if (!validate_instantiation(archive, "TAlertView")) 703 return NULL; 704 705 return new TAlertView(archive); 706 } 707 708 709 status_t 710 TAlertView::Archive(BMessage* archive, bool deep) 711 { 712 return BView::Archive(archive, deep); 713 } 714 715 716 void 717 TAlertView::Draw(BRect updateRect) 718 { 719 // Here's the fun stuff 720 if (fIconBitmap) { 721 BRect StripeRect = Bounds(); 722 StripeRect.right = kIconStripeWidth; 723 SetHighColor(tint_color(ViewColor(), B_DARKEN_1_TINT)); 724 FillRect(StripeRect); 725 726 SetDrawingMode(B_OP_OVER); 727 DrawBitmapAsync(fIconBitmap, BPoint(18, 6)); 728 SetDrawingMode(B_OP_COPY); 729 } 730 } 731 732 733 //------------------------------------------------------------------------------ 734 // #pragma mark - _BAlertFilter_ 735 736 737 _BAlertFilter_::_BAlertFilter_(BAlert* Alert) 738 : BMessageFilter(B_KEY_DOWN), 739 fAlert(Alert) 740 { 741 } 742 743 744 _BAlertFilter_::~_BAlertFilter_() 745 { 746 } 747 748 749 filter_result 750 _BAlertFilter_::Filter(BMessage* msg, BHandler** target) 751 { 752 if (msg->what == B_KEY_DOWN) { 753 char byte; 754 if (msg->FindInt8("byte", (int8*)&byte) == B_OK) { 755 for (int i = 0; i < 3; ++i) { 756 if (byte == fAlert->Shortcut(i) && fAlert->ButtonAt(i)) { 757 char space = ' '; 758 fAlert->ButtonAt(i)->KeyDown(&space, 1); 759 760 return B_SKIP_MESSAGE; 761 } 762 } 763 } 764 } 765 766 return B_DISPATCH_MESSAGE; 767 } 768