CelestiaController.m
上传用户:center1979
上传日期:2022-07-26
资源大小:50633k
文件大小:27k
源码类别:

OpenGL

开发平台:

Visual C++

  1. //
  2. //  CelestiaController.m
  3. //  celestia
  4. //
  5. //  Created by Bob Ippolito on Tue May 28 2002.
  6. //  Copyright (C) 2007, Celestia Development Team
  7. //
  8. #include <unistd.h>
  9. #import "CelestiaController.h"
  10. #import "FavoritesDrawerController.h"
  11. #import "CelestiaOpenGLView.h"
  12. #import "FullScreenWindow.h"
  13. #import "SplashScreen.h"
  14. #import "SplashWindowController.h"
  15. #import "EclipseFinderController.h"
  16. #import "ScriptsController.h"
  17. #import <Carbon/Carbon.h>
  18. #import <OpenGL/gl.h>
  19. #import "CGLInfo.h"
  20. #include <float.h>
  21. @implementation CelestiaController
  22. static CelestiaController* firstInstance;
  23. +(CelestiaController*) shared
  24. {
  25.     // class method to get single shared instance
  26.     return firstInstance;
  27. }
  28. // Startup Methods ----------------------------------------------------------
  29. NSString* fatalErrorMessage;
  30. - (void)awakeFromNib
  31. {
  32.     if ([[self superclass] instancesRespondToSelector:@selector(awakeFromNib)]) 
  33.     {
  34.         [super awakeFromNib];
  35.     }
  36.     if (firstInstance == nil ) firstInstance = self;
  37.     int cpuCount = 0;
  38.     size_t cpuCountSize = sizeof cpuCount;
  39.     if (0 == sysctlbyname("hw.ncpu", &cpuCount, &cpuCountSize, NULL, 0))
  40.     {
  41.         threaded = (cpuCount > 1);
  42.     }
  43.     ready = NO;
  44.     isDirty = YES;
  45.     isFullScreen = NO;
  46.     appCore = nil;
  47.     fatalErrorMessage = nil;
  48.     lastScript = nil;
  49.     [self setupResourceDirectory];
  50.     [scriptsController buildScriptMenu];
  51.     //  hide main window until ready
  52.     [[glView window] setAlphaValue: 0.0f];  //  not  [[glView window] orderOut: nil];
  53.     // create appCore
  54.     appCore = [CelestiaAppCore sharedAppCore];
  55.     // check for startup failure
  56.     if (appCore == nil)
  57.     {
  58.         NSLog(@"Could not create CelestiaAppCore!");
  59.         [NSApp terminate:self];
  60.         return;
  61.     }
  62.     
  63. BOOL nosplash = [[NSUserDefaults standardUserDefaults] boolForKey:@"nosplash"];
  64.     startupCondition = [[NSConditionLock alloc] initWithCondition: 0];
  65. if (!nosplash)
  66. {
  67. [splashWindowController showWindow];
  68. }
  69.     if (threaded)
  70.     {
  71.         // start initialization thread
  72.         [NSThread detachNewThreadSelector: @selector(startInitialization) toTarget: self
  73.         withObject: nil];
  74.         // wait for completion
  75.         [self performSelectorOnMainThread: @selector(waitWhileLoading:) withObject: nil waitUntilDone: NO ];
  76.     }
  77.     else
  78.     {
  79.         [self performSelector:@selector(startInitialization) withObject:nil afterDelay:0];
  80.     }
  81. }
  82. - (void) setupResourceDirectory
  83. {
  84.     NSBundle* mainBundle = [NSBundle mainBundle];
  85.     // Change directory to resource dir so Celestia can find cfg files and textures
  86.     NSFileManager *fileManager = [NSFileManager defaultManager]; 
  87.     NSString* path;
  88.     NSMutableArray *resourceDirs = [NSMutableArray array];
  89.     BOOL isFolder = NO;
  90.     if ( [ fileManager fileExistsAtPath: path = [[ mainBundle resourcePath ] stringByAppendingPathComponent: CELESTIA_RESOURCES_FOLDER ] isDirectory: &isFolder ] && isFolder )
  91.     {
  92.         [resourceDirs addObject: path];
  93.     }
  94.     if ( [ fileManager fileExistsAtPath: path = [[[ mainBundle bundlePath ]  stringByDeletingLastPathComponent] stringByAppendingPathComponent: CELESTIA_RESOURCES_FOLDER ] isDirectory: &isFolder ] && isFolder )
  95.     {
  96.         [resourceDirs addObject: path];
  97.     }
  98.     FSRef folder;
  99.     CFURLRef url;
  100.     static short domains[] = { kUserDomain, kLocalDomain, kNetworkDomain };
  101.     unsigned i;
  102.     path = nil;
  103.     for (i = 0; i < (sizeof domains / sizeof(short)); ++i)
  104.     {
  105.         if (FSFindFolder(domains[i], kApplicationSupportFolderType, FALSE, &folder) == noErr)
  106.         {
  107.             url = CFURLCreateFromFSRef(nil, &folder);
  108.             path = [(NSURL *)url path];
  109.             CFRelease(url);
  110.             if (path)
  111.             {
  112.                 if (![fileManager fileExistsAtPath: path = [path stringByAppendingPathComponent: CELESTIA_RESOURCES_FOLDER]] &&
  113.                     kUserDomain==domains[i])
  114.                 {
  115.                     if ([fileManager createDirectoryAtPath:path attributes:nil])
  116.                     {
  117.                         [fileManager createDirectoryAtPath:[path stringByAppendingPathComponent:@"extras"] attributes:nil];
  118.                         [fileManager createDirectoryAtPath:[path stringByAppendingPathComponent:CEL_SCRIPTS_FOLDER] attributes:nil];
  119.                     }
  120.                 }
  121.                 if ([fileManager fileExistsAtPath:path isDirectory:&isFolder] && isFolder)
  122.                 {
  123.                     [resourceDirs addObject: path];
  124.                 }
  125.             }
  126.             path = nil;
  127.         }
  128.     }
  129.     if ([resourceDirs count] > 0)
  130.     {
  131.         [fileManager changeCurrentDirectoryPath: [resourceDirs objectAtIndex: 0]];
  132.         [resourceDirs removeObjectAtIndex: 0];
  133.         if ([resourceDirs count] > 0) {
  134.             NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
  135.             [prefs registerDefaults:[NSDictionary dictionaryWithObject:[NSArray arrayWithObject:[resourceDirs objectAtIndex:0]] forKey:@"existingResourceDirs"]];
  136.         }
  137.     }
  138.     else 
  139.     {
  140.         [self fatalError: NSLocalizedString(@"It appears that the "CelestiaResources" directory has not been properly installed in the correct location as indicated in the installation instructions. nnPlease correct this and try again.",@"")];
  141.         [self fatalError: nil];
  142.     }
  143. }
  144. - (void)startInitialization
  145. {
  146.     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  147.     [[glView openGLContext] makeCurrentContext];
  148. #ifdef DEBUG
  149.     NSDate *t = [NSDate date];
  150. #endif
  151.     if (![appCore initSimulation])
  152.     {
  153.         [startupCondition lock];
  154.         [startupCondition unlockWithCondition: 99];
  155. #ifdef DEBUG
  156.         [pool release];
  157. #endif
  158.         [self fatalError: NSLocalizedString(@"Error loading data files. Celestia will now quit.",@"")];
  159.         if (!threaded)
  160.             [self fatalError: nil];
  161.         else
  162.             [NSThread exit];
  163.         return;
  164.     }
  165. #ifdef DEBUG
  166.     NSLog(@"Init took %lf secondsn", -[t timeIntervalSinceNow]);
  167. #endif
  168.     [startupCondition lock];
  169.     [startupCondition unlockWithCondition: 1];
  170.     if (!threaded)
  171.     {
  172.         [splashWindowController close];
  173.         // complete startup
  174.         [self fatalError: nil];
  175.         [self finishInitialization];
  176.     }
  177.     [pool release];
  178. }
  179. - (void) fatalError: (NSString *) msg
  180. {
  181.     // handle fatal error message from either main or loading threads
  182.     if ( msg == nil )
  183.     {
  184.         if (fatalErrorMessage == nil) return;
  185.         [splashWindowController close];
  186.         NSRunAlertPanel(NSLocalizedString(@"Fatal Error",@""), fatalErrorMessage, nil, nil, nil);
  187.         fatalErrorMessage = nil;    // user could cancel the terminate
  188.         [NSApp terminate:self];
  189.         return;
  190.     }
  191.     fatalErrorMessage = [msg retain];
  192. }
  193. - (void) waitWhileLoading: (id) obj
  194. {
  195.     // display loading indicator window while loading
  196.     static NSModalSession session = nil;
  197.     if ( [startupCondition condition] == 0 ) 
  198.     {
  199.         if ( session != nil )
  200.             return;
  201.         // beginModalSession also displays the window, but the centering
  202.         // is wrong so do the display and centering beforehand
  203.         session = [NSApp beginModalSessionForWindow: [splashWindowController window]];
  204.         for (;;) 
  205.         {
  206.             if ( fatalErrorMessage != nil )
  207.                 break;
  208.             if ([NSApp runModalSession:session] != NSRunContinuesResponse)
  209.                 break;
  210.             if ( [startupCondition condition] != 0 )
  211.                 break;
  212.         }
  213.         [NSApp endModalSession:session];
  214.     }
  215.     [splashWindowController close];
  216.     // check for fatal error in loading thread
  217.     [self fatalError: nil];
  218.     // complete startup
  219.     [self finishInitialization];
  220. }
  221. -(void) setupFavorites
  222. {
  223.     NSInvocation *menuCallback;
  224.     menuCallback = [NSInvocation invocationWithMethodSignature:[FavoritesDrawerController instanceMethodSignatureForSelector:@selector(synchronizeFavoritesMenu)]];
  225.     [menuCallback setSelector:@selector(synchronizeFavoritesMenu)];
  226.     [menuCallback setTarget:favoritesDrawerController];
  227.     [[CelestiaFavorites sharedFavorites] setSynchronize:menuCallback];
  228.     [[CelestiaFavorites sharedFavorites] synchronize];
  229. }
  230. -(void) startGLView
  231. {
  232.     [[glView window] setAutodisplay:YES];
  233.     [[glView window] setHidesOnDeactivate: NO];
  234.     [[glView window] setFrameUsingName: @"Celestia"];
  235.     [[glView window] setAlphaValue: 1.0f];
  236.     [[glView window] setFrameAutosaveName: @"Celestia"];
  237.     if ([[glView window] canBecomeMainWindow])
  238.         [[glView window] makeMainWindow ];
  239.     [[glView window] makeFirstResponder: glView ];
  240.     [[glView window] makeKeyAndOrderFront: glView ];
  241.     [glView registerForDraggedTypes:
  242.         [NSArray arrayWithObjects: NSStringPboardType, NSFilenamesPboardType, NSURLPboardType, nil]];
  243.     [glView setNeedsDisplay:YES];
  244. }
  245. - (void)startRender
  246. {
  247.     [self startGLView];
  248.     if (isFullScreen)
  249.     {
  250.         isFullScreen = NO;
  251.         [self toggleFullScreen: self];
  252.     }
  253.     // workaround for fov problem
  254.     if (pendingUrl) [appCore goToUrl: pendingUrl];
  255.     if ([startupCondition tryLockWhenCondition: 1])
  256.         [startupCondition unlockWithCondition:  2];
  257. }
  258. - (void)finishInitialization
  259. {
  260. #ifndef NO_VP_WORKAROUND
  261.     NSString *VP_PROBLEM_EXT  = @"GL_ARB_vertex_program";
  262.     NSString *VP_PATCH_SCRIPT = @"vp_patch.sh";
  263.     NSString *VP_PATCH_SHELL  = @"/bin/zsh";
  264.     NSString *CELESTIA_CFG    = @"~/.celestia.cfg";
  265.     const char *VP_PROBLEM_RENDERERS[] = { "ATI Radeon 9200" };
  266.     const char *glRenderer = (const char *) glGetString(GL_RENDERER);
  267.     BOOL shouldWorkaround = NO;
  268.     size_t i = 0;
  269.     if (glRenderer)
  270.     {
  271.         for (; i < (sizeof VP_PROBLEM_RENDERERS)/sizeof(char *); ++i)
  272.         {
  273.             if (strstr(glRenderer, VP_PROBLEM_RENDERERS[i]))
  274.             {
  275.                 shouldWorkaround = YES;
  276.                 break;
  277.             }
  278.         }
  279.     }
  280.     if (shouldWorkaround && ![appCore glExtensionIgnored: VP_PROBLEM_EXT])
  281.     {
  282.         if (NSRunAlertPanel([NSString stringWithFormat: NSLocalizedString(@"It appears you are running Celestia on %s hardware. Do you wish to install a workaround?",nil), VP_PROBLEM_RENDERERS[i]],
  283.                             [NSString stringWithFormat: NSLocalizedString(@"A shell script will be run to modify your %@, adding an IgnoreGLExtensions directive. This can prevent freezing issues.",nil), CELESTIA_CFG],
  284.                             NSLocalizedString(@"Yes",nil),
  285.                             NSLocalizedString(@"No",nil),
  286.                             nil) == NSAlertDefaultReturn)
  287.         {
  288.             // Install it
  289.             NSString *cfgPath = [CELESTIA_CFG stringByStandardizingPath];
  290.             NSString *toolPath = [[NSBundle mainBundle] pathForResource: VP_PATCH_SCRIPT ofType: @""];
  291.             BOOL patchInstalled = NO;
  292.             if (toolPath)
  293.             {
  294.                 NSArray *taskArgs = [NSArray arrayWithObjects:
  295.                     toolPath, cfgPath, nil];
  296.                 NSTask *theTask = [NSTask launchedTaskWithLaunchPath: VP_PATCH_SHELL
  297.                                                            arguments: taskArgs];
  298.                 if (theTask)
  299.                 {
  300.                     [theTask waitUntilExit];
  301.                     patchInstalled = ([theTask terminationStatus] == 0);
  302.                 }
  303.             }
  304.             if (patchInstalled)
  305.             {
  306.                 // Have to apply same patch to config already loaded in memory
  307.                 [appCore setGLExtensionIgnored: VP_PROBLEM_EXT];
  308.                 NSRunAlertPanel(NSLocalizedString(@"Workaround successfully installed.",nil),
  309.                                 [NSString stringWithFormat: NSLocalizedString(@"Your original %@ has been backed up.",nil), CELESTIA_CFG],
  310.                                 nil, nil, nil);
  311.             }
  312.             else
  313.             {
  314.                 [[CelestiaController shared] fatalError: NSLocalizedString(@"There was a problem installing the workaround. You can attempt to perform the workaround manually by following the instructions in the README.",nil)];
  315.                 [[CelestiaController shared] fatalError: nil];
  316.             }
  317.         }
  318.     }
  319. #endif NO_VP_WORKAROUND
  320.     [glView setAASamples: [appCore aaSamples]];
  321.     [appCore initRenderer];
  322.     [self setupFavorites];
  323.     settings = [CelestiaSettings shared];
  324.     [settings setControl: self];
  325.     [settings scanForKeys: [renderPanelController window]];
  326.     [settings validateItems];
  327.     // paste URL if pending
  328.     if (pendingUrl != nil )
  329.     {
  330.         [ appCore setStartURL: pendingUrl ];
  331.     }
  332.     // Settings used to be loaded after starting simulation due to
  333.     // timezone setting requiring simulation time, but this dependency
  334.     // has been removed. In fact timezone needs to be set in order to
  335.     // correctly set the simulation time so settings loaded before starting.
  336.     [settings loadUserDefaults];
  337.     [appCore start:[NSDate date]];
  338.     ready = YES;
  339.     timer = [[NSTimer timerWithTimeInterval: 0.01 target: self selector:@selector(timeDisplay) userInfo:nil repeats:YES] retain];
  340.     [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
  341.     // Threaded startup can allow app to be hidden during startup
  342.     // When this happens delay rendering until first unhide
  343.     // to prevent all sorts of problems
  344.     if (![NSApp isHidden])
  345.         [self startRender];
  346.     // run script if pending (scripts can run without rendering)
  347.     if (pendingScript != nil )
  348.     {
  349.         [self runScript: pendingScript ];
  350.     }    
  351. }
  352. // Application Event Handler Methods ----------------------------------------------------------
  353. - (void) applicationWillFinishLaunching:(NSNotification *) notification {
  354. [[NSAppleEventManager sharedAppleEventManager] setEventHandler:self andSelector:@selector( handleURLEvent:withReplyEvent: ) forEventClass:kInternetEventClass andEventID:kAEGetURL];
  355. }
  356. - (BOOL) application:(NSApplication *)theApplication openFile:(NSString *)filename
  357. {
  358.     if ( ready )
  359.         [self runScript: filename ];
  360.     else
  361.         pendingScript = [filename retain];
  362.     return YES;
  363. }
  364. - (void) handleURLEvent:(NSAppleEventDescriptor *) event withReplyEvent:(NSAppleEventDescriptor *) replyEvent 
  365. {
  366.     if ( ready )
  367.         [ appCore goToUrl: [[event descriptorAtIndex:1] stringValue] ];
  368.     else
  369.         pendingUrl = [[[event descriptorAtIndex:1] stringValue] retain];
  370. }
  371. /* On a multi-screen setup, user is able to change the resolution of the screen running Celestia from a different screen, or the menu bar position so handle that */
  372. - (void)applicationDidChangeScreenParameters:(NSNotification *) aNotification
  373. {
  374.     if (isFullScreen && aNotification && ([aNotification object] == NSApp))
  375.     {
  376.         // If menu bar not on same screen, don't hide it anymore
  377.         if (![self hideMenuBarOnActiveScreen])
  378.             SetSystemUIMode(kUIModeNormal, 0);
  379.         NSScreen *screen = [[self window] screen];
  380.         NSRect screenRect = [screen frame];
  381.         if (!NSEqualSizes(screenRect.size, [[self window] frame].size))
  382.             [[self window] setFrame: screenRect display:YES];
  383.     }
  384. }
  385. - (void)applicationDidHide:(NSNotification *)aNotification
  386. {
  387.     ready = NO;
  388. }
  389. - (void)applicationWillUnhide:(NSNotification *)aNotification
  390. {
  391.     if ( [startupCondition condition] == 0 ) return;
  392.     ready = YES;
  393. }
  394. - (void)applicationDidUnhide:(NSNotification *)aNotification
  395. {
  396.     if ( [startupCondition tryLockWhenCondition: 1] )
  397.     {
  398.         [startupCondition unlock];
  399.         [self startRender];
  400.     }
  401. }
  402. -(BOOL)applicationShouldTerminate:(id)sender
  403. {
  404.    if (  NSRunAlertPanel(NSLocalizedString(@"Quit Celestia?",@""),
  405.                          NSLocalizedString(@"Are you sure you want to quit Celestia?",@""),
  406.                          NSLocalizedString(@"Quit",@""),
  407.                          NSLocalizedString(@"Cancel",@""),
  408.                          nil) != NSAlertDefaultReturn ) 
  409.    {
  410.        return NO;
  411.    }
  412.     if (timer != nil) {
  413.         [timer invalidate];
  414.         [timer release];
  415.         timer = nil;
  416.     }
  417.     [[CelestiaAppCore sharedAppCore] archive];
  418.     return YES;
  419. }
  420. - (void) applicationWillTerminate:(NSNotification *) notification
  421. {
  422.     [settings storeUserDefaults];
  423.     [lastScript release];
  424.     [eclipseFinderController release];
  425.     [browserWindowController release];
  426.     [helpWindowController release];
  427.     if (appCore != nil) {
  428.         [appCore release];
  429.         appCore = nil;
  430.     }
  431. }
  432. // Window Event Handler Methods ----------------------------------------------------------
  433. -(BOOL)windowShouldClose:(id)sender
  434. {
  435.     [NSApp terminate:nil];
  436.     return NO;
  437. }
  438. - (void)resize 
  439. {
  440.     [appCore resize:[glView frame]];
  441.     isDirty = NO;
  442. }
  443. // Catch key events even when gl window is not key
  444. -(void)delegateKeyDown:(NSEvent *)theEvent
  445. {
  446.     if (ready)
  447.         [glView keyDown: theEvent];
  448. }
  449. // Held Key Simulation Methods ----------------------------------------------------------
  450. -(void) keyPress:(int) code hold: (int) time
  451. {
  452.     // start simulated key hold
  453.     keyCode = code;
  454.     keyTime = time;
  455.     [appCore keyDown: keyCode ];
  456. }
  457. - (void) keyTick
  458. {
  459.        if ( keyCode != 0 )
  460.        {
  461.             if ( keyTime <= 0 )
  462.             {
  463.                [ appCore keyUp: keyCode];
  464.                keyCode = 0;  
  465.             }
  466.             else 
  467.                keyTime --;
  468.        }    
  469. }
  470. // Display Update Management Methods ----------------------------------------------------------
  471. - (void)setDirty
  472. {
  473.     isDirty = YES;
  474. }
  475. - (void) forceDisplay
  476. {
  477.     if (![glView needsDisplay]) [glView setNeedsDisplay:YES];
  478. }
  479. - (void) display 
  480. {
  481.     // update display when required by glView (invoked from drawRect:)
  482.     if (ready)
  483.     {
  484.         if (isDirty)
  485.             [self resize];
  486.         // render to glView
  487.         [appCore draw];
  488.         // update scene
  489.         [appCore tick];
  490.     }
  491. }
  492. - (void) timeDisplay
  493. {
  494. //    if (!ready) return;
  495.     [NSEvent stopPeriodicEvents];    
  496.     // check for time to release simulated key held down
  497.     [self keyTick];
  498. #ifdef USE_PERIODIC
  499.     // adjust timer if necessary to receive waiting appkit events
  500.     static NSEvent* lastEvent = nil;
  501.     NSEvent *nextEvent;
  502.     [NSEvent startPeriodicEventsAfterDelay: 0.0 withPeriod: 0.001 ];
  503.     nextEvent = [NSApp nextEventMatchingMask: ( NSPeriodicMask|NSAppKitDefinedMask ) untilDate: nil inMode: NSDefaultRunLoopMode dequeue: NO];
  504.     [NSEvent stopPeriodicEvents]; 
  505.     if ( [nextEvent type] == NSPeriodic )
  506.     {   
  507.         // ignore periodic events
  508.         [NSApp discardEventsMatchingMask: NSPeriodicMask beforeEvent: nil ];
  509.     }
  510.     else
  511.     {
  512.         if ( nextEvent == lastEvent )
  513.     {   
  514.         // event is still waiting, so delay firing timer to allow event to process
  515.         [timer setFireDate: [[NSDate date] addTimeInterval: 0.01 ] ];
  516.     }
  517.     else
  518.     {
  519.         lastEvent = nextEvent;
  520.             return;
  521.     }
  522.     }
  523. #endif
  524.     // force display update
  525.     [self forceDisplay];
  526. }
  527. // Application Action Methods ----------------------------------------------------------
  528. /* Full screen toggle method. Uses a borderless window that covers the screen so that context menus continue to work. */
  529. - (IBAction) toggleFullScreen: (id) sender
  530. {
  531.     if (isFullScreen)
  532.     {
  533.         CelestiaOpenGLView *windowedView = nil;
  534.         Class viewClass = [CelestiaOpenGLView class];
  535.         NSArray *mainSubViews = [[origWindow contentView] subviews];
  536.         if (mainSubViews && [mainSubViews count]>0)
  537.         {
  538.             // Just to be safe, search for first child of correct type
  539.             NSEnumerator *viewEnum = [mainSubViews objectEnumerator];
  540.             id subView;
  541.             while ((subView = [viewEnum nextObject]))
  542.             {
  543.                 if ([subView isKindOfClass: viewClass])
  544.                 {
  545.                     windowedView = subView;
  546.                     break;
  547.                 }
  548.             }
  549.         }
  550.         else if ([[origWindow contentView] isKindOfClass: viewClass])
  551.             windowedView = [origWindow contentView];
  552.         [origWindow makeKeyAndOrderFront: self];
  553.         if (windowedView == nil)
  554.         {
  555.             // Can't switch back to windowed mode, but hide full screen window
  556.             // so user can still quit the program
  557.             [[self window] orderOut: self];
  558.             SetSystemUIMode(kUIModeNormal, 0);
  559.             [self fatalError: NSLocalizedString(@"Unable to properly exit full screen mode. Celestia will now quit.",@"")];
  560.             [self performSelector:@selector(fatalError:) withObject:nil afterDelay:0.1];
  561.             return;
  562.         }
  563.         [windowedView setOpenGLContext: [glView openGLContext]];
  564.         [[glView openGLContext] setView: windowedView];
  565.         [[self window] close];  // full screen window releases on close
  566.         SetSystemUIMode(kUIModeNormal, 0);
  567.         [self setWindow: origWindow];
  568.         glView = windowedView;
  569.         [self setDirty];
  570.         [origWindow makeMainWindow];
  571.         isFullScreen = NO;
  572.         return;
  573.     }
  574.     // We will take over the screen that the window is on
  575.     // (if there are >1 screens, the 50% rule applies)
  576.     NSScreen *screen = [[glView window] screen];
  577.     CelestiaOpenGLView *fullScreenView = [[CelestiaOpenGLView alloc] initWithFrame:[glView frame] pixelFormat:[glView pixelFormat]];
  578.     [fullScreenView setMenu: [glView menu]];    // context menu
  579.     FullScreenWindow *fullScreenWindow = [[FullScreenWindow alloc] initWithScreen: screen];
  580.     [fullScreenWindow fadeOutScreen];
  581.     [fullScreenWindow setBackgroundColor: [NSColor blackColor]];
  582.     [fullScreenWindow setReleasedWhenClosed: YES];
  583.     [self setWindow: fullScreenWindow]; // retains it
  584.     [fullScreenWindow release];
  585.     [fullScreenWindow setDelegate: self];
  586.     // Hide the menu bar only if it's on the same screen
  587.     [self hideMenuBarOnActiveScreen];
  588.     [fullScreenWindow makeKeyAndOrderFront: nil];
  589.     [fullScreenWindow setContentView: fullScreenView];
  590.     [fullScreenView release];
  591.     [fullScreenView setOpenGLContext: [glView openGLContext]];
  592.     [[glView openGLContext] setView: fullScreenView];
  593.     // Remember the original (bordered) window
  594.     origWindow = [glView window];
  595.     // Close the original window (does not release it)
  596.     [origWindow close];
  597.     glView = fullScreenView;
  598.     [glView takeValue: self forKey: @"controller"];
  599.     [fullScreenWindow makeFirstResponder: glView];
  600.     // Make sure the view looks ready before unfading from black
  601.     [glView update];
  602.     [glView display];
  603.     [fullScreenWindow makeMainWindow];
  604.     [fullScreenWindow restoreScreen];
  605.     isFullScreen = YES;
  606. }
  607. - (BOOL) hideMenuBarOnActiveScreen
  608. {
  609.     NSScreen *screen = [[self window] screen];
  610.     NSArray *allScreens = [NSScreen screens];
  611.     if (allScreens && [allScreens objectAtIndex: 0]!=screen)
  612.         return NO;
  613.     SetSystemUIMode(kUIModeAllHidden, kUIOptionAutoShowMenuBar);
  614.     return YES;
  615. }
  616. - (void) runScript: (NSString*) path
  617. {
  618.     NSString* oldScript = lastScript;
  619.     lastScript = [path retain];
  620.     [oldScript release];
  621.     [appCore runScript: lastScript];       
  622. }
  623. - (IBAction) openScript: (id) sender
  624. {
  625.     NSOpenPanel* panel = [NSOpenPanel openPanel];
  626.     NSDocumentController *dc = [NSDocumentController sharedDocumentController];
  627.     int result = [panel runModalForTypes: [dc fileExtensionsFromType:@"Celestia Script"]];
  628.     if (result == NSOKButton)
  629.     {
  630.         NSString *path;
  631.         path = [panel filename];
  632.         [self runScript: path];       
  633.     }
  634. }
  635. - (IBAction) rerunScript: (id) sender
  636. {
  637.     if (lastScript) [appCore runScript: lastScript];       
  638. }
  639. - (IBAction) back:(id)sender
  640. {
  641.     [appCore back];
  642. }
  643. - (IBAction) forward:(id)sender
  644. {
  645.     [appCore forward];
  646. }
  647. - (IBAction) selectSatellite:(id)sender
  648. {
  649.     if (sender &&
  650.         [sender respondsToSelector: @selector(representedObject)] &&
  651.         [sender representedObject])
  652.     {
  653.         [[appCore simulation] setSelection: [[[CelestiaSelection alloc] initWithCelestiaBody: [sender representedObject]] autorelease]];
  654.     }
  655. }
  656. - (IBAction) showGLInfo:(id)sender
  657. {
  658.     if (![glInfoPanel isVisible])
  659.     {
  660.         NSTextStorage *text = [glInfo textStorage];
  661.         NSAttributedString *str = [[NSAttributedString alloc] initWithString: [CGLInfo info]];
  662.         [text setAttributedString: str];
  663.         [str release];
  664.     }
  665.     [glInfoPanel makeKeyAndOrderFront: self];
  666. }
  667. - (IBAction) showInfoURL:(id)sender
  668. {
  669.     [appCore showInfoURL];
  670. }
  671. - (void) moviePanelDidEnd:(NSSavePanel*)savePanel returnCode: (int) rc contextInfo: (void *) ci
  672. {
  673. //    if (rc == NSOKButton )
  674.     if (rc == 0 ) return;
  675.     {
  676.         NSString *path;
  677.         path = [savePanel filename];
  678.         NSLog(@"Saving movie: %@",path);       
  679. [appCore captureMovie: path width: 640 height: 480 frameRate: 30 ];
  680.     }
  681. }
  682. - (IBAction) captureMovie: (id) sender
  683. {
  684. //  Remove following line to enable movie capture...
  685. NSRunAlertPanel(NSLocalizedString(@"No Movie Capture",@""), NSLocalizedString(@"Movie capture is not available in this version of Celestia.",@""),nil,nil,nil); return;
  686.     NSSavePanel* panel = [NSSavePanel savePanel];
  687. NSString* lastMovie = nil; // temporary; should be saved in defaults
  688. [panel setRequiredFileType: @"mov"];
  689. [panel setTitle: NSLocalizedString(@"Capture Movie",@"")];
  690.     [ panel beginSheetForDirectory:  [lastMovie stringByDeletingLastPathComponent]
  691.                               file: [lastMovie lastPathComponent]
  692.                     modalForWindow: [glView window]
  693.                      modalDelegate: self
  694.                     didEndSelector: @selector(moviePanelDidEnd:returnCode:contextInfo:)
  695.                        contextInfo: nil
  696.     ];
  697. }
  698. // GUI Tag Methods ----------------------------------------------------------
  699. - (BOOL) validateMenuItem: (id) item
  700. {
  701.     if ( [startupCondition condition] == 0 ) return NO;
  702.     if ( [item action] == nil  ) return NO;
  703.     if ( [item action] != @selector(activateMenuItem:) ) return YES;
  704.     if ( [item tag] == 0 )
  705.     {
  706.         return [item isEnabled];
  707.     }
  708.     else
  709.     {
  710.         return [settings validateItem: item ];
  711.     }
  712. }
  713. - (IBAction) activateMenuItem: (id) item
  714. {
  715.     int tag = [item tag];
  716.     if ( tag != 0 )
  717.     {
  718.         if ( tag < 0 ) // simulate key press and hold
  719.         {
  720.             [self keyPress: -tag hold: 2];
  721.         } 
  722.         else 
  723.         {
  724.             [settings actionForItem: item ];
  725.         }
  726.         [settings validateItemForTag: tag];
  727.     }
  728. }
  729. -(IBAction) showPanel: (id) sender
  730. {
  731.     switch( [sender tag] )
  732.     {
  733.         case 0:
  734.             if (!browserWindowController) browserWindowController = [[BrowserWindowController alloc] init];
  735.             [browserWindowController showWindow: self];
  736.             break;
  737.         case 1:
  738.             if (!eclipseFinderController) eclipseFinderController = [[EclipseFinderController alloc] init];
  739.             [eclipseFinderController showWindow: self];
  740.             break;
  741.     }
  742. }
  743. - (void) showHelp: (id) sender
  744. {
  745.     if (helpWindowController == nil)
  746.         helpWindowController = [[NSWindowController alloc] initWithWindowNibName: @"HelpWindow"];
  747.     [helpWindowController showWindow: self];
  748. }
  749. @end
  750. #pragma mark -
  751. // Solution for keyDown sent but keyUp not sent for Cmd key combos.
  752. // Fixes the infamous Cmd+arrow "infinite spin"
  753. @interface CelestiaApplication : NSApplication
  754. @end
  755. @implementation CelestiaApplication
  756. - (void)sendEvent: (NSEvent *)aEvent
  757. {
  758.     if ([aEvent type] == NSKeyUp)
  759.     {
  760.         [[[self mainWindow] firstResponder] tryToPerform: @selector(keyUp:)
  761.                                                     with: aEvent];
  762.         return;
  763.     }
  764.     
  765.     [super sendEvent: aEvent];
  766. }
  767. @end