1/** <title>NSAlert</title> 2 3 <abstract>Encapsulate an alert panel</abstract> 4 5 Copyright <copy>(C) 1998, 2000, 2004 Free Software Foundation, Inc.</copy> 6 7 Author: Fred Kiefer <FredKiefer@gmx.de> 8 Date: July 2004 9 10 GSAlertPanel and alert panel functions implementation 11 Author: Richard Frith-Macdonald <richard@brainstorm.co.uk> 12 Date: 1998 13 14 GSAlertPanel and alert panel functions cleanup and improvements (scroll view) 15 Author: Pascal J. Bourguignon <pjb@imaginet.fr>> 16 Date: 2000-03-08 17 18 This file is part of the GNUstep GUI Library. 19 20 This library is free software; you can redistribute it and/or 21 modify it under the terms of the GNU Lesser General Public 22 License as published by the Free Software Foundation; either 23 version 2 of the License, or (at your option) any later version. 24 25 This library is distributed in the hope that it will be useful, 26 but WITHOUT ANY WARRANTY; without even the implied warranty of 27 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 28 Lesser General Public License for more details. 29 30 You should have received a copy of the GNU Lesser General Public 31 License along with this library; see the file COPYING.LIB. 32 If not, see <http://www.gnu.org/licenses/> or write to the 33 Free Software Foundation, 51 Franklin Street, Fifth Floor, 34 Boston, MA 02110-1301, USA. 35*/ 36 37#import "config.h" 38 39#import <Foundation/NSDebug.h> 40#import <Foundation/NSBundle.h> 41#import <Foundation/NSError.h> 42#import <Foundation/NSNotification.h> 43#import <Foundation/NSString.h> 44#import <Foundation/NSThread.h> 45#import "AppKit/NSAlert.h" 46#import "AppKit/NSApplication.h" 47#import "AppKit/NSAttributedString.h" 48#import "AppKit/NSBox.h" 49#import "AppKit/NSBrowser.h" 50#import "AppKit/NSBrowserCell.h" 51#import "AppKit/NSButton.h" 52#import "AppKit/NSEvent.h" 53#import "AppKit/NSFont.h" 54#import "AppKit/NSHelpManager.h" 55#import "AppKit/NSImage.h" 56#import "AppKit/NSMatrix.h" 57#import "AppKit/NSPanel.h" 58#import "AppKit/NSScreen.h" 59#import "AppKit/NSScroller.h" 60#import "AppKit/NSScrollView.h" 61#import "AppKit/NSStringDrawing.h" 62#import "AppKit/NSTextField.h" 63#import "AppKit/NSTextView.h" 64 65#import "GNUstepGUI/GMAppKit.h" 66#import "GNUstepGUI/GMArchiver.h" 67#import "GSGuiPrivate.h" 68 69extern NSThread *GSAppKitThread; 70 71static NSNotificationCenter *nc = nil; 72 73#ifdef ALERT_TITLE 74static NSString *defaultTitle = @"Alert"; 75#else 76static NSString *defaultTitle = @" "; 77#endif 78 79/* 80 81 +--------------------------------------------------------------------------+ 82 |############################Title bar#####################################| 83 +--------------------------------------------------------------------------+ 84 | | | | | | 85 | ........... | | | | 86 | : | : | | | | 87 |--: Icon | :----Title | | | 88 | :-------|-: | | | 89 | :.........: | | | 90 | | | | 91 |-===========================|=============~~~~~~=========================-| 92 | s | | 93 | s | | 94 | ...................s....|......................................... | 95 | : Message s s ; | 96 | : s s : | 97 | : s s : | 98 |----: s s :----| 99 | :~~~~~~~~~~~~~~~~~~s~~~~~~~~~~~~~~~~~~~~s~~~~~~~~~~~~~~~~~~~~~~~~: | 100 | : s s : | 101 | :..................s.............................................: | 102 | | s | 103 | | s +-----------+ +-----------+ +-----------+ | 104 | | s | Altern |---| Cancel |---| OK |----| 105 | | s +-----------+ +-----------+ +-----------+ | 106 | | s | | | | 107 +--------------------------------------------------------------------------+ 108 109 Apart from the buttons and window borders, '|' and '-' mean not 110 flexible, while '~' and 's' mean flexible. 111 112 The global window size is determined by the message text field 113 size. which is computed with sizeToFit. However, if the window 114 would become larger than the screen, then the message text field 115 is replaced with a scroll view containing the text. 116 117 118 The strategy taken in this new version of GSAlertPanel is to let 119 the icon, the title field and the line (box view) being placed 120 automatically by resizing the window, but to always place 121 explicitely the message field (or scroll view when it's needed) 122 and the buttons (that may change of width), the whole in the 123 sizeToFitIfNeeded method. We're doing it separately from the 124 setting of the elements (setTitle: ...) because we also need to 125 recompute the size of the window and position of the elements when 126 dearchiving a panel, because it could be dearchived and displayed 127 on a much smaller screen than originaly created, in which case we 128 needed to embed the message field into a scroll view, and reduce 129 the size of the window. 130 131 132 Some rules (which are implemented in sizePanelToFit): 133 ===================================================== 134 135 136 IF the messageField is too big either vertically or horizontally and 137 would make the window greater than the screen in any direction, 138 THEN use a scroll view. 139 140 141 The width of the window content rect is the minimum of: 142 = the width of the content rect of a window filling the screen 143 = the maximum of: 144 = a minimum width of 362, 145 = the messageField width + 2 = MessageHorzMargin, 146 = the sum of sizes of the buttons and their interspaces and margins, 147 = the sum of sizes of the icon, title and their interspaces 148 and margin. 149 150 151 The height of the window content rect is the minimum of: 152 = the height of the content rect of a window filling the screen 153 = the maximum of: 154 = a minimum height of 161. 155 = the sum of: 156 = The height of the icon, the line and their interspaces. 157 = The height of the messageField and its interspaces, if present. 158 = The height of the buttons and their interspaces, if present. 159 160 161 The size of the scroll view must be at minimum ScrollMinSize 162 in each direction. 163 164 The size of the messageField is a given (sizeToFit). 165 166 The height of the scroll is the rest of the height of the content rect 167 minus the height of the other elements. 168 169 The width of the scroll is the rest of the width of the content rect 170 minus the margins. 171 172 In order to prevent alert panels from obscuring the whole screen, we 173 limit the width and height of a panel to at most a factor of SIZE_SCALE 174 of the screen size. At present, we use 60% for SIZE_SCALE, which means 175 that the limit is greater than the minimum width and height even on a 176 640x400 screen. 177 178 ((wsize.width <= ssize.width * SIZE_SCALE) 179 and ([messageField frame].size.width+2*MessageHorzMargin <= wsize.width)) 180 or ((wsize.width == ssize.width * SIZE_SCALE) 181 and ([scroll frame].size.width = wsize.width-2*MessageHorzMargin)); 182 183 ... 184*/ 185 186 187@class GSAlertPanel; 188 189static GSAlertPanel *standardAlertPanel = nil; 190static GSAlertPanel *informationalAlertPanel = nil; 191static GSAlertPanel *criticalAlertPanel = nil; 192 193@interface GSAlertPanel: NSPanel 194{ 195 NSButton *defButton; 196 NSButton *altButton; 197 NSButton *othButton; 198 NSButton *icoButton; 199 NSTextField *titleField; 200 NSTextField *messageField; 201 NSScrollView *scroll; 202 NSInteger result; 203 BOOL isGreen; // we were unarchived and not resized. 204} 205 206- (id) _initWithoutGModel; 207- (NSInteger) runModal; 208- (void) setTitleBar: (NSString*)titleBar 209 icon: (NSImage*)icon 210 title: (NSString*)title 211 message: (NSString*)message; 212- (void) setTitleBar: (NSString*)titleBar 213 icon: (NSImage*)icon 214 title: (NSString*)title 215 message: (NSString*)message 216 def: (NSString*)defaultButton 217 alt: (NSString*)alternateButton 218 other: (NSString*)otherButton; 219- (void) setButtons: (NSArray *)buttons; 220- (void) sizePanelToFit; 221- (void) buttonAction: (id)sender; 222- (NSInteger) result; 223- (BOOL) isActivePanel; 224@end 225 226 227@implementation GSAlertPanel 228 229//static const float WTitleHeight = 0.0; // TODO: Check this value. 230static const float WinMinWidth = 362.0; 231static const float WinMinHeight = 161.0; 232static const float IconSide = 48.0; 233static const float IconBottom = -56.0; // from the top of the window. 234static const float IconLeft = 8.0; 235static const float TitleLeft = 64.0; 236static const float TitleMinRight = 8.0; 237static const float LineHeight = 2.0; 238static const float LineBottom = -66.0; // from the top of the window. 239static const float LineLeft = 0.0; 240//static const float ScrollMinSize = 48.0; // in either direction. 241static const float MessageHorzMargin = 8.0; // 5 is too little margin. 242//static const float MessageMinHeight = 20.0; 243static const float MessageVertMargin = 6.0; // from the top of the buttons. 244static const float MessageTop = -72; // from the top of the window; 245static const float ButtonBottom = 8.0; // from the bottom of the window. 246static const float ButtonMargin = 8.0; 247static const float ButtonInterspace = 10.0; 248static const float ButtonMinHeight = 24.0; 249static const float ButtonMinWidth = 72.0; 250 251#define SIZE_SCALE 0.6 252#define MessageFont [NSFont messageFontOfSize: 14] 253 254+ (void) initialize 255{ 256 if (self == [GSAlertPanel class]) 257 { 258 [self setVersion: 1]; 259 } 260} 261 262- (id) init 263{ 264/* 265 if (![NSBundle loadNibNamed: @"AlertPanel" owner: self]) 266 { 267 NSLog(@"cannot open alert panel model file\n"); 268 return nil; 269 } 270 */ 271 return [self _initWithoutGModel]; 272} 273 274- (void) dealloc 275{ 276 if (self == standardAlertPanel) 277 { 278 standardAlertPanel = nil; 279 } 280 if (self == informationalAlertPanel) 281 { 282 informationalAlertPanel = nil; 283 } 284 if (self == criticalAlertPanel) 285 { 286 criticalAlertPanel = nil; 287 } 288 RELEASE(defButton); 289 RELEASE(altButton); 290 RELEASE(othButton); 291 RELEASE(icoButton); 292 RELEASE(titleField); 293 RELEASE(messageField); 294 RELEASE(scroll); 295 [super dealloc]; 296} 297 298static NSScrollView* 299makeScrollViewWithRect(NSRect rect) 300{ 301 float lineHeight = [MessageFont boundingRectForFont].size.height; 302 NSScrollView *scroll = [[NSScrollView alloc]initWithFrame: rect]; 303 304 [scroll setBorderType: NSLineBorder]; 305 [scroll setBackgroundColor: [NSColor controlBackgroundColor]]; 306 [scroll setHasHorizontalScroller: NO]; 307 [scroll setHasVerticalScroller: YES]; 308 [scroll setScrollsDynamically: YES]; 309 [scroll setLineScroll: lineHeight]; 310 [scroll setPageScroll: lineHeight*10.0]; 311 return scroll; 312} 313 314- (NSButton*) _makeButtonWithRect: (NSRect)rect tag: (NSInteger)tag 315{ 316 NSButton *button = [[NSButton alloc] initWithFrame: rect]; 317 318 [button setAutoresizingMask: NSViewMinXMargin | NSViewMaxYMargin]; 319 [button setButtonType: NSMomentaryPushInButton]; 320 [button setTitle: @""]; 321 [button setTarget: self]; 322 [button setAction: @selector(buttonAction:)]; 323 [button setTag: tag]; 324 [button setFont: [NSFont systemFontOfSize: 0]]; 325 return button; 326} 327 328#define useControl(control) ([control superview] != nil) 329 330static void 331setControl(NSView* content, id control, NSString *title) 332{ 333 if (title != nil) 334 { 335 if ([control respondsToSelector: @selector(setTitle:)]) 336 { 337 [control setTitle: title]; 338 } 339 else if ([control respondsToSelector: @selector(setStringValue:)]) 340 { 341 [control setStringValue: title]; 342 } 343 [control sizeToFit]; 344 if (!useControl(control)) 345 { 346 [content addSubview: control]; 347 } 348 } 349 else if (useControl(control)) 350 { 351 [control removeFromSuperview]; 352 } 353} 354 355static void 356setButton(NSView* content, NSButton *control, NSButton *template) 357{ 358 if (template != nil) 359 { 360 [control setTitle: [template title]]; 361 [control setKeyEquivalent: [template keyEquivalent]]; 362 [control setKeyEquivalentModifierMask: 363 [template keyEquivalentModifierMask]]; 364 [control setTag: [template tag]]; 365 [control sizeToFit]; 366 if (!useControl(control)) 367 { 368 [content addSubview: control]; 369 } 370 } 371 else if (useControl(control)) 372 { 373 [control removeFromSuperview]; 374 } 375} 376 377static void 378setKeyEquivalent(NSButton *button) 379{ 380 NSString *title = [button title]; 381 382 if (![[button keyEquivalent] isEqualToString: @"\r"]) 383 { 384 if ([title isEqualToString: _(@"Cancel")]) 385 { 386 [button setKeyEquivalent: @"\e"]; 387 [button setKeyEquivalentModifierMask: 0]; 388 } 389 else if ([title isEqualToString: _(@"Don't Save")]) 390 { 391 [button setKeyEquivalent: @"d"]; 392 [button setKeyEquivalentModifierMask: NSCommandKeyMask]; 393 } 394 else 395 { 396 [button setKeyEquivalent: @""]; 397 [button setKeyEquivalentModifierMask: 0]; 398 } 399 } 400} 401 402- (id) _initWithoutGModel 403{ 404 NSRect rect; 405 NSImage *image; 406 NSBox *box; 407 NSView *content; 408 NSRect r = NSMakeRect(0.0, 0.0, WinMinWidth, WinMinHeight); 409 NSFont *titleFont = [NSFont systemFontOfSize: 18.0]; 410 float titleHeight = [titleFont boundingRectForFont].size.height; 411 NSText *fieldEditor = [self fieldEditor: YES forObject: messageField]; 412 NSDictionary *selectedAttrs; 413 414 415 self = [self initWithContentRect: r 416 styleMask: NSTitledWindowMask 417 backing: NSBackingStoreRetained 418 defer: YES]; 419 420 if (self == nil) 421 return nil; 422 423 [self setTitle: @" "]; 424 [self setLevel: NSModalPanelWindowLevel]; 425 content = [self contentView]; 426 427 // we're an ATTENTION panel, therefore: 428 [self setHidesOnDeactivate: NO]; 429 [self setBecomesKeyOnlyIfNeeded: NO]; 430 431 // First, the subviews that will be positioned automatically. 432 rect.size.height = IconSide; 433 rect.size.width = IconSide; 434 rect.origin.y = r.origin.y + r.size.height + IconBottom; 435 rect.origin.x = IconLeft; 436 icoButton = [[NSButton alloc] initWithFrame: rect]; 437 [icoButton setAutoresizingMask: NSViewMaxXMargin|NSViewMinYMargin]; 438 [icoButton setBordered: NO]; 439 [icoButton setEnabled: NO]; 440 [[icoButton cell] setImageDimsWhenDisabled: NO]; 441 [[icoButton cell] setImageScaling: NSImageScaleProportionallyUpOrDown]; 442 [icoButton setImagePosition: NSImageOnly]; 443 image = [[NSApplication sharedApplication] applicationIconImage]; 444 [icoButton setImage: image]; 445 [content addSubview: icoButton]; 446 447 // Title 448 rect.size.height = 0.0; // will be sized to fit anyway. 449 rect.size.width = 0.0; // will be sized to fit anyway. 450 rect.origin.y = r.origin.y + r.size.height 451 + IconBottom + (IconSide - titleHeight)/2;; 452 rect.origin.x = TitleLeft; 453 titleField = [[NSTextField alloc] initWithFrame: rect]; 454 [titleField setAutoresizingMask: NSViewMinYMargin]; 455 [titleField setEditable: NO]; 456 [titleField setSelectable: NO]; 457 [titleField setBezeled: NO]; 458 [titleField setDrawsBackground: NO]; 459 [titleField setStringValue: @""]; 460 [titleField setFont: titleFont]; 461 462 // Horizontal line 463 rect.size.height = LineHeight; 464 rect.size.width = r.size.width; 465 rect.origin.y = r.origin.y + r.size.height + LineBottom; 466 rect.origin.x = LineLeft; 467 box = [[NSBox alloc] initWithFrame: rect]; 468 [box setAutoresizingMask: NSViewWidthSizable | NSViewMinYMargin]; 469 [box setTitlePosition: NSNoTitle]; 470 [box setBorderType: NSGrooveBorder]; 471 [content addSubview: box]; 472 RELEASE(box); 473 474 // Then, make the subviews that'll be sized by sizePanelToFit; 475 rect.size.height = 0.0; 476 rect.size.width = 0.0; 477 rect.origin.y = 0.0; 478 rect.origin.x = 0.0; 479 480 messageField = [[NSTextField alloc] initWithFrame: rect]; 481 [messageField setEditable: NO]; 482 /* 483 PJB: 484 How do you want the user to report an error message if it is 485 not selectable? Any text visible on the screen should always 486 be selectable for a copy-and-paste. Hence, setSelectable: YES. 487 */ 488 selectedAttrs = [NSDictionary dictionaryWithObjectsAndKeys: 489 [NSColor controlLightHighlightColor], 490 NSBackgroundColorAttributeName, 491 nil]; 492 [(NSTextView *)fieldEditor setSelectedTextAttributes: selectedAttrs]; 493 [messageField setSelectable: YES]; 494 [messageField setBezeled: NO]; 495 [messageField setDrawsBackground: NO]; 496 [messageField setAlignment: NSCenterTextAlignment]; 497 [messageField setStringValue: @""]; 498 [messageField setFont: MessageFont]; 499 500 defButton = [self _makeButtonWithRect: rect tag: NSAlertDefaultReturn]; 501 [defButton setKeyEquivalent: @"\r"]; 502 [defButton setHighlightsBy: NSPushInCellMask | NSChangeGrayCellMask 503 | NSContentsCellMask]; 504 [defButton setImagePosition: NSImageRight]; 505 [defButton setImage: [NSImage imageNamed: @"common_ret"]]; 506 [defButton setAlternateImage: [NSImage imageNamed: @"common_retH"]]; 507 508 altButton = [self _makeButtonWithRect: rect tag: NSAlertAlternateReturn]; 509 othButton = [self _makeButtonWithRect: rect tag: NSAlertOtherReturn]; 510 511 rect.size.height = 80.0; 512 scroll = makeScrollViewWithRect(rect); 513 514 result = NSAlertErrorReturn; 515 isGreen = YES; 516 517 return self; 518} 519 520- (void) sizePanelToFit 521{ 522 NSRect bounds; 523 NSSize ssize; // screen size (corrected). 524 NSSize bsize; // button size (max of the three). 525 NSSize wsize = {0.0, 0.0}; // window size (computed). 526 NSScreen *screen; 527 NSView *content; 528 NSButton *buttons[3]; 529 float position = 0.0; 530 int numberOfButtons; 531 int i; 532 BOOL needsScroll; 533 BOOL couldNeedScroll; 534 NSUInteger mask = [self styleMask]; 535 536 /* 537 * Set size to the size of a content rectangle of a panel 538 * that completely fills the screen. 539 */ 540 screen = [self screen]; 541 if (screen == nil) 542 { 543 screen = [NSScreen mainScreen]; 544 } 545 bounds = [screen frame]; 546 bounds = [NSWindow contentRectForFrameRect: bounds styleMask: mask]; 547 ssize = bounds.size; 548 /* Do not let a panel grow beyond a factor of SIZE_SCALE of the screen */ 549 ssize.width = SIZE_SCALE * ssize.width; 550 ssize.height = SIZE_SCALE * ssize.height; 551 552 // Let's size the title. 553 if (useControl(titleField)) 554 { 555 NSRect rect = [titleField frame]; 556 float width = TitleLeft + rect.size.width + TitleMinRight; 557 558 if (wsize.width < width) 559 { 560 wsize.width = width; 561 // ssize.width < width = > the title will be silently clipped. 562 } 563 } 564 565 wsize.height = -LineBottom; 566 567 568 // Let's count the buttons. 569 bsize.width = ButtonMinWidth; 570 bsize.height = ButtonMinHeight; 571 buttons[0] = defButton; 572 buttons[1] = altButton; 573 buttons[2] = othButton; 574 numberOfButtons = 0; 575 for (i = 0; i < 3; i++) 576 { 577 if (useControl(buttons[i])) 578 { 579 NSRect rect = [buttons[i] frame]; 580 581 if (bsize.width < rect.size.width) 582 { 583 bsize.width = rect.size.width; 584 } 585 if (bsize.height < rect.size.height) 586 { 587 bsize.height = rect.size.height; 588 } 589 numberOfButtons++; 590 } 591 } 592 593 if (numberOfButtons > 0) 594 { 595 // (with NSGetAlertPanel, there could be zero buttons). 596 float width = (bsize.width + ButtonInterspace) * numberOfButtons 597 -ButtonInterspace + ButtonMargin * 2; 598 /* 599 * If the buttons are too wide or too high to fit in the screen, 600 * then too bad! Thought it would be simple enough to put them 601 * in the scroll view with the messageField. 602 * TODO: See if we raise an exception here or if we let the 603 * QA people detect this kind of problem. 604 */ 605 if (wsize.width < width) 606 { 607 wsize.width = width; 608 } 609 wsize.height += ButtonBottom + bsize.height; 610 } 611 612 // Let's see the size of the messageField and how to place it. 613 needsScroll = NO; 614 couldNeedScroll = useControl(messageField); 615 if (couldNeedScroll) 616 { 617 NSRect rect = [messageField frame]; 618 float width = rect.size.width + 2*MessageHorzMargin; 619 620 if (wsize.width < width) 621 { 622 wsize.width = width; 623 } 624 625 /* If the message is too wide, wrap its text. Apparently, we cannot 626 use -sizeToFit to compute the height of the message. */ 627 [messageField setAlignment: NSLeftTextAlignment]; 628 width = ssize.width - 2*MessageHorzMargin; 629 rect.size = 630 [[messageField attributedStringValue] 631 boundingRectWithSize: NSMakeSize(width, 1e6) 632 options: 0].size; 633 [messageField setFrame: rect]; 634 635 /* 636 * But only the messageField can impose a great height, therefore 637 * we check it along in the next paragraph. 638 */ 639 wsize.height += rect.size.height + 2 * MessageVertMargin; 640 } 641 else 642 { 643 wsize.height += MessageVertMargin; 644 } 645 646 // Strategically placed here, we resize the window. 647 if (ssize.height < wsize.height) 648 { 649 wsize.height = ssize.height; 650 needsScroll = couldNeedScroll; 651 } 652 else if (wsize.height < WinMinHeight) 653 { 654 wsize.height = WinMinHeight; 655 } 656 if (needsScroll) 657 { 658 wsize.width += [NSScroller scrollerWidth] + 4.0; 659 } 660 if (ssize.width < wsize.width) 661 { 662 wsize.width = ssize.width; 663 } 664 else if (wsize.width < WinMinWidth) 665 { 666 wsize.width = WinMinWidth; 667 } 668 bounds = NSMakeRect(0, 0, wsize.width, wsize.height); 669 bounds = [NSWindow frameRectForContentRect: bounds styleMask: mask]; 670 [self setMaxSize: bounds.size]; 671 [self setMinSize: bounds.size]; 672 [self setContentSize: wsize]; 673 content = [self contentView]; 674 bounds = [content bounds]; 675 676 // Now we can place the buttons. 677 if (numberOfButtons > 0) 678 { 679 position = bounds.origin.x + bounds.size.width - ButtonMargin; 680 for (i = 0; i < 3; i++) 681 { 682 if (useControl(buttons[i])) 683 { 684 NSRect rect; 685 686 position -= bsize.width; 687 rect.origin.x = position; 688 rect.origin.y = bounds.origin.y + ButtonBottom; 689 rect.size.width = bsize.width; 690 rect.size.height = bsize.height; 691 [buttons[i] setFrame: rect]; 692 position -= ButtonInterspace; 693 } 694 } 695 } 696 697 // Finaly, place the message. 698 if (useControl(messageField)) 699 { 700 NSRect mrect = [messageField frame]; 701 702 if (needsScroll) 703 { 704 NSRect srect; 705 float width; 706 707 // The scroll view takes all the space that is available. 708 srect.origin.x = bounds.origin.x + MessageHorzMargin; 709 if (numberOfButtons > 0) 710 { 711 srect.origin.y = bounds.origin.y + ButtonBottom 712 + bsize.height + MessageVertMargin; 713 } 714 else 715 { 716 srect.origin.y = bounds.origin.y + MessageVertMargin; 717 } 718 srect.size.width = bounds.size.width - 2 * MessageHorzMargin; 719 srect.size.height = bounds.origin.y + bounds.size.height 720 + MessageTop - srect.origin.y; 721 [scroll setFrame: srect]; 722 if (!useControl(scroll)) 723 { 724 [content addSubview: scroll]; 725 } 726 727 /* Adjust the message field's width again so that it does not 728 exceed the scroll view's visible rectangle and we do not 729 need a horizontal scroller. */ 730 [messageField removeFromSuperview]; 731 width = 732 [NSScrollView contentSizeForFrameSize: srect.size 733 hasHorizontalScroller: NO 734 hasVerticalScroller: YES 735 borderType: [scroll borderType]].width; 736 mrect.origin = NSZeroPoint; 737 mrect.size = 738 [[messageField attributedStringValue] 739 boundingRectWithSize: NSMakeSize(width, 1e6) 740 options: 0].size; 741 [messageField setFrame: mrect]; 742 [scroll setDocumentView: messageField]; 743 } 744 else 745 { 746 float vmargin; 747 748 /* 749 * We must center vertically the messageField because 750 * the window has a minimum size, thus may be greater 751 * than expected. 752 */ 753 mrect.origin.x = (wsize.width - mrect.size.width)/2; 754 vmargin = bounds.size.height + LineBottom-mrect.size.height; 755 if (numberOfButtons > 0) 756 { 757 vmargin -= ButtonBottom + bsize.height; 758 } 759 vmargin/= 2.0; // if negative, it'll bite up and down. 760 mrect.origin.y = bounds.origin.y + vmargin; 761 if (numberOfButtons > 0) 762 { 763 mrect.origin.y += ButtonBottom + bsize.height; 764 } 765 [messageField setFrame: mrect]; 766 } 767 } 768 else if (useControl(scroll)) 769 { 770 [scroll removeFromSuperview]; 771 } 772 773 isGreen = NO; 774 [content display]; 775} 776 777- (void) buttonAction: (id)sender 778{ 779 if (![self isActivePanel]) 780 { 781 NSLog(@"alert panel buttonAction: when not in modal loop\n"); 782 return; 783 } 784 result = [sender tag]; 785 [NSApp stopModalWithCode: result]; 786} 787 788- (NSInteger) result 789{ 790 return result; 791} 792 793- (BOOL) isActivePanel 794{ 795 return [NSApp modalWindow] == self; 796} 797 798- (NSInteger) runModal 799{ 800 if (GSCurrentThread() != GSAppKitThread) 801 { 802 [self performSelectorOnMainThread: _cmd 803 withObject: nil 804 waitUntilDone: YES]; 805 } 806 else 807 { 808 if (isGreen) 809 { 810 [self sizePanelToFit]; 811 } 812 [NSApp runModalForWindow: self]; 813 [self orderOut: self]; 814 } 815 return result; 816} 817 818- (void) setTitleBar: (NSString*)titleBar 819 icon: (NSImage*)icon 820 title: (NSString*)title 821 message: (NSString*)message 822{ 823 NSView *content = [self contentView]; 824 825 if (titleBar != nil) 826 { 827 [self setTitle: titleBar]; 828 } 829 if (icon != nil) 830 { 831 [icoButton setImage: icon]; 832 } 833 if (title == nil) 834 { 835 title = titleBar; // Fall back to the same text as the title bar 836 } 837 setControl(content, titleField, title); 838 if (useControl(scroll)) 839 { 840 // TODO: Remove the following line once NSView is corrected. 841 [scroll setDocumentView: nil]; 842 [scroll removeFromSuperview]; 843 [messageField removeFromSuperview]; 844 } 845 setControl(content, messageField, message); 846 847 /* If the message contains a newline character then align the 848 * message to the left side, as it is quite undesirable for a long 849 * message to appear aligned in the center 850 */ 851 if ([message rangeOfString: @"\n"].location != NSNotFound) 852 { 853 [messageField setAlignment: NSLeftTextAlignment]; 854 } 855 else 856 { 857 [messageField setAlignment: NSCenterTextAlignment]; 858 } 859} 860 861- (void) setTitleBar: (NSString*)titleBar 862 icon: (NSImage*)icon 863 title: (NSString*)title 864 message: (NSString*)message 865 def: (NSString*)defaultButton 866 alt: (NSString*)alternateButton 867 other: (NSString*)otherButton 868{ 869 NSView *content = [self contentView]; 870 871 [self setTitleBar: titleBar icon: icon title: title message: message]; 872 setControl(content, defButton, defaultButton); 873 setControl(content, altButton, alternateButton); 874 setControl(content, othButton, otherButton); 875 if (useControl(defButton)) 876 { 877 [self makeFirstResponder: defButton]; 878 } 879 else 880 { 881 [self makeFirstResponder: self]; 882 } 883 if (useControl(altButton)) 884 { 885 setKeyEquivalent(altButton); 886 } 887 if (useControl(othButton)) 888 { 889 setKeyEquivalent(othButton); 890 } 891 892 /* a *working* nextKeyView chain: 893 the trick is that the 3 buttons are not always used (displayed) 894 so we have to set the nextKeyView *each* time. 895 Maybe some optimisation in the logic of this block will be good, 896 however it seems too risky for a (so) small reward 897 */ 898 { 899 BOOL ud, ua, uo; 900 ud = useControl(defButton); 901 ua = useControl(altButton); 902 uo = useControl(othButton); 903 904 if (ud) 905 { 906 if (uo) 907 [defButton setNextKeyView: othButton]; 908 else if (ua) 909 [defButton setNextKeyView: altButton]; 910 else 911 { 912 [defButton setPreviousKeyView: nil]; 913 [defButton setNextKeyView: nil]; 914 } 915 } 916 917 if (uo) 918 { 919 if (ua) 920 [othButton setNextKeyView: altButton]; 921 else if (ud) 922 [othButton setNextKeyView: defButton]; 923 else 924 { 925 [othButton setPreviousKeyView: nil]; 926 [othButton setNextKeyView: nil]; 927 } 928 } 929 930 if (ua) 931 { 932 if (ud) 933 [altButton setNextKeyView: defButton]; 934 else if (uo) 935 [altButton setNextKeyView: othButton]; 936 else 937 { 938 [altButton setPreviousKeyView: nil]; 939 [altButton setNextKeyView: nil]; 940 } 941 } 942 } 943 [self sizePanelToFit]; 944 isGreen = YES; 945 result = NSAlertErrorReturn; /* If no button was pressed */ 946} 947 948- (void) setButtons: (NSArray *)buttons; 949{ 950 NSView *content = [self contentView]; 951 NSUInteger count = [buttons count]; 952 953 setButton(content, defButton, count > 0 ? [buttons objectAtIndex: 0] : nil); 954 setButton(content, altButton, count > 1 ? [buttons objectAtIndex: 1] : nil); 955 setButton(content, othButton, count > 2 ? [buttons objectAtIndex: 2] : nil); 956 if (useControl(defButton)) 957 { 958 [self makeFirstResponder: defButton]; 959 } 960 else 961 { 962 [self makeFirstResponder: self]; 963 } 964 965 /* a *working* nextKeyView chain: 966 the trick is that the 3 buttons are not always used (displayed) 967 so we have to set the nextKeyView *each* time. 968 */ 969 if (count > 2) 970 { 971 [defButton setNextKeyView: othButton]; 972 [othButton setNextKeyView: altButton]; 973 [altButton setNextKeyView: defButton]; 974 } 975 else if (count > 1) 976 { 977 [defButton setNextKeyView: altButton]; 978 [altButton setNextKeyView: defButton]; 979 } 980 else if (count > 0) 981 { 982 [defButton setPreviousKeyView: nil]; 983 [defButton setNextKeyView: nil]; 984 } 985 986 [self sizePanelToFit]; 987 isGreen = YES; 988 result = NSAlertErrorReturn; /* If no button was pressed */ 989} 990 991@end /* GSAlertPanel */ 992 993@implementation GSAlertPanel (GMArchiverMethods) 994 995// Reuse createObjectForModelUnarchiver: from super class 996 997- (void) encodeWithModelArchiver: (GMArchiver*)archiver 998{ 999 [super encodeWithModelArchiver: archiver]; 1000 [archiver encodeSize: [self frame].size withName: @"OriginalSize"]; 1001 [archiver encodeObject: defButton withName: @"DefaultButton"]; 1002 [archiver encodeObject: altButton withName: @"AlternateButton"]; 1003 [archiver encodeObject: othButton withName: @"OtherButton"]; 1004 [archiver encodeObject: icoButton withName: @"IconButton"]; 1005 [archiver encodeObject: messageField withName: @"MessageField"]; 1006 [archiver encodeObject: titleField withName: @"TitleField"]; 1007} 1008 1009- (id) initWithModelUnarchiver: (GMUnarchiver*)unarchiver 1010{ 1011 self = [super initWithModelUnarchiver: unarchiver]; 1012 if (self != nil) 1013 { 1014 (void)[unarchiver decodeSizeWithName: @"OriginalSize"]; 1015 defButton = RETAIN([unarchiver decodeObjectWithName: @"DefaultButton"]); 1016 altButton = RETAIN([unarchiver decodeObjectWithName: @"AlternateButton"]); 1017 othButton = RETAIN([unarchiver decodeObjectWithName: @"OtherButton"]); 1018 icoButton = RETAIN([unarchiver decodeObjectWithName: @"IconButton"]); 1019 messageField = RETAIN([unarchiver decodeObjectWithName: @"MessageField"]); 1020 titleField = RETAIN([unarchiver decodeObjectWithName: @"TitleField"]); 1021 scroll = makeScrollViewWithRect(NSMakeRect(0.0, 0.0, 80.0, 80.0)); 1022 result = NSAlertErrorReturn; 1023 isGreen = YES; 1024 } 1025 return self; 1026} 1027 1028@end /* GSAlertPanel GMArchiverMethods */ 1029 1030/* 1031 GSAlertSheet. This class provides a borderless window which is 1032 attached to the parent window. 1033 */ 1034 1035@interface GSAlertSheet : GSAlertPanel 1036@end 1037 1038@implementation GSAlertSheet 1039+ (void) initialize 1040{ 1041 if (self == [GSAlertSheet class]) 1042 { 1043 if (nc == nil) 1044 { 1045 nc = [NSNotificationCenter defaultCenter]; 1046 } 1047 [self setVersion: 0]; 1048 } 1049} 1050 1051- (id) initWithContentRect: (NSRect)contentRect 1052 styleMask: (NSUInteger)aStyle 1053 backing: (NSBackingStoreType)bufferingType 1054 defer: (BOOL)flag 1055{ 1056 if (NSIsEmptyRect(contentRect)) 1057 { 1058 contentRect = NSMakeRect(0,0,100,100); 1059 } 1060 1061 self = [super initWithContentRect: contentRect 1062 styleMask: NSBorderlessWindowMask 1063 backing: bufferingType 1064 defer: flag]; 1065 if (self != nil) 1066 { 1067 // FIXME 1068 } 1069 return self; 1070} 1071 1072- (NSRect) frameFromParentWindowFrame 1073{ 1074 id parent = [self parentWindow]; 1075 NSRect frame = [self frame]; 1076 NSRect newFrame = NSZeroRect; // return zero rect, if parent isn't set. 1077 1078 if(parent != nil) 1079 { 1080 NSRect contentRect = [[parent contentView] frame]; 1081 1082 // 1083 // The calculation is based on the contentRect of the parent window 1084 // since we want the sheet to appear just inside of it. 1085 // 1086 newFrame = [parent frame]; 1087 newFrame.origin.x += ((newFrame.size.width - frame.size.width) / 2); 1088 newFrame.origin.y += (contentRect.size.height - frame.size.height) + 5; 1089 } 1090 1091 return newFrame; 1092} 1093 1094- (void) resetWindow 1095{ 1096 NSRect frame = [self frameFromParentWindowFrame]; 1097 NSWindow *parent = nil; 1098 1099 if((parent = [self parentWindow]) != nil) 1100 { 1101 [self setBackgroundColor: 1102 [[parent backgroundColor] 1103 highlightWithLevel: 0.4]]; 1104 } 1105 1106 [self setFrame: frame display: YES]; 1107} 1108 1109- (void) setParentWindow: (NSWindow *)window 1110{ 1111 [super setParentWindow: window]; 1112 [self resetWindow]; 1113 /* 1114 [nc removeObserver: self]; 1115 1116 if (parent != nil) 1117 { 1118 // add observers.... 1119 [nc addObserver: self 1120 selector: @selector(handleWindowClose:) 1121 name: NSWindowWillCloseNotification 1122 object: parent]; 1123 1124 [nc addObserver: self 1125 selector: @selector(handleWindowMiniaturize:) 1126 name: NSWindowWillMiniaturizeNotification 1127 object: parent]; 1128 1129 [nc addObserver: self 1130 selector: @selector(handleWindowMove:) 1131 name: NSWindowWillMoveNotification 1132 object: parent]; 1133 1134 [nc addObserver: self 1135 selector: @selector(handleWindowMove:) 1136 name: NSWindowDidResizeNotification 1137 object: parent]; 1138 1139 [nc addObserver: self 1140 selector: @selector(handleWindowDidBecomeKey:) 1141 name: NSWindowDidBecomeKeyNotification 1142 object: parent]; 1143 } 1144 */ 1145} 1146 1147/* 1148- (void) handleWindowClose: (NSNotification *)notification 1149{ 1150 [self close]; 1151} 1152 1153- (void) handleWindowMiniaturize: (NSNotification *)notification 1154{ 1155 [self close]; 1156} 1157 1158- (void) handleWindowMove: (NSNotification *)notification 1159{ 1160 [self _resetWindowPosition]; 1161} 1162 1163- (void) handleWindowDidBecomeKey: (NSNotification *)notification 1164{ 1165 [self _resetWindowPosition]; 1166} 1167 1168- (void) dealloc 1169{ 1170 [nc removeObserver: self]; 1171 [super dealloc]; 1172} 1173*/ 1174@end 1175 1176/* 1177 These functions may be called "recursively". For example, from a 1178 timed event. Therefore, there may be several alert panel active 1179 at the same time, but only the first one will be THE 1180 standardAlertPanel, which will not be released once finished 1181 with, but which will be kept for future use. 1182 1183 +---------+---------+---------+---------+---------+ 1184 | std !=0 | std act | pan=std | pan=new | std=new | 1185 +---------+---------+---------+---------+---------+ 1186 a: | F | N/A | | X | X | 1187 +---------+---------+---------+---------+---------+ 1188 b: | V | F | X | | | 1189 +---------+---------+---------+---------+---------+ 1190 c: | V | V | | X | | 1191 +---------+---------+---------+---------+---------+ 1192*/ 1193 1194 1195/* 1196 TODO: Check if this discrepancy is wanted and needed. 1197 If not, we could merge these parameters, even 1198 for the alert panel, setting its window title to "Alert". 1199*/ 1200 1201@interface _GSAlertCreation : NSObject 1202{ 1203 GSAlertPanel **instance; 1204 NSString *defaultTitle; 1205 NSString *title; 1206 NSString *message; 1207 NSString *defaultButton; 1208 NSString *alternateButton; 1209 NSString *otherButton; 1210 GSAlertPanel *panel; 1211} 1212- (id) initWithInstance: (GSAlertPanel**)_instance 1213 defaultTitle: (NSString*)_defaultTitle 1214 title: (NSString*)_title 1215 message: (NSString*)_message 1216 defaultButton: (NSString*)_defaultButton 1217 alternateButton: (NSString*)_alternateButton 1218 otherButton: (NSString*)_otherButton; 1219- (void) makePanel; 1220- (void) makeSheet; 1221- (GSAlertPanel*) panel; 1222@end 1223 1224@implementation _GSAlertCreation 1225- (void) dealloc 1226{ 1227 RELEASE(defaultTitle); 1228 RELEASE(title); 1229 RELEASE(defaultButton); 1230 RELEASE(alternateButton); 1231 RELEASE(otherButton); 1232 [super dealloc]; 1233} 1234 1235- (id) initWithInstance: (GSAlertPanel**)_instance 1236 defaultTitle: (NSString*)_defaultTitle 1237 title: (NSString*)_title 1238 message: (NSString*)_message 1239 defaultButton: (NSString*)_defaultButton 1240 alternateButton: (NSString*)_alternateButton 1241 otherButton: (NSString*)_otherButton 1242{ 1243 instance = _instance; 1244 ASSIGNCOPY(defaultTitle, _defaultTitle); 1245 ASSIGNCOPY(title, _title); 1246 ASSIGNCOPY(message, _message); 1247 ASSIGNCOPY(defaultButton, _defaultButton); 1248 ASSIGNCOPY(alternateButton, _alternateButton); 1249 ASSIGNCOPY(otherButton, _otherButton); 1250 return self; 1251} 1252 1253- (void) makePanel 1254{ 1255 if (*instance != 0 && [*instance isMemberOfClass: [GSAlertPanel class]]) 1256 { 1257 if ([*instance isActivePanel]) 1258 { // c: 1259 panel = [[GSAlertPanel alloc] init]; 1260 } 1261 else 1262 { // b: 1263 panel = *instance; 1264 } 1265 } 1266 else 1267 { // a: 1268 panel = [[GSAlertPanel alloc] init]; 1269 *instance = panel; 1270 } 1271 1272 [panel setTitleBar: defaultTitle 1273 icon: nil 1274 title: title 1275 message: message 1276 def: defaultButton 1277 alt: alternateButton 1278 other: otherButton]; 1279} 1280 1281- (void) makeSheet 1282{ 1283 if (*instance != 0 && [*instance isMemberOfClass: [GSAlertSheet class]]) 1284 { 1285 if ([*instance isActivePanel]) 1286 { // c: 1287 panel = [[GSAlertSheet alloc] init]; 1288 } 1289 else 1290 { // b: 1291 panel = *instance; 1292 } 1293 } 1294 else 1295 { // a: 1296 panel = [[GSAlertSheet alloc] init]; 1297 *instance = panel; 1298 } 1299 1300 [panel setTitleBar: defaultTitle 1301 icon: nil 1302 title: title 1303 message: message 1304 def: defaultButton 1305 alt: alternateButton 1306 other: otherButton]; 1307} 1308 1309- (GSAlertPanel*) panel 1310{ 1311 return panel; 1312} 1313@end 1314 1315static GSAlertPanel* 1316getSomePanel( 1317 GSAlertPanel **instance, 1318 NSString *defaultTitle, 1319 NSString *title, 1320 NSString *message, 1321 NSString *defaultButton, 1322 NSString *alternateButton, 1323 NSString *otherButton) 1324{ 1325 GSAlertPanel *panel; 1326 1327 if (GSCurrentThread() != GSAppKitThread) 1328 { 1329 _GSAlertCreation *c; 1330 1331 NSWarnFLog(@"Alert Panel functionality called from a thread other than" 1332 @" the main one, this may not work on MacOS-X and could therefore be" 1333 @" a portability problem in your code"); 1334 c = [_GSAlertCreation alloc]; 1335 c = [c initWithInstance: instance 1336 defaultTitle: defaultTitle 1337 title: title 1338 message: message 1339 defaultButton: defaultButton 1340 alternateButton: alternateButton 1341 otherButton: otherButton]; 1342 [c performSelectorOnMainThread: @selector(makePanel) 1343 withObject: nil 1344 waitUntilDone: YES]; 1345 panel = [c panel]; 1346 RELEASE(c); 1347 } 1348 else 1349 { 1350 if (*instance != 0 && [*instance isMemberOfClass: [GSAlertPanel class]]) 1351 { 1352 if ([*instance isActivePanel]) 1353 { // c: 1354 panel = [[GSAlertPanel alloc] init]; 1355 } 1356 else 1357 { // b: 1358 panel = *instance; 1359 } 1360 } 1361 else 1362 { // a: 1363 panel = [[GSAlertPanel alloc] init]; 1364 *instance = panel; 1365 } 1366 1367 [panel setTitleBar: defaultTitle 1368 icon: nil 1369 title: title 1370 message: message 1371 def: defaultButton 1372 alt: alternateButton 1373 other: otherButton]; 1374 } 1375 return panel; 1376} 1377 1378static GSAlertPanel* 1379getSomeSheet( 1380 GSAlertPanel **instance, 1381 NSString *defaultTitle, 1382 NSString *title, 1383 NSString *message, 1384 NSString *defaultButton, 1385 NSString *alternateButton, 1386 NSString *otherButton) 1387{ 1388 GSAlertSheet *panel; 1389 1390 if (GSCurrentThread() != GSAppKitThread) 1391 { 1392 _GSAlertCreation *c; 1393 1394 NSWarnFLog(@"Alert Sheet functionality called from a thread other than" 1395 @" the main one, this may not work on MacOS-X and could therefore be" 1396 @" a portability problem in your code"); 1397 c = [_GSAlertCreation alloc]; 1398 c = [c initWithInstance: instance 1399 defaultTitle: defaultTitle 1400 title: title 1401 message: message 1402 defaultButton: defaultButton 1403 alternateButton: alternateButton 1404 otherButton: otherButton]; 1405 [c performSelectorOnMainThread: @selector(makeSheet) 1406 withObject: nil 1407 waitUntilDone: YES]; 1408 panel = (GSAlertSheet *)[c panel]; 1409 RELEASE(c); 1410 } 1411 else 1412 { 1413 if (*instance != 0 && [*instance isMemberOfClass: [GSAlertSheet class]]) 1414 { 1415 if ([*instance isActivePanel]) 1416 { // c: 1417 panel = [[GSAlertSheet alloc] init]; 1418 } 1419 else 1420 { // b: 1421 panel = (GSAlertSheet *)*instance; 1422 } 1423 } 1424 else 1425 { // a: 1426 panel = [[GSAlertSheet alloc] init]; 1427 *instance = panel; 1428 } 1429 1430 [panel setTitleBar: defaultTitle 1431 icon: nil 1432 title: title 1433 message: message 1434 def: defaultButton 1435 alt: alternateButton 1436 other: otherButton]; 1437 } 1438 return panel; 1439} 1440 1441id 1442NSGetAlertPanel( 1443 NSString *title, 1444 NSString *msg, 1445 NSString *defaultButton, 1446 NSString *alternateButton, 1447 NSString *otherButton, ...) 1448{ 1449 va_list ap; 1450 NSString *message; 1451 1452 va_start(ap, otherButton); 1453 message = [NSString stringWithFormat: msg arguments: ap]; 1454 va_end(ap); 1455 1456 return getSomePanel(&standardAlertPanel, defaultTitle, title, message, 1457 defaultButton, alternateButton, otherButton); 1458} 1459 1460NSInteger 1461NSRunAlertPanel( 1462 NSString *title, 1463 NSString *msg, 1464 NSString *defaultButton, 1465 NSString *alternateButton, 1466 NSString *otherButton, ...) 1467{ 1468 va_list ap; 1469 NSString *message; 1470 GSAlertPanel *panel; 1471 NSInteger result; 1472 1473 va_start(ap, otherButton); 1474 message = [NSString stringWithFormat: msg arguments: ap]; 1475 va_end(ap); 1476 1477 if (NSApp == nil) 1478 { 1479 // No NSApp ... not running in a gui application so just log. 1480 NSLog(@"%@", message); 1481 return NSAlertDefaultReturn; 1482 } 1483 if (defaultButton == nil) 1484 { 1485 defaultButton = @"OK"; 1486 } 1487 1488 panel = getSomePanel(&standardAlertPanel, defaultTitle, title, message, 1489 defaultButton, alternateButton, otherButton); 1490 result = [panel runModal]; 1491 NSReleaseAlertPanel(panel); 1492 return result; 1493} 1494 1495NSInteger 1496NSRunLocalizedAlertPanel( 1497 NSString *table, 1498 NSString *title, 1499 NSString *msg, 1500 NSString *defaultButton, 1501 NSString *alternateButton, 1502 NSString *otherButton, ...) 1503{ 1504 va_list ap; 1505 NSString *message; 1506 GSAlertPanel *panel; 1507 NSInteger result; 1508 NSBundle *bundle = [NSBundle mainBundle]; 1509 1510 if (title == nil) 1511 { 1512 title = defaultTitle; 1513 } 1514 1515#define localize(string) if (string != nil) \ 1516 string = [bundle localizedStringForKey: string value: string table: table] 1517 1518 localize(title); 1519 localize(defaultButton); 1520 localize(alternateButton); 1521 localize(otherButton); 1522 localize(msg); 1523 1524#undef localize 1525 1526 va_start(ap, otherButton); 1527 message = [NSString stringWithFormat: msg arguments: ap]; 1528 va_end(ap); 1529 1530 if (defaultButton == nil) 1531 { 1532 defaultButton = @"OK"; 1533 } 1534 1535 panel = getSomePanel(&standardAlertPanel, @"Alert", title, message, 1536 defaultButton, alternateButton, otherButton); 1537 result = [panel runModal]; 1538 NSReleaseAlertPanel(panel); 1539 return result; 1540} 1541 1542 1543 1544id 1545NSGetCriticalAlertPanel( 1546 NSString *title, 1547 NSString *msg, 1548 NSString *defaultButton, 1549 NSString *alternateButton, 1550 NSString *otherButton, ...) 1551{ 1552 va_list ap; 1553 NSString *message; 1554 1555 va_start(ap, otherButton); 1556 message = [NSString stringWithFormat: msg arguments: ap]; 1557 va_end(ap); 1558 1559 return getSomePanel(&criticalAlertPanel, @"Critical", title, message, 1560 defaultButton, alternateButton, otherButton); 1561} 1562 1563NSInteger 1564NSRunCriticalAlertPanel( 1565 NSString *title, 1566 NSString *msg, 1567 NSString *defaultButton, 1568 NSString *alternateButton, 1569 NSString *otherButton, ...) 1570{ 1571 va_list ap; 1572 NSString *message; 1573 GSAlertPanel *panel; 1574 NSInteger result; 1575 1576 va_start(ap, otherButton); 1577 message = [NSString stringWithFormat: msg arguments: ap]; 1578 va_end(ap); 1579 1580 panel = getSomePanel(&criticalAlertPanel, @"Critical", title, message, 1581 defaultButton, alternateButton, otherButton); 1582 result = [panel runModal]; 1583 NSReleaseAlertPanel(panel); 1584 return result; 1585} 1586 1587 1588id 1589NSGetInformationalAlertPanel( 1590 NSString *title, 1591 NSString *msg, 1592 NSString *defaultButton, 1593 NSString *alternateButton, 1594 NSString *otherButton, ...) 1595{ 1596 va_list ap; 1597 NSString *message; 1598 1599 va_start(ap, otherButton); 1600 message = [NSString stringWithFormat: msg arguments: ap]; 1601 va_end(ap); 1602 1603 return getSomePanel(&informationalAlertPanel, @"Information", title, message, 1604 defaultButton, alternateButton, otherButton); 1605} 1606 1607 1608NSInteger 1609NSRunInformationalAlertPanel( 1610 NSString *title, 1611 NSString *msg, 1612 NSString *defaultButton, 1613 NSString *alternateButton, 1614 NSString *otherButton, ...) 1615{ 1616 va_list ap; 1617 NSString *message; 1618 GSAlertPanel *panel; 1619 NSInteger result; 1620 1621 va_start(ap, otherButton); 1622 message = [NSString stringWithFormat: msg arguments: ap]; 1623 va_end(ap); 1624 1625 panel = getSomePanel(&informationalAlertPanel, 1626 @"Information", 1627 title, message, 1628 defaultButton, alternateButton, otherButton); 1629 result = [panel runModal]; 1630 NSReleaseAlertPanel(panel); 1631 return result; 1632} 1633 1634void 1635NSReleaseAlertPanel(id panel) 1636{ 1637 if ((panel != standardAlertPanel) 1638 && (panel != informationalAlertPanel) 1639 && (panel != criticalAlertPanel)) 1640 { 1641 RELEASE(panel); 1642 } 1643} 1644 1645// 1646// New alert interface of Mac OS X 1647// 1648void NSBeginAlertSheet(NSString *title, 1649 NSString *defaultButton, 1650 NSString *alternateButton, 1651 NSString *otherButton, 1652 NSWindow *docWindow, 1653 id modalDelegate, 1654 SEL didEndSelector, 1655 SEL didDismissSelector, 1656 void *contextInfo, 1657 NSString *msg, ...) 1658{ 1659 va_list ap; 1660 NSString *message; 1661 GSAlertPanel *panel; 1662 1663 va_start(ap, msg); 1664 message = [NSString stringWithFormat: msg arguments: ap]; 1665 va_end(ap); 1666 1667 if (defaultButton == nil) 1668 { 1669 defaultButton = @"OK"; 1670 } 1671 1672 panel = getSomeSheet(&standardAlertPanel, defaultTitle, title, message, 1673 defaultButton, alternateButton, otherButton); 1674 1675 // FIXME: We should also change the button action to call endSheet: 1676 [NSApp beginSheet: panel 1677 modalForWindow: docWindow 1678 modalDelegate: modalDelegate 1679 didEndSelector: didEndSelector 1680 contextInfo: contextInfo]; 1681 if (modalDelegate && [modalDelegate respondsToSelector: didDismissSelector]) 1682 { 1683 void (*didDismiss)(id, SEL, id, NSInteger, void*); 1684 didDismiss = (void (*)(id, SEL, id, NSInteger, void*))[modalDelegate 1685 methodForSelector: didDismissSelector]; 1686 didDismiss(modalDelegate, didDismissSelector, panel, [panel result], 1687 contextInfo); 1688 } 1689 1690 NSReleaseAlertPanel(panel); 1691} 1692 1693void NSBeginCriticalAlertSheet(NSString *title, 1694 NSString *defaultButton, 1695 NSString *alternateButton, 1696 NSString *otherButton, 1697 NSWindow *docWindow, 1698 id modalDelegate, 1699 SEL didEndSelector, 1700 SEL didDismissSelector, 1701 void *contextInfo, 1702 NSString *msg, ...) 1703{ 1704 va_list ap; 1705 NSString *message; 1706 GSAlertPanel *panel; 1707 1708 va_start(ap, msg); 1709 message = [NSString stringWithFormat: msg arguments: ap]; 1710 va_end(ap); 1711 1712 panel = getSomeSheet(&criticalAlertPanel, @"Critical", title, message, 1713 defaultButton, alternateButton, otherButton); 1714 // FIXME: We should also change the button action to call endSheet: 1715 [NSApp beginSheet: panel 1716 modalForWindow: docWindow 1717 modalDelegate: modalDelegate 1718 didEndSelector: didEndSelector 1719 contextInfo: contextInfo]; 1720 if (modalDelegate && [modalDelegate respondsToSelector: didDismissSelector]) 1721 { 1722 void (*didDismiss)(id, SEL, id, NSInteger, void*); 1723 didDismiss = (void (*)(id, SEL, id, NSInteger, void*))[modalDelegate 1724 methodForSelector: didDismissSelector]; 1725 didDismiss(modalDelegate, didDismissSelector, panel, [panel result], 1726 contextInfo); 1727 } 1728 1729 NSReleaseAlertPanel(panel); 1730} 1731 1732void NSBeginInformationalAlertSheet(NSString *title, 1733 NSString *defaultButton, 1734 NSString *alternateButton, 1735 NSString *otherButton, 1736 NSWindow *docWindow, 1737 id modalDelegate, 1738 SEL didEndSelector, 1739 SEL didDismissSelector, 1740 void *contextInfo, 1741 NSString *msg, ...) 1742{ 1743 va_list ap; 1744 NSString *message; 1745 GSAlertPanel *panel; 1746 1747 va_start(ap, msg); 1748 message = [NSString stringWithFormat: msg arguments: ap]; 1749 va_end(ap); 1750 1751 panel = getSomeSheet(&informationalAlertPanel, 1752 @"Information", 1753 title, message, 1754 defaultButton, alternateButton, otherButton); 1755 // FIXME: We should also change the button action to call endSheet: 1756 [NSApp beginSheet: panel 1757 modalForWindow: docWindow 1758 modalDelegate: modalDelegate 1759 didEndSelector: didEndSelector 1760 contextInfo: contextInfo]; 1761 if (modalDelegate && [modalDelegate respondsToSelector: didDismissSelector]) 1762 { 1763 void (*didDismiss)(id, SEL, id, NSInteger, void*); 1764 didDismiss = (void (*)(id, SEL, id, NSInteger, void*))[modalDelegate 1765 methodForSelector: didDismissSelector]; 1766 didDismiss(modalDelegate, didDismissSelector, panel, [panel result], 1767 contextInfo); 1768 } 1769 1770 NSReleaseAlertPanel(panel); 1771} 1772 1773 1774@implementation NSAlert 1775 1776/* 1777 * Class methods 1778 */ 1779+ (void) initialize 1780{ 1781 if (self == [NSAlert class]) 1782 { 1783 [self setVersion: 1]; 1784 } 1785} 1786 1787+ (NSAlert *) alertWithError: (NSError *)error 1788{ 1789 NSArray *options; 1790 NSUInteger count; 1791 NSString *errorText; 1792 1793 errorText = [error localizedFailureReason]; 1794 if (errorText == nil) 1795 { 1796 errorText = [error localizedDescription]; 1797 } 1798 1799 options = [error localizedRecoveryOptions]; 1800 count = [options count]; 1801 return [self alertWithMessageText: errorText 1802 defaultButton: (count > 0) ? [options objectAtIndex: 0] : nil 1803 alternateButton: (count > 1) ? [options objectAtIndex: 1] : nil 1804 otherButton: (count > 2) ? [options objectAtIndex: 2] : nil 1805 informativeTextWithFormat: [error localizedRecoverySuggestion]]; 1806} 1807 1808+ (NSAlert *) alertWithMessageText: (NSString *)messageTitle 1809 defaultButton: (NSString *)defaultButtonTitle 1810 alternateButton: (NSString *)alternateButtonTitle 1811 otherButton: (NSString *)otherButtonTitle 1812 informativeTextWithFormat: (NSString *)format, ... 1813{ 1814 va_list ap; 1815 NSAlert *alert = [[self alloc] init]; 1816 NSButton *but; 1817 NSString *text; 1818 1819 va_start(ap, format); 1820 if (format != nil) 1821 { 1822 text = [[NSString alloc] initWithFormat: format arguments: ap]; 1823 [alert setInformativeText: text]; 1824 RELEASE(text); 1825 } 1826 va_end(ap); 1827 1828 [alert setMessageText: messageTitle]; 1829 1830 if (defaultButtonTitle != nil) 1831 { 1832 but = [alert addButtonWithTitle: defaultButtonTitle]; 1833 } 1834 else 1835 { 1836 but = [alert addButtonWithTitle: _(@"OK")]; 1837 } 1838 [but setTag: NSAlertDefaultReturn]; 1839 1840 if (alternateButtonTitle != nil) 1841 { 1842 but = [alert addButtonWithTitle: alternateButtonTitle]; 1843 [but setTag: NSAlertAlternateReturn]; 1844 } 1845 1846 if (otherButtonTitle != nil) 1847 { 1848 but = [alert addButtonWithTitle: otherButtonTitle]; 1849 [but setTag: NSAlertOtherReturn]; 1850 } 1851 1852 return AUTORELEASE(alert); 1853} 1854 1855- (id) init 1856{ 1857 _buttons = [[NSMutableArray alloc] init]; 1858 _style = NSWarningAlertStyle; 1859 return self; 1860} 1861 1862- (void) dealloc 1863{ 1864 RELEASE(_informative_text); 1865 RELEASE(_message_text); 1866 RELEASE(_icon); 1867 RELEASE(_buttons); 1868 RELEASE(_help_anchor); 1869 RELEASE(_window); 1870 [super dealloc]; 1871} 1872 1873- (void) setInformativeText: (NSString *)informativeText 1874{ 1875 ASSIGN(_informative_text, informativeText); 1876} 1877 1878- (NSString *) informativeText 1879{ 1880 return _informative_text; 1881} 1882 1883- (void) setMessageText: (NSString *)messageText 1884{ 1885 ASSIGN(_message_text, messageText); 1886} 1887 1888- (NSString *) messageText 1889{ 1890 return _message_text; 1891} 1892 1893- (void) setIcon: (NSImage *)icon 1894{ 1895 ASSIGN(_icon, icon); 1896} 1897 1898- (NSImage *) icon 1899{ 1900 return _icon; 1901} 1902 1903- (NSButton *) addButtonWithTitle: (NSString *)aTitle 1904{ 1905 NSButton *button = [[NSButton alloc] init]; 1906 NSUInteger count = [_buttons count]; 1907 1908 [button setTitle: aTitle]; 1909 [button setAutoresizingMask: NSViewMinXMargin | NSViewMaxYMargin]; 1910 [button setButtonType: NSMomentaryPushButton]; 1911 [button setTarget: self]; 1912 [button setAction: @selector(buttonAction:)]; 1913 [button setFont: [NSFont systemFontOfSize: 0]]; 1914 if (count == 0) 1915 { 1916 [button setTag: NSAlertFirstButtonReturn]; 1917 [button setKeyEquivalent: @"\r"]; 1918 } 1919 else 1920 { 1921 [button setTag: NSAlertFirstButtonReturn + count]; 1922 setKeyEquivalent(button); 1923 } 1924 1925 [_buttons addObject: button]; 1926 RELEASE(button); 1927 return button; 1928} 1929 1930- (NSArray *) buttons 1931{ 1932 return _buttons; 1933} 1934 1935- (void) setShowsHelp: (BOOL)showsHelp 1936{ 1937 _shows_help = showsHelp; 1938} 1939 1940- (BOOL) showsHelp 1941{ 1942 return _shows_help; 1943} 1944 1945- (void) setHelpAnchor: (NSString *)anchor 1946{ 1947 ASSIGN(_help_anchor, anchor); 1948} 1949 1950- (NSString *) helpAnchor 1951{ 1952 return _help_anchor; 1953} 1954 1955- (void) setAlertStyle: (NSAlertStyle)style 1956{ 1957 _style = style; 1958} 1959 1960- (NSAlertStyle) alertStyle 1961{ 1962 return _style; 1963} 1964 1965- (void) setDelegate: (id)delegate 1966{ 1967 _delegate = delegate; 1968} 1969 1970- (id) delegate 1971{ 1972 return _delegate; 1973} 1974 1975- (void) _setupPanel 1976{ 1977 if (GSCurrentThread() != GSAppKitThread) 1978 { 1979 [self performSelectorOnMainThread: _cmd 1980 withObject: nil 1981 waitUntilDone: YES]; 1982 } 1983 else 1984 { 1985 GSAlertPanel *panel; 1986 NSString *title; 1987 1988 panel = [[GSAlertPanel alloc] init]; 1989 _window = panel; 1990 1991 switch (_style) 1992 { 1993 case NSCriticalAlertStyle: 1994 title = @"Critical"; 1995 break; 1996 case NSInformationalAlertStyle: 1997 title = @"Information"; 1998 break; 1999 case NSWarningAlertStyle: 2000 default: 2001 title = @"Alert"; 2002 break; 2003 } 2004 [panel setTitleBar: title 2005 icon: _icon 2006 title: _message_text != nil ? _message_text : _(@"Alert") 2007 message: _informative_text != nil ? _informative_text : _(@"No information")]; 2008 [panel setButtons: _buttons]; 2009 } 2010} 2011 2012- (NSInteger) runModal 2013{ 2014 if (GSCurrentThread() != GSAppKitThread) 2015 { 2016 [self performSelectorOnMainThread: _cmd 2017 withObject: nil 2018 waitUntilDone: YES]; 2019 return _result; 2020 } 2021 else 2022 { 2023 [self _setupPanel]; 2024 [NSApp runModalForWindow: _window]; 2025 [_window orderOut: self]; 2026 _result = [(GSAlertPanel*)_window result]; 2027 DESTROY(_window); 2028 return _result; 2029 } 2030} 2031 2032- (void) beginSheetModalForWindow: (NSWindow *)window 2033 modalDelegate: (id)delegate 2034 didEndSelector: (SEL)didEndSelector 2035 contextInfo: (void *)contextInfo 2036{ 2037 [self _setupPanel]; 2038 _modalDelegate = delegate; 2039 _didEndSelector = didEndSelector; 2040 [NSApp beginSheet: _window 2041 modalForWindow: window 2042 modalDelegate: self 2043 didEndSelector: @selector(_alertDidEnd:returnCode:contextInfo:) 2044 contextInfo: contextInfo]; 2045 DESTROY(_window); 2046} 2047 2048- (void) _alertDidEnd: (NSWindow *)sheet 2049 returnCode: (NSInteger)returnCode 2050 contextInfo: (void *)contextInfo 2051{ 2052 if ([_modalDelegate respondsToSelector: _didEndSelector]) 2053 { 2054 void (*didEnd)(id, SEL, id, NSInteger, void *); 2055 didEnd = (void (*)(id, SEL, id, NSInteger, void *))[_modalDelegate 2056 methodForSelector: _didEndSelector]; 2057 didEnd(_modalDelegate, _didEndSelector, self, returnCode, contextInfo); 2058 } 2059} 2060 2061- (id) window 2062{ 2063 return _window; 2064} 2065 2066@end 2067 2068@interface GSExceptionPanel : GSAlertPanel 2069{ 2070 NSBrowser *_browser; 2071 NSDictionary *_userInfo; 2072 NSPanel *_userInfoPanel; 2073} 2074- (void) setUserInfo: (NSDictionary *)userInfo; 2075- (NSPanel *) userInfoPanel; 2076@end 2077 2078NSInteger GSRunExceptionPanel( 2079 NSString *title, 2080 NSException *exception, 2081 NSString *defaultButton, 2082 NSString *alternateButton, 2083 NSString *otherButton) 2084{ 2085 NSString *message; 2086 GSExceptionPanel *panel; 2087 NSInteger result; 2088 2089 message = [NSString stringWithFormat: @"%@: %@", 2090 [exception name], 2091 [exception reason]]; 2092 if (defaultButton == nil) 2093 { 2094 defaultButton = @"OK"; 2095 } 2096 2097 panel = [[GSExceptionPanel alloc] init]; 2098 2099 if (title == nil) 2100 { 2101 title = @"Exception"; 2102 } 2103 2104 [panel setTitleBar: nil 2105 icon: nil 2106 title: title 2107 message: message 2108 def: defaultButton 2109 alt: alternateButton 2110 other: otherButton]; 2111 [panel setUserInfo: [exception userInfo]]; 2112 result = [panel runModal]; 2113 [[panel userInfoPanel] orderOut: nil]; 2114 [panel setUserInfo: nil]; 2115 2116 RELEASE(panel); 2117 return result; 2118} 2119 2120@implementation GSExceptionPanel 2121- (void) dealloc 2122{ 2123 RELEASE(_userInfo); 2124 RELEASE(_browser); 2125 RELEASE(_userInfoPanel); 2126 [super dealloc]; 2127} 2128 2129- (id) init 2130{ 2131 if ((self = [super init])) 2132 { 2133 [icoButton setEnabled: YES]; 2134 [icoButton setTarget: self]; 2135 [icoButton setAction: @selector(_icoAction:)]; 2136 } 2137 2138 return self; 2139} 2140 2141- (NSPanel *) userInfoPanel 2142{ 2143 return _userInfoPanel; 2144} 2145 2146- (void) setUserInfo: (NSDictionary *)userInfo; 2147{ 2148 ASSIGN(_userInfo, userInfo); 2149 [_browser reloadColumn: 0]; 2150} 2151 2152- (void) _icoAction: (id)sender 2153{ 2154 NSRect fr; 2155 2156 if (_userInfoPanel) 2157 { 2158 [_browser reloadColumn: 0]; 2159 return; 2160 } 2161 2162 fr = NSMakeRect(_frame.origin.x, _frame.origin.y + _frame.size.height + 15, 2163 _frame.size.width, 108); 2164 _userInfoPanel = [[NSPanel alloc] initWithContentRect: fr 2165 styleMask: NSTitledWindowMask | NSResizableWindowMask 2166 backing: NSBackingStoreBuffered 2167 defer: NO]; 2168 [_userInfoPanel setTitle: @"User Info Inspector"]; 2169 [_userInfoPanel setWorksWhenModal: YES]; 2170 2171 fr = NSMakeRect(8, 8, _frame.size.width - 16, 100); 2172 _browser = [[NSBrowser alloc] initWithFrame: fr]; 2173 [_browser setMaxVisibleColumns: 2]; 2174 [_browser setDelegate: self]; 2175 [_browser setAutoresizingMask: NSViewWidthSizable | NSViewHeightSizable]; 2176 [_browser reloadColumn: 0]; 2177 [[_userInfoPanel contentView] addSubview:_browser]; 2178 [_userInfoPanel makeKeyAndOrderFront: self]; 2179} 2180 2181- (NSInteger) browser: (id)browser 2182numberOfRowsInColumn: (NSInteger)col 2183{ 2184 if (col == 0) 2185 return [[_userInfo allKeys] count]; 2186 else 2187 { 2188 id val; 2189 volatile id foo = nil; 2190 2191 val = [[(NSCell *)[browser selectedCellInColumn: col - 1] 2192 representedObject] description]; 2193 NS_DURING 2194 foo = [val propertyList]; 2195 val = foo; 2196 NS_HANDLER 2197 NS_ENDHANDLER 2198 2199 if ([val isKindOfClass: [NSArray class]]) 2200 return [val count]; 2201 else if ([val isKindOfClass: [NSDictionary class]]) 2202 return [[val allKeys] count]; 2203 else return val != nil; 2204 } 2205 return 0; 2206} 2207 2208- (void) browser: (NSBrowser *)browser 2209 willDisplayCell: (NSBrowserCell *)cell 2210 atRow: (NSInteger)row 2211 column: (NSInteger)column 2212{ 2213 if (column == 0) 2214 { 2215 id key = [[_userInfo allKeys] objectAtIndex: row]; 2216 id val = [_userInfo objectForKey: key]; 2217 2218 [cell setLeaf: NO]; 2219 [cell setStringValue: [key description]]; 2220 [cell setRepresentedObject: val]; 2221 } 2222 else 2223 { 2224 volatile id val; 2225 BOOL flag; 2226 2227 val = [(NSCell *)[browser selectedCellInColumn: column - 1] 2228 representedObject]; 2229 if (!([val isKindOfClass: [NSArray class]] 2230 || [val isKindOfClass: [NSArray class]])) 2231 { 2232 volatile id foo = nil; 2233 val = [val description]; 2234 NS_DURING 2235 foo = [val propertyList]; 2236 val = foo; 2237 NS_HANDLER 2238 NS_ENDHANDLER 2239 } 2240 flag = (!([val isKindOfClass: [NSArray class]] 2241 || [val isKindOfClass: [NSDictionary class]])); 2242 2243 [cell setLeaf: flag]; 2244 2245 if ([val isKindOfClass: [NSArray class]]) 2246 { 2247 volatile id obj = [val objectAtIndex: row]; 2248 2249 if (!([obj isKindOfClass: [NSArray class]] 2250 || [obj isKindOfClass: [NSArray class]])) 2251 { 2252 volatile id foo; 2253 obj = [[obj description] propertyList]; 2254 NS_DURING 2255 foo = [obj propertyList]; 2256 obj = foo; 2257 NS_HANDLER 2258 NS_ENDHANDLER 2259 } 2260 2261 if ([obj isKindOfClass: [NSArray class]]) 2262 { 2263 [cell setRepresentedObject: obj]; 2264 [cell setLeaf: NO]; 2265 [cell setStringValue: 2266 [NSString stringWithFormat: @"%@ %p", [obj class], obj]]; 2267 } 2268 else if ([obj isKindOfClass: [NSDictionary class]]) 2269 { 2270 [cell setRepresentedObject: obj]; 2271 [cell setLeaf: NO]; 2272 [cell setStringValue: 2273 [NSString stringWithFormat: @"%@ %p", [obj class], obj]]; 2274 } 2275 else 2276 { 2277 [cell setLeaf: YES]; 2278 [cell setStringValue: [obj description]]; 2279 [cell setRepresentedObject: nil]; 2280 } 2281 } 2282 else if ([val isKindOfClass: [NSDictionary class]]) 2283 { 2284 id key = [[val allKeys] objectAtIndex: row]; 2285 volatile id it = [(NSDictionary *)val objectForKey: key]; 2286 volatile id foo; 2287 foo = [it description]; 2288 NS_DURING 2289 foo = [it propertyList]; 2290 it = foo; 2291 NS_HANDLER 2292 NS_ENDHANDLER 2293 [cell setStringValue: [key description]]; 2294 [cell setRepresentedObject: it]; 2295 } 2296 else 2297 { 2298 [cell setLeaf: YES]; 2299 [cell setStringValue: [val description]]; 2300 } 2301 } 2302} 2303 2304- (id) browser: (NSBrowser *)browser titleOfColumn: (NSInteger)column 2305{ 2306 id val; 2307 NSString *title; 2308 2309 if (column == 0) 2310 return @"userInfo"; 2311 val = [(NSCell *)[browser selectedCellInColumn: column - 1] 2312 representedObject]; 2313 title = [NSString stringWithFormat: @"%@ %p", [val class], val]; 2314 return title; 2315} 2316@end 2317 2318