root/OpenSceneGraph/trunk/examples/osgviewerCocoa/ViewerCocoa.mm @ 6886

Revision 6886, 52.3 kB (checked in by robert, 7 years ago)

From Martin Lavery, port of ViewerCocoa? and updates to Xcode projects

Line 
1//
2//  ViewerCocoa.mm
3//  osgviewerCocoa
4//
5//  Created by Eric Wing on 11/12/06.
6//  Copyright 2006. Released under the OSGPL.
7//  Ported to osgViewer::Viewer by Martin Lavery 7/06/07
8//
9/* This class demonstrates how to subclass NSOpenGLView to integrate with the
10 * osgViewer::Viewer.
11 * This class demonstrates:
12 * Objective-C++
13 * How to subclass NSOpenGLView
14 * Specifying OpenGL pixel formats
15 * V-sync
16 * Timer based animation
17 * One button and multibutton mice.
18 * Scroll events (Mighty Mouse, Two-finger trackpads)
19 * Keyboard events
20 * Drag and drop (as target)
21 * Drag and drop (as source)
22 * Copy (for copy/paste)
23 * Resolution Independent UI (maybe, not Leopard tested/only Tiger Quartz Debug)
24 * Target-Action (for other widgets to invoke actions on this view)
25 * Shared OpenGLContexts
26 * Multithreaded OpenGL Engine (with CPU count check)
27 * Getting an image for minimization in the Dock
28 * Using osg::Camera's Framebuffer Objects to generate screen captures
29 * Code to detect drawing to offscreen (e.g. printer)
30 * Localization/Localizable strings (really need some different languages)
31 * Use of respondsToSelector and instancesRespondToSelector to demonstrate
32 *   runtime feature checking to provide access to newer features while still
33 *   supporting legacy versions.
34 *
35 * Things not demonstrated by this view or application example (but would be interesting):
36 * Cocoa Bindings (highly recommended)
37 * Core Data (works great with Cocoa Bindings)
38 * Custom Interface Builder palette with Inspector configurable options
39 * More PixelFormat options
40 * Fullscreen mode
41 * Low-level CGL access (kind of have with multithreaded OpenGL engine).
42 * Delegates for your view
43 * Multithreading
44 * Updating images of the window for the minimized view in the Dock
45 * Full Printing support
46 * Non-view stuff (Application Delegates, models, controllers)
47 * Launching by double-clicking a model or drag-and-drop onto Application Icon (non-view)
48 * Launching via commandline with parameters (non-view)
49 */ 
50
51/* Commentary:
52 * This class packs a lot of functionality that you might not expect from a "Viewer".
53 * The reason is that Mac users have high expectations of their applications, so things
54 * that might not even be considered on other platforms (like drag-and-drop), are almost
55 * a requirment on Mac OS X.
56 * The good news is that this class can almost be used as a template for other purposes.
57 * If you are looking for the bare minimum code needed, focus your attention on
58 * the init* routines and drawRect.
59 */
60
61/*
62 * Coding conventions:
63 * My coding style is slightly different than what you normally see in Cocoa.
64 * So here is the cheat sheet:
65 * I hate Hungarian (Microsoft) notation. And prefixed underscore _variables
66 * are technically reserved by the compiler which is why I avoid them. So...
67 * Member variables use camelCase (Pascal/Java)
68 * Local variables use under_scores (Ada)
69 * For methods, I follow standard Cocoa conventions.
70 * I tend to keep * with the type (e.g. NSView* foo) instead of with the variable (NSView *foo).
71 * (I tend to think of the pointer as part of the type.)
72 * For Obj-C named parameters, I tend to keep the namedParameter and the value
73 * together instead of separated by spaces
74 * e.g. [self initWithX:x_val yVal:y_val zVal:z_val].
75 * (When I was first learning Objective-C, this made it easier for me to
76 * figure out which things were paired.)
77 */
78#import "ViewerCocoa.h"
79
80#include <OpenGL/gl.h>
81#include <OpenGL/glu.h> // handy for gluErrorString
82
83#include <osgViewer/Viewer>
84#include <osgViewer/ViewerEventHandlers>
85#include <osgGA/TrackballManipulator>
86// Needed to explicitly typecast keys to the OSG type
87#include <osgGA/GUIEventAdapter>
88// Needed to load models
89#include <osgDB/ReadFile>
90
91// Used so I can change the background (clear) color
92
93#include <osg/Vec4>
94
95// For debugging
96//#include <osg/Notify>
97
98// osgText is used only to announce that you can use drag-and-drop to load models.
99// osgViewer itself does not have a dependency on osgText.
100#include <osg/ref_ptr>
101#include <osgText/Text>
102
103#include <osg/Geode>
104
105// Needed for Multithreaded OpenGL Engine (sysctlbyname for num CPUs)
106#include <sys/types.h>
107#include <sys/sysctl.h>
108#include <OpenGL/OpenGL.h> // for CoreOpenGL (CGL) for Multithreaded OpenGL Engine
109
110
111
112// This is optional. This allows memory for things like textures and displaylists to be shared among different contexts.
113#define VIEWER_USE_SHARED_CONTEXTS
114#ifdef VIEWER_USE_SHARED_CONTEXTS
115static NSOpenGLContext* s_sharedOpenGLContext = NULL;
116#endif // VIEWER_USE_SHARED_CONTEXTS
117
118// Taken/Adapted from one of the Apple OpenGL developer examples
119static void Internal_SetAlpha(NSBitmapImageRep *imageRep, unsigned char alpha_value)
120{
121        register unsigned char * sp = [imageRep bitmapData];
122        register int bytesPerRow = [imageRep bytesPerRow];
123        register int height = [imageRep pixelsHigh];
124        register int width = [imageRep pixelsWide];
125
126        for(int i=0; i<height; i++)
127        {
128                register unsigned int * the_pixel = (unsigned int *) sp;
129                register int w = width;
130                while (w-- > 0)
131                {
132                        unsigned char* sp_char = (unsigned char *) the_pixel;
133//                      register unsigned char * the_red = sp_char;
134//                      register unsigned char * the_green = (sp_char+1);
135//                      register unsigned char * the_blue = (sp_char+2);
136                        register unsigned char * the_alpha = (sp_char+3);
137       
138                        *the_alpha = alpha_value;
139                        *the_pixel++;
140                }
141                sp += bytesPerRow;
142        }
143}
144
145
146@implementation ViewerCocoa
147
148// My simple pixel format definition
149+ (NSOpenGLPixelFormat*) basicPixelFormat
150{
151        NSOpenGLPixelFormatAttribute pixel_attributes[] =
152        {
153                NSOpenGLPFAWindow,
154                NSOpenGLPFADoubleBuffer,  // double buffered
155                NSOpenGLPFADepthSize, (NSOpenGLPixelFormatAttribute)32, // depth buffer size in bits
156//              NSOpenGLPFAColorSize, (NSOpenGLPixelFormatAttribute)24, // Not sure if this helps
157//              NSOpenGLPFAAlphaSize, (NSOpenGLPixelFormatAttribute)8, // Not sure if this helps
158                (NSOpenGLPixelFormatAttribute)nil
159    };
160    return [[[NSOpenGLPixelFormat alloc] initWithAttributes:pixel_attributes] autorelease];
161}
162
163
164
165////////////////////////////////////////////////////////////////////////
166/////////////////////////// Init Stuff /////////////////////////////////
167////////////////////////////////////////////////////////////////////////
168
169/* This is the designated initializer for an NSOpenGLView. However, since I'm
170 * using Interface Builder to help, initWithCoder: is the initializer that gets called.
171 * But for completeness, I implement this method here.
172 */
173- (id) initWithFrame:(NSRect)frame_rect pixelFormat:(NSOpenGLPixelFormat*)pixel_format
174{
175        self = [super initWithFrame:frame_rect pixelFormat:pixel_format];
176        if(self)
177        {
178                [self commonInit];
179        }
180        return self;
181}
182
183/* Going through the IB palette, this initializer is calling instead of the designated initializer
184 * initWithFrame:pixelFormat:
185 * But for some reason, the pixel format set in IB selected seems to be either ignored or is missing
186 * a value I need. (The depth buffer looks too shallow to me and glErrors are triggered.)
187 * So I explicitly set the pixel format inside here (overriding the IB palette options).
188 * This probably should be investigated, but since IB is getting an overhaul for Leopard,
189 * I'll wait on this for now.
190 */
191- (id) initWithCoder:(NSCoder*)the_coder
192{
193        self = [super initWithCoder:the_coder];
194        if(self)
195        {
196                NSOpenGLPixelFormat* pixel_format = [ViewerCocoa basicPixelFormat];
197                [self setPixelFormat:pixel_format];
198                [self commonInit];
199        }
200        return self;
201}
202
203/* Some generic code expecting regular NSView's may call this initializer instead of the specialized NSOpenGLView designated initializer.
204 * I override this method here to make sure it does the right thing.
205 */
206- (id) initWithFrame:(NSRect)frame_rect
207{
208        self = [super initWithFrame:frame_rect pixelFormat:[ViewerCocoa basicPixelFormat]];
209        if(self)
210        {
211                [self commonInit];
212        }
213        return self;
214}
215
216
217// My custom methods to centralize common init stuff
218- (void) commonInit
219{
220        isUsingCtrlClick = NO;
221        isUsingOptionClick = NO;
222        isUsingMultithreadedOpenGLEngine = NO;
223
224        [self initSharedOpenGLContext];
225
226        [self initOSGViewer];
227        [self initAnimationTimer];
228       
229        // Register for Drag and Drop
230        [self registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, NSURLPboardType, nil]];
231       
232    // Add minification observer so we can set the Dock picture since OpenGL views don't do this automatically for us.
233        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(prepareForMiniaturization:) name:NSWindowWillMiniaturizeNotification object:nil];
234
235}
236
237/* Optional: This will setup shared OpenGL contexts so resources like textures, etc, can be shared/reused
238 * by multiple instances of SimpleViwerCocoa views.
239 */
240- (void) initSharedOpenGLContext
241{
242#ifdef VIEWER_USE_SHARED_CONTEXTS
243
244        NSOpenGLContext* this_views_opengl_context = nil;
245       
246        // create a context the first time through
247        if(s_sharedOpenGLContext == NULL)
248        {
249                s_sharedOpenGLContext = [[NSOpenGLContext alloc]
250                      initWithFormat:[ViewerCocoa basicPixelFormat]
251                                                shareContext:nil];
252               
253        }
254       
255        this_views_opengl_context = [[NSOpenGLContext alloc]
256                      initWithFormat:[ViewerCocoa basicPixelFormat]
257                                                shareContext:s_sharedOpenGLContext];
258        [self setOpenGLContext:this_views_opengl_context];
259//      [this_views_opengl_context makeCurrentContext];
260#endif // VIEWER_USE_SHARED_CONTEXTS
261}
262
263// Allocate a Viewer and do basic initialization. No assumption about having an
264// a valid OpenGL context is made by this function.
265- (void) initOSGViewer
266{
267
268
269//      osg::setNotifyLevel( osg::DEBUG_FP );
270        Viewer = new osgViewer::Viewer;
271        graphicsWindow = Viewer->setUpViewerAsEmbeddedInWindow(0,0,800,800);
272
273#ifdef VIEWER_USE_SHARED_CONTEXTS
274        // Workaround: osgViewer::Viewer automatically increments its context ID values.
275        // Since we're using a shared context, we want all Viewer's to use the same context ID.
276        // There is no API to avoid this behavior, so we need to undo what Viewer's constructor did.
277    graphicsWindow->getState()->setContextID(0);
278        osg::DisplaySettings::instance()->setMaxNumberOfGraphicsContexts(1);
279#endif // VIEWER_USE_SHARED_CONTEXTS
280
281        // Cocoa follows the same coordinate convention as OpenGL. osgViewer's default is inverted.
282        Viewer->getEventQueue()->getCurrentEventState()->setMouseYOrientation(osgGA::GUIEventAdapter::Y_INCREASING_UPWARDS);
283        // Use a trackball manipulator...matches nicely with the Mighty Mouse Scrollball.
284        Viewer->setCameraManipulator(new osgGA::TrackballManipulator);
285}
286
287- (void) initAnimationTimer
288{
289        // Cocoa is event driven, so by default, there is nothing to trigger redraws for animation.
290        // The easiest way to animate is to set a repeating NSTimer which triggers a redraw.
291        SEL the_selector;
292        NSMethodSignature* a_signature;
293        NSInvocation* an_invocation;
294        // animationCallback is my animation callback method
295        the_selector = @selector( animationCallback );
296        a_signature = [ViewerCocoa instanceMethodSignatureForSelector:the_selector];
297        an_invocation = [NSInvocation invocationWithMethodSignature:a_signature] ;
298        [an_invocation setSelector:the_selector];
299        [an_invocation setTarget:self];
300       
301        animationTimer = [NSTimer
302                scheduledTimerWithTimeInterval:1.0/60.0 // fps
303                invocation:an_invocation
304                repeats:YES];
305        [animationTimer retain];
306       
307        // For single threaded apps like this one,
308        // Cocoa seems to block timers or events sometimes. This can be seen
309        // when I'm animating (via a timer) and you open an popup box or move a slider.
310        // Apparently, sheets and dialogs can also block (try printing).
311        // To work around this, Cocoa provides different run-loop modes. I need to
312        // specify the modes to avoid the blockage.
313        // NSDefaultRunLoopMode seems to be the default. I don't think I need to explicitly
314        // set this one, but just in case, I will set it anyway.
315        [[NSRunLoop currentRunLoop] addTimer:animationTimer forMode:NSDefaultRunLoopMode];
316        // This seems to be the one for preventing blocking on other events (popup box, slider, etc)
317        [[NSRunLoop currentRunLoop] addTimer:animationTimer forMode:NSEventTrackingRunLoopMode];
318        // This seems to be the one for dialogs.
319        [[NSRunLoop currentRunLoop] addTimer:animationTimer forMode:NSModalPanelRunLoopMode];
320}
321
322- (void) dealloc
323{
324        [animationTimer invalidate];
325        [animationTimer release];
326        delete Viewer;
327        Viewer = NULL;
328        [super dealloc];
329}
330
331- (void) finalize
332{
333        delete Viewer;
334        Viewer = NULL;
335        [super finalize];
336}
337
338/* NSOpenGLView defines this method to be called (only once) after the OpenGL
339 * context is created and made the current context. It is intended to be used to setup
340 * your initial OpenGL state. This seems like a good place to initialize the
341 * OSG stuff. This method exists in 10.3 and later. If you are running pre-10.3, you
342 * must manually call this method sometime after the OpenGL context is created and
343 * made current, or refactor this code.
344 */
345- (void) prepareOpenGL
346{
347        [super prepareOpenGL];
348       
349        // The NSOpenGLCPSwapInterval seems to be vsync. If 1, buffers are swapped with vertical refresh.
350        // If 0, flushBuffer will execute as soon as possible.
351        long swap_interval = 1 ;
352    [[self openGLContext] setValues:&swap_interval forParameter:NSOpenGLCPSwapInterval];
353
354
355        // Try new multithreaded OpenGL engine?
356        // See Technical Note TN2085 Enabling multi-threaded execution of the OpenGL framework
357        // http://developer.apple.com/technotes/tn2006/tn2085.html
358        uint64_t num_cpus = 0;
359        size_t num_cpus_length = sizeof(num_cpus);
360        // Multithreaded engine only benefits with muliple CPUs, so do CPU count check
361        if(sysctlbyname("hw.activecpu", &num_cpus, &num_cpus_length, NULL, 0) == 0)
362        {
363//              NSLog(@"Num CPUs=%d", num_cpus);
364                if(num_cpus >= 2)
365                {
366                        // Cleared to enable multi-threaded engine
367                        CGLError error_val = CGLEnable((CGLContextObj)[[self openGLContext] CGLContextObj], kCGLCEMPEngine);
368                        if(error_val != 0)
369                        {
370                                // The likelihood of failure seems quite high on older hardware, at least for now.
371                                // NSLog(@"Failed to enable Multithreaded OpenGL Engine: %s", CGLErrorString(error_val));
372                                isUsingMultithreadedOpenGLEngine = NO;
373                        }
374                        else
375                        {
376                                // NSLog(@"Success! Multithreaded OpenGL Engine activated!");
377                                isUsingMultithreadedOpenGLEngine = YES;
378                        }
379                }
380                else
381                {
382                        isUsingMultithreadedOpenGLEngine = NO;
383                }
384        }
385
386        // This is also might be a good place to setup OpenGL state that OSG doesn't control.
387        glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST);
388        glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
389        glHint(GL_POINT_SMOOTH_HINT, GL_NICEST);
390        glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
391
392/*
393        GLint maxbuffers[1];
394        glGetIntegerv(GL_MAX_COLOR_ATTACHMENTS_EXT, maxbuffers);
395        NSLog(@"GL_MAX_COLOR_ATTACHMENTS=%d", maxbuffers[0]);
396*/
397
398        // We need to tell the osgViewer what the viewport size is
399        [self resizeViewport];
400
401
402        // This is optional:
403        // This is to setup some default text in the OpenGL view so the
404        // user knows that they should drag and drop a model into the view.
405        osg::ref_ptr<osgText::Text> default_text = new osgText::Text;
406
407    default_text->setAlignment(osgText::Text::CENTER_CENTER);
408    default_text->setBackdropType(osgText::Text::OUTLINE);
409//      default_text->setBackdropImplementation(osgText::Text::POLYGON_OFFSET);
410        default_text->setColor(osg::Vec4(1.0, 1.0, 0.0, 1.0));
411        default_text->setBackdropColor(osg::Vec4(0.0, 0.0, 0.0, 1.0));
412    default_text->setAxisAlignment(osgText::Text::XZ_PLANE);
413       
414        // We should use a (Cocoa) localizable string instead of a hard coded string.
415//      default_text->setText("Drag-and-Drop\nyour .osg model here!");
416        // The first string is the key name (you need a Localizable.strings file for your Nib). The second string is just a comment.
417        NSString* localized_string = NSLocalizedString(@"DragAndDropHere", @"Drag-and-Drop\nyour .osg model here!");
418        default_text->setText([localized_string UTF8String]);
419       
420        osg::ref_ptr<osg::Geode> the_geode = new osg::Geode;
421        the_geode->addDrawable(default_text.get());
422
423        Viewer->setSceneData(the_geode.get());
424}
425
426/* disableScreenUpdatesUntilFlush was introduced in Tiger. It will prevent
427 * unnecessary screen flashing caused by NSSplitViews or NSScrollviews.
428 * From Apple's release notes:
429 
430NSWindow -disableScreenUpdatesUntilFlush API (Section added since WWDC)
431
432When a view that renders to a hardware surface (such as an OpenGL view) is placed in an NSScrollView or NSSplitView, there can be a noticeable flicker or lag when the scroll position or splitter position is moved. This happens because each move of the hardware surface takes effect immediately, before the remaining window content has had the chance to draw and flush.
433
434To enable applications to eliminate this visual artifact, Tiger AppKit provides a new NSWindow message, -disableScreenUpdatesUntilFlush. This message asks a window to suspend screen updates until the window's updated content is subsequently flushed to the screen. This message can be sent from a view that is about to move its hardware surface, to insure that the hardware surface move and window redisplay will be visually synchronized. The window responds by immediately disabling screen updates via a call to NSDisableScreenUpdates(), and setting a flag that will cause it to call NSEnableScreenUpdates() later, when the window flushes. It is permissible to send this message to a given window more than once during a given window update cycle; the window will only suspend and re-enable updates once during that cycle.
435
436A view class that renders to a hardware surface can send this message from an override of -renewGState (a method that is is invoked immediately before the view's surface is moved) to effectively defer compositing of the moved surface until the window has finished drawing and flushing its remaining content.
437A -respondsToSelector: check has been used to provide compatibility with previous system releases. On pre-Tiger systems, where NSWindow has no -disableScreenUpdatesUntilFlush method, the -renewGState override will have no effect.
438 */
439- (void) renewGState
440{
441    NSWindow* the_window = [self window];
442    if([the_window respondsToSelector:@selector(disableScreenUpdatesUntilFlush)])
443        {
444                [the_window disableScreenUpdatesUntilFlush];
445    }
446    [super renewGState];
447}
448
449
450/* When you minimize an app, you usually can see its shrunken contents
451 * in the dock. However, an OpenGL view by default only produces a blank
452 * white window. So we use this method to do an image capture of our view
453 * which will be used as its minimized picture.
454 * (A possible enhancement to consider is to update the picture over time.)
455 */
456- (void) prepareForMiniaturization:(NSNotification*)notification
457{
458        NSBitmapImageRep* ns_image_rep = [self renderOpenGLSceneToFramebuffer];
459        if([self lockFocusIfCanDraw])
460        {
461                [ns_image_rep draw];
462                [self unlockFocus];
463                [[self window] flushWindow];
464        }
465}
466
467/* Allow people to easily query if the multithreaded OpenGL engine is activated.
468 */
469- (BOOL) isUsingMultithreadedOpenGLEngine
470{
471        return isUsingMultithreadedOpenGLEngine;
472}
473
474
475////////////////////////////////////////////////////////////////////////
476/////////////////////////// End Init Stuff /////////////////////////////
477////////////////////////////////////////////////////////////////////////
478
479
480
481////////////////////////////////////////////////////////////////////////
482/////////////////////////// Mouse Stuff ////////////////////////////////
483////////////////////////////////////////////////////////////////////////
484
485- (void) mouseDown:(NSEvent*)the_event
486{
487        // Because many Mac users have only a 1-button mouse, we should provide ways
488        // to access the button 2 and 3 actions of osgViewer.
489        // I will use the Ctrl modifer to represent right-clicking
490        // and Option modifier to represent middle clicking.
491        if([the_event modifierFlags] & NSControlKeyMask)
492        {
493                [self setIsUsingCtrlClick:YES];
494                [self doRightMouseButtonDown:the_event];
495        }
496        else if([the_event modifierFlags] & NSAlternateKeyMask)
497        {
498                [self setIsUsingOptionClick:YES];
499                [self doMiddleMouseButtonDown:the_event];
500        }
501        else if([the_event modifierFlags] & NSCommandKeyMask)
502        {
503                [self startDragAndDropAsSource:the_event];
504        }
505        else
506        {
507                [self doLeftMouseButtonDown:the_event];
508        }
509}
510
511- (void) mouseDragged:(NSEvent*)the_event
512{
513        // We must convert the mouse event locations from the window coordinate system to the
514        // local view coordinate system.
515        NSPoint the_point = [the_event locationInWindow];
516    NSPoint converted_point = [self convertPoint:the_point fromView:nil];
517       
518        Viewer->getEventQueue()->mouseMotion(converted_point.x, converted_point.y);
519        [self setNeedsDisplay:YES];
520}
521
522- (void) mouseUp:(NSEvent*)the_event
523{
524        // Because many Mac users have only a 1-button mouse, we should provide ways
525        // to access the button 2 and 3 actions of osgViewer.
526        // I will use the Ctrl modifer to represent right-clicking
527        // and Option modifier to represent middle clicking.
528        if([self isUsingCtrlClick] == YES)
529        {
530                [self setIsUsingCtrlClick:NO];
531                [self doRightMouseButtonUp:the_event];
532        }
533        else if([self isUsingOptionClick] == YES)
534        {
535                [self setIsUsingOptionClick:NO];
536                [self doMiddleMouseButtonUp:the_event];
537        }
538        else
539        {
540                [self doLeftMouseButtonUp:the_event];
541        }
542}
543
544- (void) rightMouseDown:(NSEvent*)the_event
545{
546        [self doRightMouseButtonDown:the_event];
547}
548
549- (void) rightMouseDragged:(NSEvent*)the_event
550{
551        // We must convert the mouse event locations from the window coordinate system to the
552        // local view coordinate system.
553        NSPoint the_point = [the_event locationInWindow];
554    NSPoint converted_point = [self convertPoint:the_point fromView:nil];
555       
556        Viewer->getEventQueue()->mouseMotion(converted_point.x, converted_point.y);
557        [self setNeedsDisplay:YES];
558}
559
560- (void) rightMouseUp:(NSEvent*)the_event
561{
562        [self doRightMouseButtonUp:the_event];
563}
564
565// "otherMouse" seems to capture middle button and any other buttons beyond (4th, etc).
566- (void) otherMouseDown:(NSEvent*)the_event
567{
568        // Button 0 is left
569        // Button 1 is right
570        // Button 2 is middle
571        // Button 3 keeps going
572        // osgViewer expects 1 for left, 3 for right, 2 for middle
573        // osgViewer has a reversed number mapping for right and middle compared to Cocoa
574        if([the_event buttonNumber] == 2)
575        {
576                [self doMiddleMouseButtonDown:the_event];
577        }
578        else // buttonNumber should be 3,4,5,etc; must map to 4,5,6,etc in osgViewer
579        {
580                [self doExtraMouseButtonDown:the_event buttonNumber:[the_event buttonNumber]];
581        }
582}
583
584- (void) otherMouseDragged:(NSEvent*)the_event
585{
586        // We must convert the mouse event locations from the window coordinate system to the
587        // local view coordinate system.
588        NSPoint the_point = [the_event locationInWindow];
589    NSPoint converted_point = [self convertPoint:the_point fromView:nil];
590       
591        Viewer->getEventQueue()->mouseMotion(converted_point.x, converted_point.y);
592        [self setNeedsDisplay:YES];
593}
594
595// "otherMouse" seems to capture middle button and any other buttons beyond (4th, etc).
596- (void) otherMouseUp:(NSEvent*)the_event
597{
598        // Button 0 is left
599        // Button 1 is right
600        // Button 2 is middle
601        // Button 3 keeps going
602        // osgViewer expects 1 for left, 3 for right, 2 for middle
603        // osgViewer has a reversed number mapping for right and middle compared to Cocoa
604        if([the_event buttonNumber] == 2)
605        {
606                [self doMiddleMouseButtonUp:the_event];
607        }
608        else // buttonNumber should be 3,4,5,etc; must map to 4,5,6,etc in osgViewer
609        {
610                // I don't think osgViewer does anything for these additional buttons,
611                // but just in case, pass them along. But as a Cocoa programmer, you might
612                // think about things you can do natively here instead of passing the buck.
613        }       [self doExtraMouseButtonUp:the_event buttonNumber:[the_event buttonNumber]];
614}
615
616- (void) setIsUsingCtrlClick:(BOOL)is_using_ctrl_click
617{
618        isUsingCtrlClick = is_using_ctrl_click;
619}
620
621- (BOOL) isUsingCtrlClick
622{
623        return isUsingCtrlClick;
624}
625
626- (void) setIsUsingOptionClick:(BOOL)is_using_option_click
627{
628        isUsingOptionClick = is_using_option_click;
629}
630
631- (BOOL) isUsingOptionClick
632{
633        return isUsingOptionClick;
634}
635
636
637- (void) doLeftMouseButtonDown:(NSEvent*)the_event
638{
639        // We must convert the mouse event locations from the window coordinate system to the
640        // local view coordinate system.
641        NSPoint the_point = [the_event locationInWindow];
642    NSPoint converted_point = [self convertPoint:the_point fromView:nil];
643        if([the_event clickCount] == 1)
644        {
645                Viewer->getEventQueue()->mouseButtonPress(converted_point.x, converted_point.y, 1);
646        }
647        else
648        {
649                Viewer->getEventQueue()->mouseDoubleButtonPress(converted_point.x, converted_point.y, 1);
650        }
651        [self setNeedsDisplay:YES];
652}
653
654- (void) doLeftMouseButtonUp:(NSEvent*)the_event
655{
656        // We must convert the mouse event locations from the window coordinate system to the
657        // local view coordinate system.
658        NSPoint the_point = [the_event locationInWindow];
659    NSPoint converted_point = [self convertPoint:the_point fromView:nil];
660       
661        Viewer->getEventQueue()->mouseButtonRelease(converted_point.x, converted_point.y, 1);
662        [self setNeedsDisplay:YES];
663}
664
665- (void) doRightMouseButtonDown:(NSEvent*)the_event
666{
667        // We must convert the mouse event locations from the window coordinate system to the
668        // local view coordinate system.
669        NSPoint the_point = [the_event locationInWindow];
670    NSPoint converted_point = [self convertPoint:the_point fromView:nil];
671        if([the_event clickCount] == 1)
672        {
673                Viewer->getEventQueue()->mouseButtonPress(converted_point.x, converted_point.y, 3);
674        }
675        else
676        {
677                Viewer->getEventQueue()->mouseDoubleButtonPress(converted_point.x, converted_point.y, 3);
678        }
679        [self setNeedsDisplay:YES];
680}
681
682
683- (void) doRightMouseButtonUp:(NSEvent*)the_event
684{
685        // We must convert the mouse event locations from the window coordinate system to the
686        // local view coordinate system.
687        NSPoint the_point = [the_event locationInWindow];
688    NSPoint converted_point = [self convertPoint:the_point fromView:nil];
689       
690        Viewer->getEventQueue()->mouseButtonRelease(converted_point.x, converted_point.y, 3);
691        [self setNeedsDisplay:YES];
692}
693
694- (void) doMiddleMouseButtonDown:(NSEvent*)the_event
695{
696        // We must convert the mouse event locations from the window coordinate system to the
697        // local view coordinate system.
698        NSPoint the_point = [the_event locationInWindow];
699    NSPoint converted_point = [self convertPoint:the_point fromView:nil];
700       
701        if([the_event clickCount] == 1)
702        {
703                Viewer->getEventQueue()->mouseButtonPress(converted_point.x, converted_point.y, 2);
704        }
705        else
706        {
707                Viewer->getEventQueue()->mouseDoubleButtonPress(converted_point.x, converted_point.y, 2);
708        }
709        [self setNeedsDisplay:YES];
710}
711
712- (void) doExtraMouseButtonDown:(NSEvent*)the_event buttonNumber:(int)button_number
713{
714        // We must convert the mouse event locations from the window coordinate system to the
715        // local view coordinate system.
716        NSPoint the_point = [the_event locationInWindow];
717    NSPoint converted_point = [self convertPoint:the_point fromView:nil];
718
719        if([the_event clickCount] == 1)
720        {
721                Viewer->getEventQueue()->mouseButtonPress(converted_point.x, converted_point.y, button_number+1);
722        }
723        else
724        {
725                Viewer->getEventQueue()->mouseDoubleButtonPress(converted_point.x, converted_point.y, button_number+1);
726        }
727        [self setNeedsDisplay:YES];
728}
729
730
731- (void) doMiddleMouseButtonUp:(NSEvent*)the_event
732{
733        // We must convert the mouse event locations from the window coordinate system to the
734        // local view coordinate system.        NSPoint the_point = [the_event locationInWindow];
735        NSPoint the_point = [the_event locationInWindow];
736        NSPoint converted_point = [self convertPoint:the_point fromView:nil];
737        Viewer->getEventQueue()->mouseButtonRelease(converted_point.x, converted_point.y, 2);
738        [self setNeedsDisplay:YES];
739}
740
741- (void) doExtraMouseButtonUp:(NSEvent*)the_event buttonNumber:(int)button_number
742{
743        // We must convert the mouse event locations from the window coordinate system to the
744        // local view coordinate system.        NSPoint the_point = [the_event locationInWindow];
745        NSPoint the_point = [the_event locationInWindow];
746        NSPoint converted_point = [self convertPoint:the_point fromView:nil];
747        Viewer->getEventQueue()->mouseButtonRelease(converted_point.x, converted_point.y, button_number+1);
748       
749        [self setNeedsDisplay:YES];
750}
751
752// This is a job for Mighty Mouse!
753// For the most fluid experience turn on 360 degree mode availble in 10.4.8+.
754// With your Mighty Mouse plugged in,
755// open 'Keyboard & Mouse' in 'System Preferences'.
756// Select the 'Mouse' tab.
757// Under 'Scrolling Options' select '360 degree'.
758// That should improve diagonal scrolling.
759// You should also be able to use 'two-finger scrolling' on newer laptops.
760- (void) scrollWheel:(NSEvent*)the_event
761{
762        // Unfortunately, it turns out mouseScroll2D doesn't actually do anything.
763        // The camera manipulators don't seem to implement any code that utilize the scroll values.
764        // This this call does nothing.
765//      Viewer->getEventQueue()->mouseScroll2D([the_event deltaX], [the_event deltaY]);
766
767        // With the absense of a useful mouseScroll2D API, we can manually simulate the desired effect.
768        NSPoint the_point = [the_event locationInWindow];
769        NSPoint converted_point = [self convertPoint:the_point fromView:nil];
770        Viewer->getEventQueue()->mouseButtonPress(converted_point.x, converted_point.y, 1);
771        Viewer->getEventQueue()->mouseMotion(converted_point.x + -[the_event deltaX], converted_point.y + [the_event deltaY]);
772        Viewer->getEventQueue()->mouseButtonRelease(converted_point.x + -[the_event deltaX], converted_point.y + [the_event deltaY], 1);
773
774        [self setNeedsDisplay:YES];
775}
776
777////////////////////////////////////////////////////////////////////////
778/////////////////////////// End Mouse Stuff ////////////////////////////
779////////////////////////////////////////////////////////////////////////
780
781////////////////////////////////////////////////////////////////////////
782/////////////////////////// Keyboard Stuff /////////////////////////////
783////////////////////////////////////////////////////////////////////////
784// Needed to accept keyboard events
785- (BOOL) acceptsFirstResponder
786{
787        return YES;
788}
789
790- (void) keyDown:(NSEvent*)the_event
791{
792        // Do you want characters or charactersIgnoringModifiers?
793        NSString* event_characters = [the_event characters];
794//      NSString* event_characters = [the_event charactersIgnoringModifiers];
795
796        unichar unicode_character = [event_characters characterAtIndex:0];
797//      NSLog(@"unicode_character: %d", unicode_character);
798        Viewer->getEventQueue()->keyPress(static_cast<osgGA::GUIEventAdapter::KeySymbol>(unicode_character));
799
800        [self setNeedsDisplay:YES];
801}
802
803- (void) keyUp:(NSEvent*)the_event
804{
805        // Do you want characters or charactersIgnoringModifiers?
806        NSString* event_characters = [the_event characters];
807//      NSString* event_characters = [the_event charactersIgnoringModifiers];
808        unichar unicode_character = [event_characters characterAtIndex:0];
809        Viewer->getEventQueue()->keyRelease(static_cast<osgGA::GUIEventAdapter::KeySymbol>(unicode_character));
810        [self setNeedsDisplay:YES];
811}
812
813////////////////////////////////////////////////////////////////////////
814/////////////////////////// End Keyboard Stuff /////////////////////////
815////////////////////////////////////////////////////////////////////////
816
817////////////////////////////////////////////////////////////////////////
818/////////////////////////// View and Draw Stuff ////////////////////////
819////////////////////////////////////////////////////////////////////////
820
821// This method is periodically called by my timer.
822- (void) animationCallback
823{
824        // Simply notify Cocoa that a drawRect needs to take place.
825        // Potential optimization is to query the OSG stuff to find out if a redraw is actually necessary.
826        [self setNeedsDisplay:YES];
827}
828
829// This is an optional optimization. This states you don't have a transparent view/window.
830// Obviously don't use this or set it to NO if you intend for your view to be see-through.
831- (BOOL) isOpaque
832{
833        return YES;
834}
835
836// Resolution Independent UI is coming... (Tiger's Quartz Debug already has the tool.)
837// We must think in 'point sizes', not pixel sizes, so a conversion is needed for OpenGL.
838- (void) resizeViewport
839{
840        NSSize size_in_points = [self bounds].size;
841        // This coordinate system conversion seems to make things work with Quartz Debug.
842        NSSize size_in_window_coordinates = [self convertSize:size_in_points toView:nil];
843        Viewer->getEventQueue()->windowResize(0, 0, size_in_window_coordinates.width, size_in_window_coordinates.height);
844        graphicsWindow->resized(0, 0, size_in_window_coordinates.width, size_in_window_coordinates.height);
845}
846
847// For window resize
848- (void) reshape
849{
850        [super reshape];
851        [self resizeViewport];
852}
853
854// This is the code that actually draws.
855// Remember you shouldn't call drawRect: directly and should use setNeedsDisplay:YES
856// This is so the operating system can optimize when a draw is actually needed.
857// (e.g. No sense drawing when the application is hidden.)
858- (void) drawRect:(NSRect)the_rect
859{
860        if([[NSGraphicsContext currentContext] isDrawingToScreen])
861        {
862//              [[self openGLContext] makeCurrentContext];
863                Viewer->frame();
864                [[self openGLContext] flushBuffer];
865        }
866        else // This is usually the print case
867        {
868//              [[self openGLContext] makeCurrentContext];
869
870                // FIXME: We should be computing a size that fits best to the paper target
871                NSSize size_in_points = [self bounds].size;
872                NSSize size_in_window_coordinates = [self convertSize:size_in_points toView:nil];
873                NSBitmapImageRep * bitmap_image_rep = [self renderOpenGLSceneToFramebufferAsFormat:GL_RGB viewWidth:size_in_window_coordinates.width viewHeight:size_in_window_coordinates.height];
874               
875                NSImage* ns_image = [self imageFromBitmapImageRep:bitmap_image_rep];
876
877                if(ns_image)
878                {
879                        NSSize image_size = [ns_image size];
880                        [ns_image drawAtPoint:NSMakePoint(0.0, 0.0)
881                                        fromRect: NSMakeRect(0.0, 0.0, image_size.width, image_size.height)
882//                                 operation: NSCompositeSourceOver
883                                   operation: NSCompositeCopy
884                                        fraction: 1.0];         
885                }
886                else
887                {
888                        NSLog(@"Image not valid");
889                }
890        }
891}
892
893
894/* Optional render to framebuffer stuff below. The code renders offscreen to assist in screen capture stuff.
895 * This can be useful for things like the Dock minimization picture, drag-and-drop dropImage, copy and paste,
896 * and printing.
897 */
898
899// Convenience version. Will use the current view's bounds and produce an RGB image with the current clear color.
900- (NSBitmapImageRep*) renderOpenGLSceneToFramebuffer
901{
902        NSSize size_in_points = [self bounds].size;
903        NSSize size_in_window_coordinates = [self convertSize:size_in_points toView:nil];
904        const osg::Vec4& clear_color = Viewer->getCamera()->getClearColor();
905
906        return [self renderOpenGLSceneToFramebufferAsFormat:GL_RGB viewWidth:size_in_window_coordinates.width viewHeight:size_in_window_coordinates.height clearColorRed:clear_color[0] clearColorGreen:clear_color[1] clearColorBlue:clear_color[2] clearColorAlpha:clear_color[3]];
907}
908
909// Convenience version. Allows you to specify the view and height and format, but uses the current the current clear color.
910- (NSBitmapImageRep*) renderOpenGLSceneToFramebufferAsFormat:(int)gl_format viewWidth:(float)view_width viewHeight:(float)view_height
911{
912        const osg::Vec4& clear_color = Viewer->getCamera()->getClearColor();
913
914        return [self renderOpenGLSceneToFramebufferAsFormat:gl_format viewWidth:view_width viewHeight:view_height clearColorRed:clear_color[0] clearColorGreen:clear_color[1] clearColorBlue:clear_color[2] clearColorAlpha:clear_color[3]];
915}
916
917// Renders to an offscreen buffer and returns a copy of the data to an NSBitmapImageRep.
918// Allows you to specify the gl_format, width and height, and the glClearColor
919// gl_format is only GL_RGB or GLRGBA.
920- (NSBitmapImageRep*) renderOpenGLSceneToFramebufferAsFormat:(int)gl_format viewWidth:(float)view_width viewHeight:(float)view_height clearColorRed:(float)clear_red clearColorGreen:(float)clear_green clearColorBlue:(float)clear_blue clearColorAlpha:(float)clear_alpha
921{
922        // Round values and bring to closest integer.
923        int viewport_width = (int)(view_width + 0.5f);
924        int viewport_height = (int)(view_height + 0.5f);
925       
926        NSBitmapImageRep* ns_image_rep;
927        osg::ref_ptr<osg::Image> osg_image = new osg::Image;
928       
929        if(GL_RGBA == gl_format)
930        {
931                // Introduced in 10.4, but gives much better looking results if you utilize transparency
932                if([NSBitmapImageRep instancesRespondToSelector:@selector(initWithBitmapDataPlanes:pixelsWide:pixelsHigh:bitsPerSample:samplesPerPixel:hasAlpha:isPlanar:colorSpaceName:bitmapFormat:bytesPerRow:bitsPerPixel:)])
933                {
934                        ns_image_rep = [[[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
935                                                                                                  pixelsWide:viewport_width
936                                                                                                  pixelsHigh:viewport_height
937                                                                                           bitsPerSample:8
938                                                                                         samplesPerPixel:4
939                                                                                                        hasAlpha:YES
940                                                                                                        isPlanar:NO
941                                                                                          colorSpaceName:NSCalibratedRGBColorSpace
942                                                                                                bitmapFormat:NSAlphaNonpremultipliedBitmapFormat // 10.4+, gives much better looking results if you utilize transparency
943                                                                                                 bytesPerRow:osg::Image::computeRowWidthInBytes(viewport_width, GL_RGBA, GL_UNSIGNED_BYTE, 1)
944                                                                                                bitsPerPixel:32]
945                                        autorelease];
946                }
947                else // fallback for 10.0 to 10.3
948                {
949                        ns_image_rep = [[[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
950                                                                                                  pixelsWide:viewport_width
951                                                                                                  pixelsHigh:viewport_height
952                                                                                           bitsPerSample:8
953                                                                                         samplesPerPixel:4
954                                                                                                        hasAlpha:YES
955                                                                                                        isPlanar:NO
956                                                                                          colorSpaceName:NSCalibratedRGBColorSpace
957                                                                                                // bitmapFormat:NSAlphaNonpremultipliedBitmapFormat // 10.4+, gives much better looking results if you utilize transparency
958                                                                                                 bytesPerRow:osg::Image::computeRowWidthInBytes(viewport_width, GL_RGBA, GL_UNSIGNED_BYTE, 1)
959                                                                                                bitsPerPixel:32]
960                                        autorelease];
961                }
962                // This is an optimization. Instead of creating data in both an osg::Image and NSBitmapImageRep,
963                // Allocate just the memory in the NSBitmapImageRep and give the osg::Image a reference to the data.
964                // I let NSBitmapImageRep control the memory because I think it will be easier to deal with
965                // memory management in the cases where it must interact with other Cocoa mechanisms like Drag-and-drop
966                // where the memory persistence is less obvious. Make sure that you don't expect to use the osg::Image
967                // outside the scope of this function because there is no telling when the data will be removed out
968                // from under it by Cocoa since osg::Image will not retain.
969                osg_image->setImage([ns_image_rep pixelsWide], [ns_image_rep pixelsHigh], 1,  GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, [ns_image_rep bitmapData], osg::Image::NO_DELETE, 1);
970        }
971        else if(GL_RGB == gl_format)
972        {
973                ns_image_rep = [[[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
974                                                                                                  pixelsWide:viewport_width
975                                                                                                  pixelsHigh:viewport_height
976                                                                                           bitsPerSample:8
977                                                                                         samplesPerPixel:3
978                                                                                                        hasAlpha:NO
979                                                                                                        isPlanar:NO
980                                                                                          colorSpaceName:NSCalibratedRGBColorSpace
981                                                                                                // bitmapFormat:(NSBitmapFormat)0 // 10.4+
982                                                                                                 bytesPerRow:osg::Image::computeRowWidthInBytes(viewport_width, GL_RGB, GL_UNSIGNED_BYTE, 1)
983                                                                                                bitsPerPixel:24]
984                                        autorelease];
985
986                // This is an optimization. Instead of creating data in both an osg::Image and NSBitmapImageRep,
987                // Allocate just the memory in the NSBitmapImageRep and give the osg::Image a reference to the data.
988                // I let NSBitmapImageRep control the memory because I think it will be easier to deal with
989                // memory management in the cases where it must interact with other Cocoa mechanisms like Drag-and-drop
990                // where the memory persistence is less obvious. Make sure that you don't expect to use the osg::Image
991                // outside the scope of this function because there is no telling when the data will be removed out
992                // from under it by Cocoa since osg::Image will not retain.
993                osg_image->setImage([ns_image_rep pixelsWide], [ns_image_rep pixelsHigh], 1,  GL_RGB, GL_RGB, GL_UNSIGNED_BYTE, [ns_image_rep bitmapData], osg::Image::NO_DELETE, 1);
994        }
995        else
996        {
997                NSLog(@"Sorry, unsupported format in renderOpenGLSceneToFramebufferAsFormat");
998                return nil;
999        }
1000
1001        // Can't find a way to query Viewer for the current values, so recompute current view size.
1002        NSSize original_size_in_points = [self bounds].size;
1003        NSSize original_size_in_window_coordinates = [self convertSize:original_size_in_points toView:nil];
1004//      Viewer->getEventQueue()->windowResize(0, 0, original_size_in_window_coordinates.width, original_size_in_window_coordinates.height);
1005
1006        Viewer->getEventQueue()->windowResize(0, 0, viewport_width, viewport_height);
1007        graphicsWindow->resized(0, 0, viewport_width, viewport_height);
1008
1009        /*
1010         * I want to use a Framebuffer Object because it seems to be the OpenGL sanctioned way of rendering offscreen.
1011         * Also, I want to try to decouple the image capture from the onscreen rendering. This is potentially useful
1012         * for two reasons:
1013         * 1) You may want to customize the image dimensions to best fit the situation (consider printing to a page to fit)
1014         * 2) You may want to customize the scene for the target (consider special output for a printer, or removed data for a thumbnail)
1015         * Unfortunately, I have hit two problems.
1016         * 1) osg::Camera (which seems to be the way to access Framebuffer Objects in OSG) doesn't seem to capture if it is the root node.
1017         * The workaround is to copy the camera attributes into another camera, and then add a second camera node into the scene.
1018         * I'm hoping OSG will simplify this in the future.
1019         * 2) I may have encountered a bug. Under some circumstances, the offscreen renderbuffer seems to get drawn into the onscreen view
1020         * when using a DragImage for drag-and-drop. I reproduced a non-OSG example, but learned I missed two important FBO calls which trigger gl errors.
1021         * So I'm wondering if OSG made the same mistake.
1022         * But the problem doesn't seem critical. It just looks bad.
1023         */
1024        //NSLog(@"Before camera glGetError: %s", gluErrorString(glGetError()));
1025        osg::Camera* root_camera = Viewer->getCamera();
1026
1027        // I originally tried the clone() method and the copy construction, but it didn't work right,
1028        // so I manually copy the attributes.
1029        osg::Camera* the_camera = new osg::Camera;
1030
1031        the_camera->setClearMask(root_camera->getClearMask());
1032        the_camera->setProjectionMatrix(root_camera->getProjectionMatrix());
1033        the_camera->setViewMatrix(root_camera->getViewMatrix());
1034        the_camera->setViewport(root_camera->getViewport());
1035        the_camera->setClearColor(
1036                osg::Vec4(
1037                        clear_red,
1038                        clear_green,
1039                        clear_blue,
1040                        clear_alpha
1041                )
1042        );
1043
1044        // This must be ABSOLUTE_RF, and not a copy of the root camera because the transforms are additive.
1045        the_camera->setReferenceFrame(osg::Transform::ABSOLUTE_RF);
1046
1047        // We need to insert the new (second) camera into the scene (below the root camera) and attach
1048        // the scene data to the new camera.
1049        osg::ref_ptr<osg::Node> root_node = Viewer->getSceneData();
1050
1051        the_camera->addChild(root_node.get());
1052        // Don't call (bypass) Viewer's setSceneData, but the underlying SceneView's setSceneData.
1053        // Otherwise, the camera position gets reset to the home position.
1054        Viewer->setSceneData(the_camera);
1055
1056        // set the camera to render before the main camera.
1057        the_camera->setRenderOrder(osg::Camera::PRE_RENDER);
1058
1059        // tell the camera to use OpenGL frame buffer object where supported.
1060        the_camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT);
1061
1062
1063        // attach the image so its copied on each frame.
1064        the_camera->attach(osg::Camera::COLOR_BUFFER, osg_image.get());
1065
1066
1067        //NSLog(@"Before frame(), glGetError: %s", gluErrorString(glGetError()));
1068
1069
1070        // Render the scene
1071        Viewer->frame();
1072
1073        // Not sure if I really need this (seems to work without it), and if so, not sure if I need flush or finish
1074        glFlush();
1075//      glFinish();
1076
1077        //NSLog(@"After flush(), glGetError: %s", gluErrorString(glGetError()));
1078
1079
1080
1081        // The image is upside-down to Cocoa, so invert it.
1082        osg_image.get()->flipVertical();
1083
1084        // Clean up everything I changed
1085//      the_camera->detach(osg::Camera::COLOR_BUFFER);
1086//      the_camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER);
1087        // Don't call (bypass) Viewer's setSceneData, but the underlying SceneView's setSceneData.
1088        // Otherwise, the camera position gets reset to the home position.
1089        Viewer->setSceneData(root_node.get());
1090        Viewer->getEventQueue()->windowResize(0, 0, original_size_in_window_coordinates.width, original_size_in_window_coordinates.height);
1091        graphicsWindow->resized(0, 0, original_size_in_window_coordinates.width, original_size_in_window_coordinates.height);
1092
1093
1094        // Ugh. Because of the bug I mentioned, I'm losing the picture in the display when I print.
1095        [self setNeedsDisplay:YES];
1096        //NSLog(@"at return, glGetError: %s", gluErrorString(glGetError()));
1097
1098        return ns_image_rep;
1099}
1100
1101// Convenience method
1102- (NSImage*)imageFromBitmapImageRep:(NSBitmapImageRep*)bitmap_image_rep
1103{
1104        if(nil == bitmap_image_rep)
1105        {
1106                return nil;
1107        }
1108        NSImage* image = [[[NSImage alloc] initWithSize:[bitmap_image_rep size]] autorelease];
1109        [image addRepresentation:bitmap_image_rep];
1110        // This doesn't seem to work as I want it to. The image only gets flipped when rendered in a regular view.
1111        // It doesn't flip for the printer view. I must actually invert the pixels.
1112//      [image setFlipped:YES];
1113        return image;
1114}
1115
1116
1117
1118
1119////////////////////////////////////////////////////////////////////////
1120/////////////////////////// End View and Draw Stuff ////////////////////
1121////////////////////////////////////////////////////////////////////////
1122
1123////////////////////////////////////////////////////////////////////////
1124/////////////////////////// For drag and drop //////////////////////////
1125////////////////////////////////////////////////////////////////////////
1126- (unsigned int) draggingEntered:(id <NSDraggingInfo>)the_sender
1127{
1128        if([the_sender draggingSource] != self)
1129        {
1130                NSPasteboard* paste_board = [the_sender draggingPasteboard];
1131                // I respond to filename types or URL types
1132                NSArray* supported_types = [NSArray arrayWithObjects:NSFilenamesPboardType, NSURLPboardType, nil];
1133                // If any of the supported types are being dragged in, activate the copy operation
1134                NSString* first_type = [paste_board availableTypeFromArray:supported_types];
1135                if(first_type != nil)
1136                {
1137                        return NSDragOperationCopy;
1138                }
1139        }
1140        // Means we don't support this type
1141        return NSDragOperationNone;
1142}
1143
1144// We're not using this method, but here it is as an example.
1145- (void) draggingExited:(id <NSDraggingInfo>)the_sender
1146{
1147}
1148
1149- (BOOL) prepareForDragOperation:(id <NSDraggingInfo>)the_sender
1150{
1151        return YES;
1152}
1153
1154- (BOOL) performDragOperation:(id <NSDraggingInfo>)the_sender
1155{
1156        NSPasteboard* paste_board = [the_sender draggingPasteboard];
1157
1158 
1159    if([[paste_board types] containsObject:NSFilenamesPboardType])
1160        {
1161        NSArray* file_names = [paste_board propertyListForType:NSFilenamesPboardType];
1162//        int number_of_files = [file_names count];
1163                // Exercise for the reader: Try loading all files in the array
1164                NSString* single_file = [file_names objectAtIndex:0];
1165            osg::ref_ptr<osg::Node> loaded_model = osgDB::readNodeFile([single_file fileSystemRepresentation]);
1166                if(!loaded_model)
1167                {
1168                        NSLog(@"File: %@ failed to load", single_file);
1169                        return NO;
1170                }
1171                Viewer->setSceneData(loaded_model.get());
1172                return YES;
1173    }
1174        else if([[paste_board types] containsObject:NSURLPboardType])
1175        {
1176                NSURL* file_url = [NSURL URLFromPasteboard:paste_board];
1177                // See if the URL is valid file path
1178                if(![file_url isFileURL])
1179                {
1180                        NSLog(@"URL: %@ needs to be a file for readNodeFile()", file_url);
1181                        return NO;
1182                }
1183                NSString* file_path = [file_url path];
1184            osg::ref_ptr<osg::Node> loaded_model = osgDB::readNodeFile([file_path fileSystemRepresentation]);
1185                if(!loaded_model)
1186                {
1187                        NSLog(@"URL: %@ failed to load, %@", file_url, file_path);
1188                        return NO;
1189                }
1190                Viewer->setSceneData(loaded_model.get());
1191                return YES;
1192        }
1193    return NO;
1194}
1195
1196// This method isn't really needed (I could move setNeedsDisplay up), but is here as an example
1197- (void) concludeDragOperation:(id <NSDraggingInfo>)the_sender
1198{
1199        [self setNeedsDisplay:YES];
1200}
1201
1202////////////////////////////////////////////////////////////////////////
1203/////////////////////////// End of drag and drop (receiver) ////////////
1204////////////////////////////////////////////////////////////////////////
1205
1206
1207//////////////////////////////////////////////////////////////////////////////////////
1208/////////////////////////// For drag and drop and copy/paste (source) ////////////////
1209//////////////////////////////////////////////////////////////////////////////////////
1210- (IBAction) copy:(id)sender
1211{
1212    NSString* type = NSTIFFPboardType;
1213    NSData* image_data = [self contentsAsDataOfType:type];
1214       
1215    if(image_data)
1216        {
1217        NSPasteboard* general_pboard = [NSPasteboard generalPasteboard];
1218        [general_pboard declareTypes:[NSArray arrayWithObjects:type, nil] owner: nil];
1219        [general_pboard setData:image_data forType:type];
1220    }
1221}
1222
1223- (NSData*) dataWithTIFFOfContentView
1224{
1225        NSBitmapImageRep * image = [self renderOpenGLSceneToFramebuffer];
1226        NSData* data = nil;
1227
1228        if(image != nil)
1229        {
1230                data = [image TIFFRepresentation];
1231        }
1232        return data;
1233}
1234
1235/* Returns a data object containing the current contents of the receiving window */
1236- (NSData*) contentsAsDataOfType:(NSString *)pboardType
1237{
1238        NSData * data = nil;
1239        if ([pboardType isEqualToString: NSTIFFPboardType] == YES)
1240        {
1241                data = [self dataWithTIFFOfContentView];
1242        }
1243    return data;
1244}
1245
1246
1247- (void) startDragAndDropAsSource:(NSEvent*)the_event
1248{
1249        NSPasteboard* drag_paste_board;
1250        NSImage* the_image;
1251        NSSize the_size;
1252        NSPoint the_point;
1253
1254        NSSize size_in_points = [self bounds].size;
1255        NSSize size_in_window_coordinates = [self convertSize:size_in_points toView:nil];
1256
1257        // Create the image that will be dragged
1258        NSString * type = NSTIFFPboardType;
1259
1260        // I want two images. One to be rendered for the target, and one as the drag-image.
1261        // I want the drag-image to be translucent.
1262        // I think this is where render GL_COLOR_ATTACHMENTn (COLOR_BUFFERn?) would be handy.
1263        // But my hardware only returns 1 for glGetIntegerv(GL_MAX_COLOR_ATTACHMENTS_EXT, maxbuffers);
1264        // So I won't bother and will just render twice.
1265        NSBitmapImageRep* bitmap_image_rep = [self renderOpenGLSceneToFramebufferAsFormat:GL_RGB viewWidth:size_in_window_coordinates.width viewHeight:size_in_window_coordinates.height];
1266        NSBitmapImageRep* bitmap_image_rep_transparent_copy = [self renderOpenGLSceneToFramebufferAsFormat:GL_RGBA viewWidth:size_in_window_coordinates.width viewHeight:size_in_window_coordinates.height];
1267//      NSBitmapImageRep* bitmap_image_rep = [self renderOpenGLSceneToFramebufferAsFormat:GL_RGBA viewWidth:size_in_window_coordinates.width viewHeight:size_in_window_coordinates.height clearColorRed:1.0f clearColorGreen:1.0f clearColorBlue:0.0f clearColorAlpha:0.4f];
1268
1269//NSBitmapImageRep* bitmap_image_rep_transparent_copy = bitmap_image_rep;
1270
1271        // 0x32 is an arbitrary number. Basically, I want something between 0 and 0xFF.
1272        Internal_SetAlpha(bitmap_image_rep_transparent_copy, 0x32);
1273
1274        NSData* image_data = [bitmap_image_rep TIFFRepresentation];
1275
1276    if(image_data)
1277        {
1278       
1279                drag_paste_board = [NSPasteboard pasteboardWithName:NSDragPboard];
1280                // is owner:self or nil? (Hillegass says self)
1281        [drag_paste_board declareTypes: [NSArray arrayWithObjects: type, nil] owner: self];
1282        [drag_paste_board setData:image_data forType: type];
1283               
1284                // create an image from the data
1285                the_image = [[NSImage alloc] initWithData:[bitmap_image_rep_transparent_copy TIFFRepresentation]];
1286               
1287                the_point = [self convertPoint:[the_event locationInWindow] fromView:nil];
1288                the_size = [the_image size];
1289               
1290                // shift the point to the center of the image
1291                the_point.x = the_point.x - the_size.width/2.0;
1292                the_point.y = the_point.y - the_size.height/2.0;
1293
1294                // start drag
1295                [self dragImage:the_image
1296                                         at:the_point
1297                                 offset:NSMakeSize(0,0)
1298                                  event:the_event
1299                         pasteboard:drag_paste_board
1300                                 source:self
1301                          slideBack:YES];
1302                         
1303                [the_image release];
1304        }
1305        else
1306        {
1307                NSLog(@"Error, failed to create image data");
1308        }
1309       
1310}
1311
1312//////////////////////////////////////////////////////////////////////////////////////
1313/////////////////////////// For drag and drop and copy/paste (source) ////////////////
1314//////////////////////////////////////////////////////////////////////////////////////
1315
1316
1317
1318
1319
1320
1321////////////////////////////////////////////////////////////////////////
1322/////////////////////////// IBAction examples  /////////////////////////
1323////////////////////////////////////////////////////////////////////////
1324
1325// Connect a button to this to stop and reset the position.
1326- (IBAction) resetPosition:(id)the_sender
1327{
1328        //      osgGA::MatrixManipulator* camera_manipulator = Viewer->getCameraManipulator();
1329        // This only resets the position
1330        //      camera_manipulator->home(0.0);
1331       
1332        // There is no external API from Viewer that I can see that will stop movement.
1333        // So fake the 'spacebar' to stop things and reset.
1334        printf("I'am here");
1335       
1336        Viewer->home();
1337        [self setNeedsDisplay:YES];
1338}
1339
1340// Connect a NSColorWell to this to change color.
1341// A better way to do this is use Cocoa Bindings because it will automatically
1342// synchronize between your model and view, but since this demo lacks a model and controller
1343// aspect it wouldn't do much good.
1344- (IBAction) takeBackgroundColorFrom:(id)the_sender
1345{
1346        NSColor* the_color = [the_sender color];
1347
1348        Viewer->getCamera()->setClearColor(
1349                osg::Vec4(
1350                        [the_color redComponent],
1351                        [the_color greenComponent],
1352                        [the_color blueComponent],
1353                        [the_color alphaComponent]
1354                )
1355        );
1356        [self setNeedsDisplay:YES];
1357}
1358
1359////////////////////////////////////////////////////////////////////////
1360/////////////////////////// End IBAction examples  /////////////////////
1361////////////////////////////////////////////////////////////////////////
1362
1363@end
Note: See TracBrowser for help on using the browser.