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