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