transmission 2.51 update
[tomato.git] / release / src / router / transmission / macosx / CreatorWindowController.m
blobc639c94f7965a5b17652454fd544b01a77e00592
1 /******************************************************************************
2  * $Id: CreatorWindowController.m 13251 2012-03-13 02:52:11Z livings124 $
3  *
4  * Copyright (c) 2007-2012 Transmission authors and contributors
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a
7  * copy of this software and associated documentation files (the "Software"),
8  * to deal in the Software without restriction, including without limitation
9  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10  * and/or sell copies of the Software, and to permit persons to whom the
11  * Software is furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in
14  * all copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22  * DEALINGS IN THE SOFTWARE.
23  *****************************************************************************/
25 #import "CreatorWindowController.h"
26 #import "NSStringAdditions.h"
28 #import "transmission.h" // required by utils.h
29 #import "utils.h" // tr_urlIsValidTracker()
31 #define TRACKER_ADD_TAG 0
32 #define TRACKER_REMOVE_TAG 1
34 @interface CreatorWindowController (Private)
36 + (NSString *) chooseFile;
38 - (void) createBlankAddressAlertDidEnd: (NSAlert *) alert returnCode: (NSInteger) returnCode contextInfo: (void *) contextInfo;
39 - (void) createReal;
40 - (void) checkProgress;
41 - (void) failureSheetClosed: (NSAlert *) alert returnCode: (NSInteger) code contextInfo: (void *) info;
43 @end
45 @implementation CreatorWindowController
47 + (void) createTorrentFile: (tr_session *) handle
49     //get file/folder for torrent
50     NSString * path;
51     if (!(path = [CreatorWindowController chooseFile]))
52         return;
53     
54     CreatorWindowController * creator = [[self alloc] initWithHandle: handle path: path];
55     [creator showWindow: nil];
58 + (void) createTorrentFile: (tr_session *) handle forFile: (NSString *) file
60     CreatorWindowController * creator = [[self alloc] initWithHandle: handle path: file];
61     [creator showWindow: nil];
64 - (id) initWithHandle: (tr_session *) handle path: (NSString *) path
66     if ((self = [super initWithWindowNibName: @"Creator"]))
67     {
68         fStarted = NO;
69         
70         fPath = [path retain];
71         fInfo = tr_metaInfoBuilderCreate([fPath UTF8String]);
72         
73         if (fInfo->fileCount == 0)
74         {
75             NSAlert * alert = [[NSAlert alloc] init];
76             [alert addButtonWithTitle: NSLocalizedString(@"OK", "Create torrent -> no files -> button")];
77             [alert setMessageText: NSLocalizedString(@"This folder contains no files.",
78                                                     "Create torrent -> no files -> title")];
79             [alert setInformativeText: NSLocalizedString(@"There must be at least one file in a folder to create a torrent file.",
80                                                         "Create torrent -> no files -> warning")];
81             [alert setAlertStyle: NSWarningAlertStyle];
82             
83             [alert runModal];
84             [alert release];
85             
86             [self release];
87             return nil;
88         }
89         if (fInfo->totalSize == 0)
90         {
91             NSAlert * alert = [[NSAlert alloc] init];
92             [alert addButtonWithTitle: NSLocalizedString(@"OK", "Create torrent -> zero size -> button")];
93             [alert setMessageText: NSLocalizedString(@"The total file size is zero bytes.",
94                                                     "Create torrent -> zero size -> title")];
95             [alert setInformativeText: NSLocalizedString(@"A torrent file cannot be created for files with no size.",
96                                                         "Create torrent -> zero size -> warning")];
97             [alert setAlertStyle: NSWarningAlertStyle];
98             
99             [alert runModal];
100             [alert release];
101             
102             [self release];
103             return nil;
104         }
105         
106         fDefaults = [NSUserDefaults standardUserDefaults];
107         
108         //get list of trackers
109         if (!(fTrackers = [[fDefaults arrayForKey: @"CreatorTrackers"] mutableCopy]))
110         {
111             fTrackers = [[NSMutableArray alloc] initWithCapacity: 1];
112             
113             //check for single tracker from versions before 1.3
114             NSString * tracker;
115             if ((tracker = [fDefaults stringForKey: @"CreatorTracker"]))
116             {
117                 [fDefaults removeObjectForKey: @"CreatorTracker"];
118                 if (![tracker isEqualToString: @""])
119                 {
120                     [fTrackers addObject: tracker];
121                     [fDefaults setObject: fTrackers forKey: @"CreatorTrackers"];
122                 }
123             }
124         }
125         
126         //remove potentially invalid addresses
127         for (NSInteger i = [fTrackers count]-1; i >= 0; i--)
128         {
129             if (!tr_urlIsValidTracker([[fTrackers objectAtIndex: i] UTF8String]))
130                 [fTrackers removeObjectAtIndex: i];
131         }
132     }
133     return self;
136 - (void) awakeFromNib
138     NSString * name = [fPath lastPathComponent];
139     
140     [[self window] setTitle: name];
141     
142     [fNameField setStringValue: name];
143     [fNameField setToolTip: fPath];
144     
145     const BOOL multifile = !fInfo->isSingleFile;
146     
147     NSImage * icon = [[NSWorkspace sharedWorkspace] iconForFileType: multifile
148                         ? NSFileTypeForHFSTypeCode(kGenericFolderIcon) : [fPath pathExtension]];
149     [icon setSize: [fIconView frame].size];
150     [fIconView setImage: icon];
151     
152     NSString * statusString = [NSString stringForFileSize: fInfo->totalSize];
153     if (multifile)
154     {
155         NSString * fileString;
156         NSInteger count = fInfo->fileCount;
157         if (count != 1)
158             fileString = [NSString stringWithFormat: NSLocalizedString(@"%@ files", "Create torrent -> info"),
159                             [NSString formattedUInteger: count]];
160         else
161             fileString = NSLocalizedString(@"1 file", "Create torrent -> info");
162         statusString = [NSString stringWithFormat: @"%@, %@", fileString, statusString];
163     }
164     [fStatusField setStringValue: statusString];
165     
166     if (fInfo->pieceCount == 1)
167         [fPiecesField setStringValue: [NSString stringWithFormat: NSLocalizedString(@"1 piece, %@", "Create torrent -> info"),
168                                                             [NSString stringForFileSize: fInfo->pieceSize]]];
169     else
170         [fPiecesField setStringValue: [NSString stringWithFormat: NSLocalizedString(@"%d pieces, %@ each", "Create torrent -> info"),
171                                                             fInfo->pieceCount, [NSString stringForFileSize: fInfo->pieceSize]]];
172     
173     fLocation = [[[[fDefaults stringForKey: @"CreatorLocation"] stringByExpandingTildeInPath] stringByAppendingPathComponent:
174                     [name stringByAppendingPathExtension: @"torrent"]] retain];
175     [fLocationField setStringValue: [fLocation stringByAbbreviatingWithTildeInPath]];
176     [fLocationField setToolTip: fLocation];
177     
178     //set previously saved values
179     if ([fDefaults objectForKey: @"CreatorPrivate"])
180         [fPrivateCheck setState: [fDefaults boolForKey: @"CreatorPrivate"] ? NSOnState : NSOffState];
181     
182     [fOpenCheck setState: [fDefaults boolForKey: @"CreatorOpen"] ? NSOnState : NSOffState];
185 - (void) dealloc
187     [fPath release];
188     [fLocation release];
189     
190     [fTrackers release];
191     
192     if (fInfo)
193         tr_metaInfoBuilderFree(fInfo);
194     
195     [fTimer invalidate];
196     
197     [super dealloc];
200 - (void) setLocation: (id) sender
202     NSSavePanel * panel = [NSSavePanel savePanel];
204     [panel setPrompt: NSLocalizedString(@"Select", "Create torrent -> location sheet -> button")];
205     [panel setMessage: NSLocalizedString(@"Select the name and location for the torrent file.",
206                                         "Create torrent -> location sheet -> message")]; 
207     
208     [panel setAllowedFileTypes: [NSArray arrayWithObjects: @"org.bittorrent.torrent", @"torrent", nil]];
209     [panel setCanSelectHiddenExtension: YES];
210     
211     [panel setDirectoryURL: [NSURL fileURLWithPath: [fLocation stringByDeletingLastPathComponent]]];
212     [panel setNameFieldStringValue: [fLocation lastPathComponent]];
213     
214     [panel beginSheetModalForWindow: [self window] completionHandler: ^(NSInteger result) {
215         if (result == NSFileHandlingPanelOKButton)
216         {
217             [fLocation release];
218             fLocation = [[[panel URL] path] retain];
219             
220             [fLocationField setStringValue: [fLocation stringByAbbreviatingWithTildeInPath]];
221             [fLocationField setToolTip: fLocation];
222         }
223     }];
226 - (void) create: (id) sender
228     //make sure the trackers are no longer being verified
229     if ([fTrackerTable editedRow] != -1)
230         [[self window] endEditingFor: fTrackerTable];
231     
232     const BOOL isPrivate = [fPrivateCheck state] == NSOnState;
233     if ([fTrackers count] == 0
234         && [fDefaults boolForKey: isPrivate ? @"WarningCreatorPrivateBlankAddress" : @"WarningCreatorBlankAddress"])
235     {
236         NSAlert * alert = [[NSAlert alloc] init];
237         [alert setMessageText: NSLocalizedString(@"There are no tracker addresses.", "Create torrent -> blank address -> title")];
238         
239         NSString * infoString = isPrivate
240                     ? NSLocalizedString(@"A transfer marked as private with no tracker addresses will be unable to connect to peers."
241                         " The torrent file will only be useful if you plan to upload the file to a tracker website"
242                         " that will add the addresses for you.", "Create torrent -> blank address -> message")
243                     : NSLocalizedString(@"The transfer will not contact trackers for peers, and will have to rely solely on"
244                         " non-tracker peer discovery methods such as PEX and DHT to download and seed.",
245                         "Create torrent -> blank address -> message");
246         
247         [alert setInformativeText: infoString];
248         [alert addButtonWithTitle: NSLocalizedString(@"Create", "Create torrent -> blank address -> button")];
249         [alert addButtonWithTitle: NSLocalizedString(@"Cancel", "Create torrent -> blank address -> button")];
250         [alert setShowsSuppressionButton: YES];
252         [alert beginSheetModalForWindow: [self window] modalDelegate: self
253             didEndSelector: @selector(createBlankAddressAlertDidEnd:returnCode:contextInfo:) contextInfo: nil];
254     }
255     else
256         [self createReal];
259 - (void) cancelCreateWindow: (id) sender
261     [[self window] close];
264 - (void) windowWillClose: (NSNotification *) notification
266     [self autorelease];
269 - (void) cancelCreateProgress: (id) sender
271     fInfo->abortFlag = 1;
272     [fTimer fire];
275 - (NSInteger) numberOfRowsInTableView: (NSTableView *) tableView
277     return [fTrackers count];
280 - (id) tableView: (NSTableView *) tableView objectValueForTableColumn: (NSTableColumn *) tableColumn row: (NSInteger) row
282     return [fTrackers objectAtIndex: row];
285 - (void) addRemoveTracker: (id) sender
287     //don't allow add/remove when currently adding - it leads to weird results
288     if ([fTrackerTable editedRow] != -1)
289         return;
290     
291     if ([[sender cell] tagForSegment: [sender selectedSegment]] == TRACKER_REMOVE_TAG)
292     {
293         [fTrackers removeObjectsAtIndexes: [fTrackerTable selectedRowIndexes]];
294         
295         [fTrackerTable deselectAll: self];
296         [fTrackerTable reloadData];
297     }
298     else
299     {
300         [fTrackers addObject: @""];
301         [fTrackerTable reloadData];
302         
303         const NSInteger row = [fTrackers count] - 1;
304         [fTrackerTable selectRowIndexes: [NSIndexSet indexSetWithIndex: row] byExtendingSelection: NO];
305         [fTrackerTable editColumn: 0 row: row withEvent: nil select: YES];
306     }
309 - (void) tableView: (NSTableView *) tableView setObjectValue: (id) object forTableColumn: (NSTableColumn *) tableColumn
310     row: (NSInteger) row
312     NSString * tracker = (NSString *)object;
313     
314     tracker = [tracker stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]];
315     
316     if ([tracker rangeOfString: @"://"].location == NSNotFound)
317         tracker = [@"http://" stringByAppendingString: tracker];
318     
319     if (!tr_urlIsValidTracker([tracker UTF8String]))
320     {
321         NSBeep();
322         [fTrackers removeObjectAtIndex: row];
323     }
324     else
325         [fTrackers replaceObjectAtIndex: row withObject: tracker];
326     
327     [fTrackerTable deselectAll: self];
328     [fTrackerTable reloadData];
331 - (void) tableViewSelectionDidChange: (NSNotification *) notification
333     [fTrackerAddRemoveControl setEnabled: [fTrackerTable numberOfSelectedRows] > 0 forSegment: TRACKER_REMOVE_TAG];
336 - (void) copy: (id) sender
338     NSArray * addresses = [fTrackers objectsAtIndexes: [fTrackerTable selectedRowIndexes]];
339     NSString * text = [addresses componentsJoinedByString: @"\n"];
340     
341     NSPasteboard * pb = [NSPasteboard generalPasteboard];
342     [pb clearContents];
343     [pb writeObjects: [NSArray arrayWithObject: text]];
346 - (BOOL) validateMenuItem: (NSMenuItem *) menuItem
348     const SEL action = [menuItem action];
349     
350     if (action == @selector(copy:))
351         return [[self window] firstResponder] == fTrackerTable && [fTrackerTable numberOfSelectedRows] > 0;
352     
353     if (action == @selector(paste:))
354         return [[self window] firstResponder] == fTrackerTable
355             && [[NSPasteboard generalPasteboard] canReadObjectForClasses: [NSArray arrayWithObject: [NSString class]] options: nil];
356     
357     return YES;
360 - (void) paste: (id) sender
362     NSMutableArray * tempTrackers = [NSMutableArray array];
363     
364     NSArray * items = [[NSPasteboard generalPasteboard] readObjectsForClasses: [NSArray arrayWithObject: [NSString class]] options: nil];
365     NSAssert(items != nil, @"no string items to paste; should not be able to call this method");
366     
367     for (NSString * pbItem in items)
368     {
369         for (NSString * tracker in [pbItem componentsSeparatedByString: @"\n"])
370             [tempTrackers addObject: tracker];
371     }
372     
373     BOOL added = NO;
374     
375     for (NSString * tracker in tempTrackers)
376     {
377         tracker = [tracker stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]];
378         
379         if ([tracker rangeOfString: @"://"].location == NSNotFound)
380             tracker = [@"http://" stringByAppendingString: tracker];
381         
382         if (tr_urlIsValidTracker([tracker UTF8String]))
383         {
384             [fTrackers addObject: tracker];
385             added = YES;
386         }
387     }
388     
389     if (added)
390     {
391         [fTrackerTable deselectAll: self];
392         [fTrackerTable reloadData];
393     }
394     else
395         NSBeep();
398 @end
400 @implementation CreatorWindowController (Private)
402 + (NSString *) chooseFile
404     NSOpenPanel * panel = [NSOpenPanel openPanel];
405     
406     [panel setTitle: NSLocalizedString(@"Create Torrent File", "Create torrent -> select file")];
407     [panel setPrompt: NSLocalizedString(@"Select", "Create torrent -> select file")];
408     [panel setAllowsMultipleSelection: NO];
409     [panel setCanChooseFiles: YES];
410     [panel setCanChooseDirectories: YES];
411     [panel setCanCreateDirectories: NO];
413     [panel setMessage: NSLocalizedString(@"Select a file or folder for the torrent file.", "Create torrent -> select file")];
414     
415     BOOL success = [panel runModal] == NSOKButton;
416     return success ? [[[panel URLs] objectAtIndex: 0] path] : nil;
419 - (void) createBlankAddressAlertDidEnd: (NSAlert *) alert returnCode: (NSInteger) returnCode contextInfo: (void *) contextInfo
421     if ([[alert suppressionButton] state] == NSOnState)
422     {
423         [[NSUserDefaults standardUserDefaults] setBool: NO forKey: @"WarningCreatorBlankAddress"]; //set regardless of private/public
424         if ([fPrivateCheck state] == NSOnState)
425             [[NSUserDefaults standardUserDefaults] setBool: NO forKey: @"WarningCreatorPrivateBlankAddress"];
426     }
427     
428     [alert release];
429     
430     if (returnCode == NSAlertFirstButtonReturn)
431         [self performSelectorOnMainThread: @selector(createReal) withObject: nil waitUntilDone: NO];
434 - (void) createReal
436     //check if the location currently exists
437     if (![[NSFileManager defaultManager] fileExistsAtPath: [fLocation stringByDeletingLastPathComponent]])
438     {
439         NSAlert * alert = [[[NSAlert alloc] init] autorelease];
440         [alert addButtonWithTitle: NSLocalizedString(@"OK", "Create torrent -> directory doesn't exist warning -> button")];
441         [alert setMessageText: NSLocalizedString(@"The chosen torrent file location does not exist.",
442                                                 "Create torrent -> directory doesn't exist warning -> title")];
443         [alert setInformativeText: [NSString stringWithFormat:
444                 NSLocalizedString(@"The directory \"%@\" does not currently exist. "
445                     "Create this directory or choose a different one to create the torrent file.",
446                     "Create torrent -> directory doesn't exist warning -> warning"),
447                     [fLocation stringByDeletingLastPathComponent]]];
448         [alert setAlertStyle: NSWarningAlertStyle];
449         
450         [alert beginSheetModalForWindow: [self window] modalDelegate: self didEndSelector: nil contextInfo: nil];
451         return;
452     }
453     
454     //check if a file with the same name and location already exists
455     if ([[NSFileManager defaultManager] fileExistsAtPath: fLocation])
456     {
457         NSArray * pathComponents = [fLocation pathComponents];
458         NSInteger count = [pathComponents count];
459         
460         NSAlert * alert = [[[NSAlert alloc] init] autorelease];
461         [alert addButtonWithTitle: NSLocalizedString(@"OK", "Create torrent -> file already exists warning -> button")];
462         [alert setMessageText: NSLocalizedString(@"A torrent file with this name and directory cannot be created.",
463                                                 "Create torrent -> file already exists warning -> title")];
464         [alert setInformativeText: [NSString stringWithFormat:
465                 NSLocalizedString(@"A file with the name \"%@\" already exists in the directory \"%@\". "
466                     "Choose a new name or directory to create the torrent file.",
467                     "Create torrent -> file already exists warning -> warning"),
468                     [pathComponents objectAtIndex: count-1], [pathComponents objectAtIndex: count-2]]];
469         [alert setAlertStyle: NSWarningAlertStyle];
470         
471         [alert beginSheetModalForWindow: [self window] modalDelegate: self didEndSelector: nil contextInfo: nil];
472         return;
473     }
474     
475     //parse non-empty tracker strings
476     tr_tracker_info * trackerInfo = tr_new0(tr_tracker_info, [fTrackers count]);
477     
478     for (NSUInteger i = 0; i < [fTrackers count]; i++)
479     {
480         trackerInfo[i].announce = (char *)[[fTrackers objectAtIndex: i] UTF8String];
481         trackerInfo[i].tier = i;
482     }
483     
484     //store values
485     [fDefaults setObject: fTrackers forKey: @"CreatorTrackers"];
486     [fDefaults setBool: [fPrivateCheck state] == NSOnState forKey: @"CreatorPrivate"];
487     [fDefaults setBool: [fOpenCheck state] == NSOnState forKey: @"CreatorOpen"];
488     [fDefaults setObject: [fLocation stringByDeletingLastPathComponent] forKey: @"CreatorLocation"];
489     
490     [[NSNotificationCenter defaultCenter] postNotificationName: @"BeginCreateTorrentFile" object: fLocation userInfo: nil];
491     tr_makeMetaInfo(fInfo, [fLocation UTF8String], trackerInfo, [fTrackers count], [[fCommentView string] UTF8String],
492                     [fPrivateCheck state] == NSOnState);
493     tr_free(trackerInfo);
494     
495     fTimer = [NSTimer scheduledTimerWithTimeInterval: 0.1 target: self selector: @selector(checkProgress)
496                 userInfo: nil repeats: YES];
499 - (void) checkProgress
501     if (fInfo->isDone)
502     {
503         [fTimer invalidate];
504         fTimer = nil;
505         
506         NSAlert * alert;
507         switch (fInfo->result)
508         {
509             case TR_MAKEMETA_OK:
510                 if ([fDefaults boolForKey: @"CreatorOpen"])
511                 {
512                     NSDictionary * dict = [[NSDictionary alloc] initWithObjectsAndKeys: fLocation, @"File",
513                                             [fPath stringByDeletingLastPathComponent], @"Path", nil];
514                     [[NSNotificationCenter defaultCenter] postNotificationName: @"OpenCreatedTorrentFile" object: self userInfo: dict];
515                 }
516                 
517                 [[self window] close];
518                 break;
519             
520             case TR_MAKEMETA_CANCELLED:
521                 [[self window] close];
522                 break;
523             
524             default:
525                 alert = [[[NSAlert alloc] init] autorelease];
526                 [alert addButtonWithTitle: NSLocalizedString(@"OK", "Create torrent -> failed -> button")];
527                 [alert setMessageText: [NSString stringWithFormat: NSLocalizedString(@"Creation of \"%@\" failed.",
528                                                 "Create torrent -> failed -> title"), [fLocation lastPathComponent]]];
529                 [alert setAlertStyle: NSWarningAlertStyle];
530                 
531                 if (fInfo->result == TR_MAKEMETA_IO_READ)
532                     [alert setInformativeText: [NSString stringWithFormat: NSLocalizedString(@"Could not read \"%s\": %s.",
533                         "Create torrent -> failed -> warning"), fInfo->errfile, strerror(fInfo->my_errno)]];
534                 else if (fInfo->result == TR_MAKEMETA_IO_WRITE)
535                     [alert setInformativeText: [NSString stringWithFormat: NSLocalizedString(@"Could not write \"%s\": %s.",
536                         "Create torrent -> failed -> warning"), fInfo->errfile, strerror(fInfo->my_errno)]];
537                 else //invalid url should have been caught before creating
538                     [alert setInformativeText: [NSString stringWithFormat: @"%@ (%d)",
539                         NSLocalizedString(@"An unknown error has occurred.", "Create torrent -> failed -> warning"), fInfo->result]];
540                 
541                 [alert beginSheetModalForWindow: [self window] modalDelegate: self
542                     didEndSelector: @selector(failureSheetClosed:returnCode:contextInfo:) contextInfo: nil];
543         }
544     }
545     else
546     {
547         [fProgressIndicator setDoubleValue: (double)fInfo->pieceIndex / fInfo->pieceCount];
548         
549         if (!fStarted)
550         {
551             fStarted = YES;
552             
553             [fProgressView setHidden: YES];
554             
555             NSWindow * window = [self window];
556             [window setFrameAutosaveName: @""];
557             
558             NSRect windowRect = [window frame];
559             CGFloat difference = [fProgressView frame].size.height - [[window contentView] frame].size.height;
560             windowRect.origin.y -= difference;
561             windowRect.size.height += difference;
562             
563             //don't allow vertical resizing
564             CGFloat height = windowRect.size.height;
565             [window setMinSize: NSMakeSize([window minSize].width, height)];
566             [window setMaxSize: NSMakeSize([window maxSize].width, height)];
567             
568             [window setContentView: fProgressView];
569             [window setFrame: windowRect display: YES animate: YES];
570             [fProgressView setHidden: NO];
571             
572             [[window standardWindowButton: NSWindowCloseButton] setEnabled: NO];
573         }
574     }
577 - (void) failureSheetClosed: (NSAlert *) alert returnCode: (NSInteger) code contextInfo: (void *) info
579     [[alert window] orderOut: nil];
580     [[self window] close];
583 @end