1// 2// CelestiaOpenGLView.m 3// celestia 4// 5// Created by Bob Ippolito on Tue May 28 2002. 6// Copyright (C) 2007, Celestia Development Team 7// 8#import "CelestiaOpenGLView.h" 9#import "CelestiaAppCore.h" 10#import "MacInputWatcher.h" 11#import "TextWindowController.h" 12#import "Menu_Extensions.h" 13#import <OpenGL/gl.h> 14#import <OpenGL/glext.h> 15#import <OpenGL/CGLTypes.h> 16 17#ifndef ATI_FSAA_LEVEL 18#define ATI_FSAA_LEVEL 510 19#endif 20 21#define CEL_LEFT_BUTTON 1 22#define CEL_MIDDLE_BUTTON 2 23#define CEL_RIGHT_BUTTON 4 24 25 26@implementation CelestiaOpenGLView 27 28- (id) initWithCoder: (NSCoder *) coder 29{ 30 NSOpenGLPixelFormatAttribute attrs[] = 31 { 32 NSOpenGLPFADoubleBuffer, 33 NSOpenGLPFADepthSize, 34 (NSOpenGLPixelFormatAttribute)32, 35 nil 36 } ; 37 38 NSOpenGLPixelFormat* pixFmt ; 39 long swapInterval ; 40 41 self = [super initWithCoder: coder] ; 42 if (self) 43 { 44 pixFmt = [[NSOpenGLPixelFormat alloc] initWithAttributes: attrs]; 45 46 if (pixFmt) 47 { 48 [self setPixelFormat: pixFmt] ; 49 [pixFmt release]; 50 51 if (0 == CGLEnable([[self openGLContext] CGLContextObj], 313)) 52 { 53 NSLog(@"Multithreaded OpenGL enabled."); 54 } 55 else 56 { 57 NSLog(@"Multithreaded OpenGL not supported on your system."); 58 } 59 60 swapInterval = 1 ; 61 62 [[self openGLContext] 63 setValues: &swapInterval 64 forParameter: NSOpenGLCPSwapInterval ] ; 65 } 66 else 67 { 68 [self release]; 69 return nil; 70 } 71 } 72 73 return self; 74} 75 76- (void)dealloc 77{ 78 [inputWatcher release]; 79 [textWindow release]; 80 [super dealloc]; 81} 82 83- (void)setAASamples: (unsigned int)aaSamples 84{ 85 if (aaSamples > 1) 86 { 87 const char *glRenderer = (const char *) glGetString(GL_RENDERER); 88 89 if (strstr(glRenderer, "ATI")) 90 { 91 [[self openGLContext] setValues: (const long *)&aaSamples 92 forParameter: ATI_FSAA_LEVEL]; 93 } 94 else 95 { 96 NSOpenGLPixelFormat *pixFmt; 97 NSOpenGLContext *context; 98 99 NSOpenGLPixelFormatAttribute fsaaAttrs[] = 100 { 101 NSOpenGLPFADoubleBuffer, 102 NSOpenGLPFADepthSize, 103 (NSOpenGLPixelFormatAttribute)32, 104 NSOpenGLPFASampleBuffers, 105 (NSOpenGLPixelFormatAttribute)1, 106 NSOpenGLPFASamples, 107 (NSOpenGLPixelFormatAttribute)1, 108 nil 109 }; 110 111 fsaaAttrs[6] = aaSamples; 112 113 pixFmt = 114 [[NSOpenGLPixelFormat alloc] initWithAttributes: fsaaAttrs]; 115 116 if (pixFmt) 117 { 118 context = [[NSOpenGLContext alloc] initWithFormat: pixFmt 119 shareContext: nil]; 120 [pixFmt release]; 121 122 if (context) 123 { 124 // The following silently fails if not supported 125 CGLEnable([context CGLContextObj], 313); 126 127 long swapInterval = 1; 128 [context setValues: &swapInterval 129 forParameter: NSOpenGLCPSwapInterval]; 130 [self setOpenGLContext: context]; 131 [context setView: self]; 132 [context makeCurrentContext]; 133 [context release]; 134 135 glEnable(GL_MULTISAMPLE_ARB); 136 // GL_NICEST enables Quincunx on supported NVIDIA cards, 137 // but smears text. 138// glHint(GL_MULTISAMPLE_FILTER_HINT_NV, GL_NICEST); 139 } 140 } 141 } 142 } 143} 144 145 146- (BOOL) isFlipped {return YES;} 147 148- (void) viewDidMoveToWindow 149{ 150 [[self window] setAcceptsMouseMovedEvents: YES]; 151} 152 153- (NSMenu *)menuForEvent:(NSEvent *)theEvent 154{ 155 CelestiaSelection* selection; 156 NSString* selectionName; 157 NSPoint location = [self convertPoint: [theEvent locationInWindow] fromView: self]; 158 NSPoint location2 = [self convertPoint: [theEvent locationInWindow] fromView: nil]; 159 CelestiaAppCore *appCore = [CelestiaAppCore sharedAppCore]; 160 161 [appCore charEntered: 8 withModifiers:0]; 162 [appCore mouseButtonDown:location modifiers:[appCore toCelestiaModifiers: 0 buttons:CEL_LEFT_BUTTON]]; 163 [appCore mouseButtonUp:location2 modifiers:[appCore toCelestiaModifiers: 0 buttons:CEL_LEFT_BUTTON]]; 164 165 int auxItemIndex = -1; 166 int separatorIndex = -1; 167 BOOL insertedAuxItem = NO; 168 169 [[self menu] removePlanetarySystemItem]; 170 [[self menu] removeAltSurfaceItem]; 171 [[self menu] removeRefMarkItems]; 172 173 separatorIndex = [[self menu] numberOfItems] - 4; 174 if (separatorIndex < 0) separatorIndex = 0; 175 if ([[[self menu] itemAtIndex: separatorIndex] isSeparatorItem]) 176 ++separatorIndex; 177 if ([[[self menu] itemAtIndex: separatorIndex] isSeparatorItem]) 178 [[self menu] removeItemAtIndex: separatorIndex]; 179 180 selection = [[appCore simulation] selection]; 181 182 auxItemIndex = [[self menu] numberOfItems] - 2; 183 if (auxItemIndex < 0) auxItemIndex = 0; 184 insertedAuxItem = 185 [[self menu] insertRefMarkItemsForSelection: selection 186 atIndex: auxItemIndex]; 187 auxItemIndex = [[self menu] numberOfItems] - 2; 188 if (auxItemIndex < 0) auxItemIndex = 0; 189 insertedAuxItem = 190 [[self menu] insertPlanetarySystemItemForSelection: selection 191 target: controller 192 atIndex: auxItemIndex] 193 || insertedAuxItem; 194 auxItemIndex = [[self menu] numberOfItems] - 2; 195 if (auxItemIndex < 0) auxItemIndex = 0; 196 insertedAuxItem = 197 [[self menu] insertAltSurfaceItemForSelection: selection 198 target: controller 199 atIndex: auxItemIndex] 200 || insertedAuxItem; 201 202 if (insertedAuxItem) 203 { 204 separatorIndex = [[self menu] numberOfItems] - 2; 205 if (separatorIndex > 0) 206 { 207 [[self menu] insertItem: [NSMenuItem separatorItem] 208 atIndex: separatorIndex]; 209 } 210 } 211 212 if ([selection body]) 213 { 214 selectionName = [[selection body] name]; 215 } 216 else if ([selection star]) 217 { 218 selectionName = [[selection star] name]; 219 } 220 else 221 { 222 selectionName = [selection briefName]; 223 } 224/* 225 selectionName = [[[appCore simulation] selection] briefName]; 226 if ([selectionName isEqualToString: @""]) 227 { 228 [appCore mouseButtonDown:location modifiers:[appCore toCelestiaModifiers: 0 buttons:CEL_LEFT_BUTTON]]; 229 [appCore mouseButtonUp:location2 modifiers:[appCore toCelestiaModifiers: 0 buttons:CEL_LEFT_BUTTON]]; 230 selection = [[appCore simulation] selection]; 231 selectionName = [[[appCore simulation] selection] name]; 232 } 233*/ 234 if ([selectionName isEqualToString: @""]) return nil; 235 [[[self menu] itemAtIndex: 0] setTitle: selectionName ]; 236 [[[self menu] itemAtIndex: 0] setEnabled: YES ]; 237// [ [self menu] setAutoenablesItems: NO ]; 238 return [self menu]; 239} 240 241- (void) textEnterModeChanged 242{ 243 CelestiaAppCore *appCore = [CelestiaAppCore sharedAppCore]; 244 if ([appCore textEnterMode] & 1) 245 { 246 if (textWindow == nil) 247 textWindow = [[TextWindowController alloc] init]; 248 249 [textWindow makeActiveWithDelegate: inputWatcher]; 250 } 251 else if (0 == ([appCore textEnterMode] & 1)) 252 { 253 [[textWindow window] orderOut: nil]; 254 [[self window] makeFirstResponder: self]; 255 } 256} 257 258- (void) keyDown: (NSEvent*)theEvent 259{ 260 CelestiaAppCore *appCore = [CelestiaAppCore sharedAppCore]; 261 if (inputWatcher == nil) 262 inputWatcher = [[MacInputWatcher alloc] initWithWatched: self]; 263 264 NSString *eventChars = [theEvent characters]; 265 if (!eventChars || [eventChars length] == 0) 266 { 267 eventChars = [theEvent charactersIgnoringModifiers]; 268 if (!eventChars || [eventChars length] == 0) 269 return; 270 } 271 unichar key = [eventChars characterAtIndex: 0]; 272 int modifiers = [appCore toCelestiaModifiers: [theEvent modifierFlags] buttons: 0]; 273 274 if ( key == NSDeleteCharacter ) 275 key = NSBackspaceCharacter; // delete = backspace 276 else if ( key == NSDeleteFunctionKey || key == NSClearLineFunctionKey ) 277 key = NSDeleteCharacter; // del = delete 278 else if ( key == NSBackTabCharacter ) 279 key = 127; 280 281 if ( (key<128) && ((key < '0') || (key>'9') || !([theEvent modifierFlags] & NSNumericPadKeyMask)) ) 282 [ appCore charEntered: key 283 withModifiers: modifiers]; 284 285 [ appCore keyDown: [appCore toCelestiaKey: theEvent] 286 withModifiers: modifiers ]; 287} 288 289- (void) keyUp: (NSEvent*)theEvent 290{ 291 CelestiaAppCore *appCore = [CelestiaAppCore sharedAppCore]; 292// if ( [[theEvent characters] characterAtIndex: 0] >= 128 ) 293// [ appCore keyUp: [appCore toCelestiaKey: theEvent] ]; 294 [ appCore keyUp: [appCore toCelestiaKey: theEvent] 295 withModifiers: [appCore toCelestiaModifiers: [theEvent modifierFlags] buttons: 0] ]; 296// NSLog(@"keyUp: %@",theEvent); 297} 298 299- (void) mouseDown: (NSEvent*)theEvent 300{ 301 [ [self window] makeFirstResponder: self ]; 302 303 NSPoint location = [self convertPoint: [theEvent locationInWindow] fromView: nil]; 304 CelestiaAppCore *appCore = [CelestiaAppCore sharedAppCore]; 305 [appCore mouseButtonDown:location modifiers:[appCore toCelestiaModifiers:[theEvent modifierFlags] buttons:CEL_LEFT_BUTTON]]; 306} 307 308- (void) mouseUp: (NSEvent*)theEvent 309{ 310 if ( [theEvent modifierFlags] & NSAlternateKeyMask ) 311 { 312 [self otherMouseUp: theEvent]; 313 return; 314 } 315 316 NSPoint location = [self convertPoint: [theEvent locationInWindow] fromView: nil]; 317 NSRect bounds = [self bounds]; 318 if (!NSPointInRect(location, bounds)) 319 { 320 // -ve coords can crash Celestia so clamp to view bounds 321 if (location.x < NSMinX(bounds)) location.x = NSMinX(bounds); 322 if (location.x > NSMaxX(bounds)) location.x = NSMaxX(bounds); 323 if (location.y < NSMinY(bounds)) location.y = NSMinY(bounds); 324 if (location.y > NSMaxY(bounds)) location.y = NSMaxY(bounds); 325 } 326 CelestiaAppCore *appCore = [CelestiaAppCore sharedAppCore]; 327 [appCore mouseButtonUp:location modifiers:[appCore toCelestiaModifiers:[theEvent modifierFlags] buttons:CEL_LEFT_BUTTON]]; 328} 329 330- (void) mouseDragged: (NSEvent*) theEvent 331{ 332 if ( [theEvent modifierFlags] & NSAlternateKeyMask ) 333 { 334 [self rightMouseDragged: theEvent]; 335 return; 336 } 337 338 CelestiaAppCore *appCore = [CelestiaAppCore sharedAppCore]; 339 [appCore mouseMove:NSMakePoint([theEvent deltaX], [theEvent deltaY]) modifiers:[appCore toCelestiaModifiers:[theEvent modifierFlags] buttons:CEL_LEFT_BUTTON]]; 340} 341 342- (void) mouseMoved: (NSEvent*)theEvent 343{ 344 CelestiaAppCore *appCore = [CelestiaAppCore sharedAppCore]; 345 NSPoint location = [self convertPoint: [theEvent locationInWindow] fromView: nil]; 346 [appCore mouseMove: location]; 347} 348 349- (void) rightMouseDown: (NSEvent*)theEvent 350{ 351 NSPoint location = [self convertPoint: [theEvent locationInWindow] fromView: nil]; 352 CelestiaAppCore *appCore = [CelestiaAppCore sharedAppCore]; 353 [appCore mouseButtonDown:location modifiers:[appCore toCelestiaModifiers:[theEvent modifierFlags] buttons:CEL_RIGHT_BUTTON]]; 354} 355 356- (void) rightMouseUp: (NSEvent*)theEvent 357{ 358 NSPoint location = [self convertPoint: [theEvent locationInWindow] fromView: nil]; 359 CelestiaAppCore *appCore = [CelestiaAppCore sharedAppCore]; 360 [appCore mouseButtonUp:location modifiers:[appCore toCelestiaModifiers:[theEvent modifierFlags] buttons:CEL_RIGHT_BUTTON]]; 361 if([theEvent clickCount] > 0) 362 [super rightMouseDown:theEvent]; //...Force context menu to appear only on clicks (not drags) 363} 364 365- (void) rightMouseDragged: (NSEvent*) theEvent 366{ 367 CelestiaAppCore *appCore = [CelestiaAppCore sharedAppCore]; 368 [appCore mouseMove:NSMakePoint([theEvent deltaX], [theEvent deltaY]) modifiers:[appCore toCelestiaModifiers:[theEvent modifierFlags] buttons:CEL_RIGHT_BUTTON]]; 369} 370 371- (void) otherMouseDown: (NSEvent *) theEvent 372{ 373 NSPoint location = [self convertPoint: [theEvent locationInWindow] fromView: nil]; 374 CelestiaAppCore *appCore = [CelestiaAppCore sharedAppCore]; 375 [appCore mouseButtonDown:location modifiers:[appCore toCelestiaModifiers:[theEvent modifierFlags] buttons:CEL_MIDDLE_BUTTON]]; 376} 377 378- (void) otherMouseUp: (NSEvent *) theEvent 379{ 380 NSPoint location = [self convertPoint: [theEvent locationInWindow] fromView: nil]; 381 CelestiaAppCore *appCore = [CelestiaAppCore sharedAppCore]; 382 [appCore mouseButtonUp:location modifiers:[appCore toCelestiaModifiers:[theEvent modifierFlags] buttons:CEL_MIDDLE_BUTTON]]; 383} 384 385- (void) otherMouseDragged: (NSEvent *) theEvent 386{ 387 CelestiaAppCore *appCore = [CelestiaAppCore sharedAppCore]; 388 [appCore mouseMove:NSMakePoint([theEvent deltaX], [theEvent deltaY]) modifiers:[appCore toCelestiaModifiers:[theEvent modifierFlags] buttons:CEL_MIDDLE_BUTTON]]; 389} 390 391- (void) scrollWheel: (NSEvent*)theEvent 392{ 393 CelestiaAppCore *appCore = [CelestiaAppCore sharedAppCore]; 394 [ appCore mouseWheel: [theEvent deltaY] 395 modifiers: [appCore toCelestiaModifiers: [theEvent modifierFlags] buttons: 0] ]; 396} 397 398- (BOOL) acceptsFirstResponder: (NSEvent*)theEvent 399{ 400 return YES; 401} 402 403- (BOOL) becomeFirstResponder 404{ 405 return YES; 406} 407 408- (BOOL) resignFirstResponder 409{ 410 return YES; 411} 412 413- (BOOL) acceptsFirstMouse: (NSEvent*)theEvent 414{ 415 return YES; 416} 417 418- (void) drawRect: (NSRect) rect 419{ 420 NSOpenGLContext *oglContext; 421 oglContext = [self openGLContext]; 422 if (oglContext != nil) 423 { 424 [controller display]; 425 [oglContext flushBuffer]; 426 } 427} 428 429- (void) update 430{ 431 [controller setDirty]; 432 [super update]; 433} 434 435- (void) writeStringToPasteboard: (NSPasteboard *) pb 436{ 437 NSString *value; 438 [pb declareTypes: 439 [NSArray arrayWithObject: NSStringPboardType] owner: self]; 440 CelestiaAppCore *appCore = [CelestiaAppCore sharedAppCore]; 441 value = [appCore currentURL]; 442 [ pb setString: value forType: NSStringPboardType ]; 443} 444 445- (BOOL) readStringFromPasteboard: (NSPasteboard *) pb 446{ 447 NSString *value = nil; 448 NSString *type = [pb availableTypeFromArray: 449 [NSArray arrayWithObjects: NSURLPboardType, NSFilenamesPboardType, NSStringPboardType, nil ]]; 450 if ( type != nil ) 451 { 452 CelestiaAppCore *appCore = [CelestiaAppCore sharedAppCore]; 453 value = [ pb stringForType: NSStringPboardType ]; 454 if (value != nil ) 455 { 456 if ( [value rangeOfString:@"cel:" options: (NSCaseInsensitiveSearch|NSAnchoredSearch) ].location == 0 ) 457 [appCore goToUrl: value ]; 458 else 459 [appCore runScript: value ]; 460 } 461 else 462 { 463 value = [[NSURL URLWithString: (NSString*) [((NSArray*)[ pb propertyListForType: type ]) objectAtIndex: 0 ]] path]; 464 [controller runScript: value ]; 465 466 return NO; 467 if (value != nil) 468 { 469 value = [ pb stringForType: NSFilenamesPboardType ]; 470 if (value != nil ) 471 { 472 [controller runScript: value ]; 473 } 474 } 475 } 476 return YES; 477 } 478 return NO; 479} 480- (unsigned int) draggingEntered: (id <NSDraggingInfo>) sender 481{ 482 NSPasteboard *pb = [sender draggingPasteboard]; 483 NSString *type = [pb availableTypeFromArray: 484 [NSArray arrayWithObjects: NSStringPboardType, NSFilenamesPboardType, nil ]]; 485 if ( type != nil ) 486 { 487 return NSDragOperationCopy; 488 } 489 return NSDragOperationNone; 490} 491 492- (BOOL) prepareForDragOperation: (id <NSDraggingInfo>) sender 493{ 494 return YES; 495} 496 497- (BOOL) performDragOperation: (id <NSDraggingInfo>) sender 498{ 499 NSPasteboard *pb = [sender draggingPasteboard]; 500 return [ self readStringFromPasteboard: pb ]; 501} 502 503- (IBAction) paste: (id) sender 504{ 505 NSPasteboard *pb = [NSPasteboard generalPasteboard]; 506 [ self readStringFromPasteboard: pb ]; 507} 508 509- (IBAction) copy: (id) sender 510{ 511 NSPasteboard *pb = [NSPasteboard generalPasteboard]; 512 [ self writeStringToPasteboard: pb ]; 513} 514 515@end 516