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