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

Revision 6892, 52.4 kB (checked in by robert, 7 years ago)

From Martin Lavery, StatsHandler? added to the ViewerCocoa? example

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        // Builts in Stats handler
273        Viewer->addEventHandler(new osgViewer::StatsHandler);
274#ifdef VIEWER_USE_SHARED_CONTEXTS
275        // Workaround: osgViewer::Viewer automatically increments its context ID values.
276        // Since we're using a shared context, we want all Viewer's to use the same context ID.
277        // There is no API to avoid this behavior, so we need to undo what Viewer's constructor did.
278    graphicsWindow->getState()->setContextID(0);
279        osg::DisplaySettings::instance()->setMaxNumberOfGraphicsContexts(1);
280#endif // VIEWER_USE_SHARED_CONTEXTS
281
282        // Cocoa follows the same coordinate convention as OpenGL. osgViewer's default is inverted.
283        Viewer->getEventQueue()->getCurrentEventState()->setMouseYOrientation(osgGA::GUIEventAdapter::Y_INCREASING_UPWARDS);
284        // Use a trackball manipulator...matches nicely with the Mighty Mouse Scrollball.
285        Viewer->setCameraManipulator(new osgGA::TrackballManipulator);
286       
287}
288
289- (void) initAnimationTimer
290{
291        // Cocoa is event driven, so by default, there is nothing to trigger redraws for animation.
292        // The easiest way to animate is to set a repeating NSTimer which triggers a redraw.
293        SEL the_selector;
294        NSMethodSignature* a_signature;
295        NSInvocation* an_invocation;
296        // animationCallback is my animation callback method
297        the_selector = @selector( animationCallback );
298        a_signature = [ViewerCocoa instanceMethodSignatureForSelector:the_selector];
299        an_invocation = [NSInvocation invocationWithMethodSignature:a_signature] ;
300        [an_invocation setSelector:the_selector];
301        [an_invocation setTarget:self];
302       
303        animationTimer = [NSTimer
304                scheduledTimerWithTimeInterval:1.0/60.0 // fps
305                invocation:an_invocation
306                repeats:YES];
307        [animationTimer retain];
308       
309        // For single threaded apps like this one,
310        // Cocoa seems to block timers or events sometimes. This can be seen
311        // when I'm animating (via a timer) and you open an popup box or move a slider.
312        // Apparently, sheets and dialogs can also block (try printing).
313        // To work around this, Cocoa provides different run-loop modes. I need to
314        // specify the modes to avoid the blockage.
315        // NSDefaultRunLoopMode seems to be the default. I don't think I need to explicitly
316        // set this one, but just in case, I will set it anyway.
317        [[NSRunLoop currentRunLoop] addTimer:animationTimer forMode:NSDefaultRunLoopMode];
318        // This seems to be the one for preventing blocking on other events (popup box, slider, etc)
319        [[NSRunLoop currentRunLoop] addTimer:animationTimer forMode:NSEventTrackingRunLoopMode];
320        // This seems to be the one for dialogs.
321        [[NSRunLoop currentRunLoop] addTimer:animationTimer forMode:NSModalPanelRunLoopMode];
322}
323
324- (void) dealloc
325{
326        [animationTimer invalidate];
327        [animationTimer release];
328        delete Viewer;
329        Viewer = NULL;
330        [super dealloc];
331}
332
333- (void) finalize
334{
335        delete Viewer;
336        Viewer = NULL;
337        [super finalize];
338}
339
340/* NSOpenGLView defines this method to be called (only once) after the OpenGL
341 * context is created and made the current context. It is intended to be used to setup
342 * your initial OpenGL state. This seems like a good place to initialize the
343 * OSG stuff. This method exists in 10.3 and later. If you are running pre-10.3, you
344 * must manually call this method sometime after the OpenGL context is created and
345 * made current, or refactor this code.
346 */
347- (void) prepareOpenGL
348{
349        [super prepareOpenGL];
350       
351        // The NSOpenGLCPSwapInterval seems to be vsync. If 1, buffers are swapped with vertical refresh.
352        // If 0, flushBuffer will execute as soon as possible.
353        long swap_interval = 1 ;
354    [[self openGLContext] setValues:&swap_interval forParameter:NSOpenGLCPSwapInterval];
355
356
357        // Try new multithreaded OpenGL engine?
358        // See Technical Note TN2085 Enabling multi-threaded execution of the OpenGL framework
359        // http://developer.apple.com/technotes/tn2006/tn2085.html
360        uint64_t num_cpus = 0;
361        size_t num_cpus_length = sizeof(num_cpus);
362        // Multithreaded engine only benefits with muliple CPUs, so do CPU count check
363        if(sysctlbyname("hw.activecpu", &num_cpus, &num_cpus_length, NULL, 0) == 0)
364        {
365//              NSLog(@"Num CPUs=%d", num_cpus);
366                if(num_cpus >= 2)
367                {
368                        // Cleared to enable multi-threaded engine
369                        CGLError error_val = CGLEnable((CGLContextObj)[[self openGLContext] CGLContextObj], kCGLCEMPEngine);
370                        if(error_val != 0)
371                        {
372                                // The likelihood of failure seems quite high on older hardware, at least for now.
373                                // NSLog(@"Failed to enable Multithreaded OpenGL Engine: %s", CGLErrorString(error_val));
374                                isUsingMultithreadedOpenGLEngine = NO;
375                        }
376                        else
377                        {
378                                // NSLog(@"Success! Multithreaded OpenGL Engine activated!");
379                                isUsingMultithreadedOpenGLEngine = YES;
380                        }
381                }
382                else
383                {
384                        isUsingMultithreadedOpenGLEngine = NO;
385                }
386        }
387
388        // This is also might be a good place to setup OpenGL state that OSG doesn't control.
389        glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST);
390        glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
391        glHint(GL_POINT_SMOOTH_HINT, GL_NICEST);
392        glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
393
394/*
395        GLint maxbuffers[1];
396        glGetIntegerv(GL_MAX_COLOR_ATTACHMENTS_EXT, maxbuffers);
397        NSLog(@"GL_MAX_COLOR_ATTACHMENTS=%d", maxbuffers[0]);
398*/
399
400        // We need to tell the osgViewer what the viewport size is
401        [self resizeViewport];
402
403
404        // This is optional:
405        // This is to setup some default text in the OpenGL view so the
406        // user knows that they should drag and drop a model into the view.
407        osg::ref_ptr<osgText::Text> default_text = new osgText::Text;
408
409    default_text->setAlignment(osgText::Text::CENTER_CENTER);
410    default_text->setBackdropType(osgText::Text::OUTLINE);
411//      default_text->setBackdropImplementation(osgText::Text::POLYGON_OFFSET);
412        default_text->setColor(osg::Vec4(1.0, 1.0, 0.0, 1.0));
413        default_text->setBackdropColor(osg::Vec4(0.0, 0.0, 0.0, 1.0));
414    default_text->setAxisAlignment(osgText::Text::XZ_PLANE);
415       
416        // We should use a (Cocoa) localizable string instead of a hard coded string.
417//      default_text->setText("Drag-and-Drop\nyour .osg model here!");
418        // The first string is the key name (you need a Localizable.strings file for your Nib). The second string is just a comment.
419        NSString* localized_string = NSLocalizedString(@"DragAndDropHere", @"Drag-and-Drop\nyour .osg model here!");
420        default_text->setText([localized_string UTF8String]);
421       
422        osg::ref_ptr<osg::Geode> the_geode = new osg::Geode;
423        the_geode->addDrawable(default_text.get());
424
425        Viewer->setSceneData(the_geode.get());
426}
427
428/* disableScreenUpdatesUntilFlush was introduced in Tiger. It will prevent
429 * unnecessary screen flashing caused by NSSplitViews or NSScrollviews.
430 * From Apple's release notes:
431 
432NSWindow -disableScreenUpdatesUntilFlush API (Section added since WWDC)
433
434When 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.
435
436To 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.
437
438A 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.
439A -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.
440 */
441- (void) renewGState
442{
443    NSWindow* the_window = [self window];
444    if([the_window respondsToSelector:@selector(disableScreenUpdatesUntilFlush)])
445        {
446                [the_window disableScreenUpdatesUntilFlush];
447    }
448    [super renewGState];
449}
450
451
452/* When you minimize an app, you usually can see its shrunken contents
453 * in the dock. However, an OpenGL view by default only produces a blank
454 * white window. So we use this method to do an image capture of our view
455 * which will be used as its minimized picture.
456 * (A possible enhancement to consider is to update the picture over time.)
457 */
458- (void) prepareForMiniaturization:(NSNotification*)notification
459{
460        NSBitmapImageRep* ns_image_rep = [self renderOpenGLSceneToFramebuffer];
461        if([self lockFocusIfCanDraw])
462        {
463                [ns_image_rep draw];
464                [self unlockFocus];
465                [[self window] flushWindow];
466        }
467}
468
469/* Allow people to easily query if the multithreaded OpenGL engine is activated.
470 */
471- (BOOL) isUsingMultithreadedOpenGLEngine
472{
473        return isUsingMultithreadedOpenGLEngine;
474}
475
476
477////////////////////////////////////////////////////////////////////////
478/////////////////////////// End Init Stuff /////////////////////////////
479////////////////////////////////////////////////////////////////////////
480
481
482
483////////////////////////////////////////////////////////////////////////
484/////////////////////////// Mouse Stuff ////////////////////////////////
485////////////////////////////////////////////////////////////////////////
486
487- (void) mouseDown:(NSEvent*)the_event
488{
489        // Because many Mac users have only a 1-button mouse, we should provide ways
490        // to access the button 2 and 3 actions of osgViewer.
491        // I will use the Ctrl modifer to represent right-clicking
492        // and Option modifier to represent middle clicking.
493        if([the_event modifierFlags] & NSControlKeyMask)
494        {
495                [self setIsUsingCtrlClick:YES];
496                [self doRightMouseButtonDown:the_event];
497        }
498        else if([the_event modifierFlags] & NSAlternateKeyMask)
499        {
500                [self setIsUsingOptionClick:YES];
501                [self doMiddleMouseButtonDown:the_event];
502        }
503        else if([the_event modifierFlags] & NSCommandKeyMask)
504        {
505                [self startDragAndDropAsSource:the_event];
506        }
507        else
508        {
509                [self doLeftMouseButtonDown:the_event];
510        }
511}
512
513- (void) mouseDragged:(NSEvent*)the_event
514{
515        // We must convert the mouse event locations from the window coordinate system to the
516        // local view coordinate system.
517        NSPoint the_point = [the_event locationInWindow];
518    NSPoint converted_point = [self convertPoint:the_point fromView:nil];
519       
520        Viewer->getEventQueue()->mouseMotion(converted_point.x, converted_point.y);
521        [self setNeedsDisplay:YES];
522}
523
524- (void) mouseUp:(NSEvent*)the_event
525{
526        // Because many Mac users have only a 1-button mouse, we should provide ways
527        // to access the button 2 and 3 actions of osgViewer.
528        // I will use the Ctrl modifer to represent right-clicking
529        // and Option modifier to represent middle clicking.
530        if([self isUsingCtrlClick] == YES)
531        {
532                [self setIsUsingCtrlClick:NO];
533                [self doRightMouseButtonUp:the_event];
534        }
535        else if([self isUsingOptionClick] == YES)
536        {
537                [self setIsUsingOptionClick:NO];
538                [self doMiddleMouseButtonUp:the_event];
539        }
540        else
541        {
542                [self doLeftMouseButtonUp:the_event];
543        }
544}
545
546- (void) rightMouseDown:(NSEvent*)the_event
547{
548        [self doRightMouseButtonDown:the_event];
549}
550
551- (void) rightMouseDragged:(NSEvent*)the_event
552{
553        // We must convert the mouse event locations from the window coordinate system to the
554        // local view coordinate system.
555        NSPoint the_point = [the_event locationInWindow];
556    NSPoint converted_point = [self convertPoint:the_point fromView:nil];
557       
558        Viewer->getEventQueue()->mouseMotion(converted_point.x, converted_point.y);
559        [self setNeedsDisplay:YES];
560}
561
562- (void) rightMouseUp:(NSEvent*)the_event
563{
564        [self doRightMouseButtonUp:the_event];
565}
566
567// "otherMouse" seems to capture middle button and any other buttons beyond (4th, etc).
568- (void) otherMouseDown:(NSEvent*)the_event
569{
570        // Button 0 is left
571        // Button 1 is right
572        // Button 2 is middle
573        // Button 3 keeps going
574        // osgViewer expects 1 for left, 3 for right, 2 for middle
575        // osgViewer has a reversed number mapping for right and middle compared to Cocoa
576        if([the_event buttonNumber] == 2)
577        {
578                [self doMiddleMouseButtonDown:the_event];
579        }
580        else // buttonNumber should be 3,4,5,etc; must map to 4,5,6,etc in osgViewer
581        {
582                [self doExtraMouseButtonDown:the_event buttonNumber:[the_event buttonNumber]];
583        }
584}
585
586- (void) otherMouseDragged:(NSEvent*)the_event
587{
588        // We must convert the mouse event locations from the window coordinate system to the
589        // local view coordinate system.
590        NSPoint the_point = [the_event locationInWindow];
591    NSPoint converted_point = [self convertPoint:the_point fromView:nil];
592       
593        Viewer->getEventQueue()->mouseMotion(converted_point.x, converted_point.y);
594        [self setNeedsDisplay:YES];
595}
596
597// "otherMouse" seems to capture middle button and any other buttons beyond (4th, etc).
598- (void) otherMouseUp:(NSEvent*)the_event
599{
600        // Button 0 is left
601        // Button 1 is right
602        // Button 2 is middle
603        // Button 3 keeps going
604        // osgViewer expects 1 for left, 3 for right, 2 for middle
605        // osgViewer has a reversed number mapping for right and middle compared to Cocoa
606        if([the_event buttonNumber] == 2)
607        {
608                [self doMiddleMouseButtonUp:the_event];
609        }
610        else // buttonNumber should be 3,4,5,etc; must map to 4,5,6,etc in osgViewer
611        {
612                // I don't think osgViewer does anything for these additional buttons,
613                // but just in case, pass them along. But as a Cocoa programmer, you might
614                // think about things you can do natively here instead of passing the buck.
615        }       [self doExtraMouseButtonUp:the_event buttonNumber:[the_event buttonNumber]];
616}
617
618- (void) setIsUsingCtrlClick:(BOOL)is_using_ctrl_click
619{
620        isUsingCtrlClick = is_using_ctrl_click;
621}
622
623- (BOOL) isUsingCtrlClick
624{
625        return isUsingCtrlClick;
626}
627
628- (void) setIsUsingOptionClick:(BOOL)is_using_option_click
629{
630        isUsingOptionClick = is_using_option_click;
631}
632
633- (BOOL) isUsingOptionClick
634{
635        return isUsingOptionClick;
636}
637
638
639- (void) doLeftMouseButtonDown:(NSEvent*)the_event
640{
641        // We must convert the mouse event locations from the window coordinate system to the
642        // local view coordinate system.
643        NSPoint the_point = [the_event locationInWindow];
644    NSPoint converted_point = [self convertPoint:the_point fromView:nil];
645        if([the_event clickCount] == 1)
646        {
647                Viewer->getEventQueue()->mouseButtonPress(converted_point.x, converted_point.y, 1);
648        }
649        else
650        {
651                Viewer->getEventQueue()->mouseDoubleButtonPress(converted_point.x, converted_point.y, 1);
652        }
653        [self setNeedsDisplay:YES];
654}
655
656- (void) doLeftMouseButtonUp:(NSEvent*)the_event
657{
658        // We must convert the mouse event locations from the window coordinate system to the
659        // local view coordinate system.
660        NSPoint the_point = [the_event locationInWindow];
661    NSPoint converted_point = [self convertPoint:the_point fromView:nil];
662       
663        Viewer->getEventQueue()->mouseButtonRelease(converted_point.x, converted_point.y, 1);
664        [self setNeedsDisplay:YES];
665}
666
667- (void) doRightMouseButtonDown:(NSEvent*)the_event
668{
669        // We must convert the mouse event locations from the window coordinate system to the
670        // local view coordinate system.
671        NSPoint the_point = [the_event locationInWindow];
672    NSPoint converted_point = [self convertPoint:the_point fromView:nil];
673        if([the_event clickCount] == 1)
674        {
675                Viewer->getEventQueue()->mouseButtonPress(converted_point.x, converted_point.y, 3);
676        }
677        else
678        {
679                Viewer->getEventQueue()->mouseDoubleButtonPress(converted_point.x, converted_point.y, 3);
680        }
681        [self setNeedsDisplay:YES];
682}
683
684
685- (void) doRightMouseButtonUp:(NSEvent*)the_event
686{
687        // We must convert the mouse event locations from the window coordinate system to the
688        // local view coordinate system.
689        NSPoint the_point = [the_event locationInWindow];
690    NSPoint converted_point = [self convertPoint:the_point fromView:nil];
691       
692        Viewer->getEventQueue()->mouseButtonRelease(converted_point.x, converted_point.y, 3);
693        [self setNeedsDisplay:YES];
694}
695
696- (void) doMiddleMouseButtonDown:(NSEvent*)the_event
697{
698        // We must convert the mouse event locations from the window coordinate system to the
699        // local view coordinate system.
700        NSPoint the_point = [the_event locationInWindow];
701    NSPoint converted_point = [self convertPoint:the_point fromView:nil];
702       
703        if([the_event clickCount] == 1)
704        {
705                Viewer->getEventQueue()->mouseButtonPress(converted_point.x, converted_point.y, 2);
706        }
707        else
708        {
709                Viewer->getEventQueue()->mouseDoubleButtonPress(converted_point.x, converted_point.y, 2);
710        }
711        [self setNeedsDisplay:YES];
712}
713
714- (void) doExtraMouseButtonDown:(NSEvent*)the_event buttonNumber:(int)button_number
715{
716        // We must convert the mouse event locations from the window coordinate system to the
717        // local view coordinate system.
718        NSPoint the_point = [the_event locationInWindow];
719    NSPoint converted_point = [self convertPoint:the_point fromView:nil];
720
721        if([the_event clickCount] == 1)
722        {
723                Viewer->getEventQueue()->mouseButtonPress(converted_point.x, converted_point.y, button_number+1);
724        }
725        else
726        {
727                Viewer->getEventQueue()->mouseDoubleButtonPress(converted_point.x, converted_point.y, button_number+1);
728        }
729        [self setNeedsDisplay:YES];
730}
731
732
733- (void) doMiddleMouseButtonUp:(NSEvent*)the_event
734{
735        // We must convert the mouse event locations from the window coordinate system to the
736        // local view coordinate system.        NSPoint the_point = [the_event locationInWindow];
737        NSPoint the_point = [the_event locationInWindow];
738        NSPoint converted_point = [self convertPoint:the_point fromView:nil];
739        Viewer->getEventQueue()->mouseButtonRelease(converted_point.x, converted_point.y, 2);
740        [self setNeedsDisplay:YES];
741}
742
743- (void) doExtraMouseButtonUp:(NSEvent*)the_event buttonNumber:(int)button_number
744{
745        // We must convert the mouse event locations from the window coordinate system to the
746        // local view coordinate system.        NSPoint the_point = [the_event locationInWindow];
747        NSPoint the_point = [the_event locationInWindow];
748        NSPoint converted_point = [self convertPoint:the_point fromView:nil];
749        Viewer->getEventQueue()->mouseButtonRelease(converted_point.x, converted_point.y, button_number+1);
750       
751        [self setNeedsDisplay:YES];
752}
753
754// This is a job for Mighty Mouse!
755// For the most fluid experience turn on 360 degree mode availble in 10.4.8+.
756// With your Mighty Mouse plugged in,
757// open 'Keyboard & Mouse' in 'System Preferences'.
758// Select the 'Mouse' tab.
759// Under 'Scrolling Options' select '360 degree'.
760// That should improve diagonal scrolling.
761// You should also be able to use 'two-finger scrolling' on newer laptops.
762- (void) scrollWheel:(NSEvent*)the_event
763{
764        // Unfortunately, it turns out mouseScroll2D doesn't actually do anything.
765        // The camera manipulators don't seem to implement any code that utilize the scroll values.
766        // This this call does nothing.
767//      Viewer->getEventQueue()->mouseScroll2D([the_event deltaX], [the_event deltaY]);
768
769        // With the absense of a useful mouseScroll2D API, we can manually simulate the desired effect.
770        NSPoint the_point = [the_event locationInWindow];
771        NSPoint converted_point = [self convertPoint:the_point fromView:nil];
772        Viewer->getEventQueue()->mouseButtonPress(converted_point.x, converted_point.y, 1);
773        Viewer->getEventQueue()->mouseMotion(converted_point.x + -[the_event deltaX], converted_point.y + [the_event deltaY]);
774        Viewer->getEventQueue()->mouseButtonRelease(converted_point.x + -[the_event deltaX], converted_point.y + [the_event deltaY], 1);
775
776        [self setNeedsDisplay:YES];
777}
778
779////////////////////////////////////////////////////////////////////////
780/////////////////////////// End Mouse Stuff ////////////////////////////
781////////////////////////////////////////////////////////////////////////
782
783////////////////////////////////////////////////////////////////////////
784/////////////////////////// Keyboard Stuff /////////////////////////////
785////////////////////////////////////////////////////////////////////////
786// Needed to accept keyboard events
787- (BOOL) acceptsFirstResponder
788{
789        return YES;
790}
791
792- (void) keyDown:(NSEvent*)the_event
793{
794        // Do you want characters or charactersIgnoringModifiers?
795        NSString* event_characters = [the_event characters];
796//      NSString* event_characters = [the_event charactersIgnoringModifiers];
797
798        unichar unicode_character = [event_characters characterAtIndex:0];
799//      NSLog(@"unicode_character: %d", unicode_character);
800        Viewer->getEventQueue()->keyPress(static_cast<osgGA::GUIEventAdapter::KeySymbol>(unicode_character));
801
802        [self setNeedsDisplay:YES];
803}
804
805- (void) keyUp:(NSEvent*)the_event
806{
807        // Do you want characters or charactersIgnoringModifiers?
808        NSString* event_characters = [the_event characters];
809//      NSString* event_characters = [the_event charactersIgnoringModifiers];
810        unichar unicode_character = [event_characters characterAtIndex:0];
811        Viewer->getEventQueue()->keyRelease(static_cast<osgGA::GUIEventAdapter::KeySymbol>(unicode_character));
812        [self setNeedsDisplay:YES];
813}
814
815////////////////////////////////////////////////////////////////////////
816/////////////////////////// End Keyboard Stuff /////////////////////////
817////////////////////////////////////////////////////////////////////////
818
819////////////////////////////////////////////////////////////////////////
820/////////////////////////// View and Draw Stuff ////////////////////////
821////////////////////////////////////////////////////////////////////////
822
823// This method is periodically called by my timer.
824- (void) animationCallback
825{
826        // Simply notify Cocoa that a drawRect needs to take place.
827        // Potential optimization is to query the OSG stuff to find out if a redraw is actually necessary.
828        [self setNeedsDisplay:YES];
829}
830
831// This is an optional optimization. This states you don't have a transparent view/window.
832// Obviously don't use this or set it to NO if you intend for your view to be see-through.
833- (BOOL) isOpaque
834{
835        return YES;
836}
837
838// Resolution Independent UI is coming... (Tiger's Quartz Debug already has the tool.)
839// We must think in 'point sizes', not pixel sizes, so a conversion is needed for OpenGL.
840- (void) resizeViewport
841{
842        NSSize size_in_points = [self bounds].size;
843        // This coordinate system conversion seems to make things work with Quartz Debug.
844        NSSize size_in_window_coordinates = [self convertSize:size_in_points toView:nil];
845        Viewer->getEventQueue()->windowResize(0, 0, size_in_window_coordinates.width, size_in_window_coordinates.height);
846        graphicsWindow->resized(0, 0, size_in_window_coordinates.width, size_in_window_coordinates.height);
847}
848
849// For window resize
850- (void) reshape
851{
852        [super reshape];
853        [self resizeViewport];
854}
855
856// This is the code that actually draws.
857// Remember you shouldn't call drawRect: directly and should use setNeedsDisplay:YES
858// This is so the operating system can optimize when a draw is actually needed.
859// (e.g. No sense drawing when the application is hidden.)
860- (void) drawRect:(NSRect)the_rect
861{
862        if([[NSGraphicsContext currentContext] isDrawingToScreen])
863        {
864//              [[self openGLContext] makeCurrentContext];
865                Viewer->frame();
866                [[self openGLContext] flushBuffer];
867        }
868        else // This is usually the print case
869        {
870//              [[self openGLContext] makeCurrentContext];
871
872                // FIXME: We should be computing a size that fits best to the paper target
873                NSSize size_in_points = [self bounds].size;
874                NSSize size_in_window_coordinates = [self convertSize:size_in_points toView:nil];
875                NSBitmapImageRep * bitmap_image_rep = [self renderOpenGLSceneToFramebufferAsFormat:GL_RGB viewWidth:size_in_window_coordinates.width viewHeight:size_in_window_coordinates.height];
876               
877                NSImage* ns_image = [self imageFromBitmapImageRep:bitmap_image_rep];
878
879                if(ns_image)
880                {
881                        NSSize image_size = [ns_image size];
882                        [ns_image drawAtPoint:NSMakePoint(0.0, 0.0)
883                                        fromRect: NSMakeRect(0.0, 0.0, image_size.width, image_size.height)
884//                                 operation: NSCompositeSourceOver
885                                   operation: NSCompositeCopy
886                                        fraction: 1.0];         
887                }
888                else
889                {
890                        NSLog(@"Image not valid");
891                }
892        }
893}
894
895
896/* Optional render to framebuffer stuff below. The code renders offscreen to assist in screen capture stuff.
897 * This can be useful for things like the Dock minimization picture, drag-and-drop dropImage, copy and paste,
898 * and printing.
899 */
900
901// Convenience version. Will use the current view's bounds and produce an RGB image with the current clear color.
902- (NSBitmapImageRep*) renderOpenGLSceneToFramebuffer
903{
904        NSSize size_in_points = [self bounds].size;
905        NSSize size_in_window_coordinates = [self convertSize:size_in_points toView:nil];
906        const osg::Vec4& clear_color = Viewer->getCamera()->getClearColor();
907
908        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]];
909}
910
911// Convenience version. Allows you to specify the view and height and format, but uses the current the current clear color.
912- (NSBitmapImageRep*) renderOpenGLSceneToFramebufferAsFormat:(int)gl_format viewWidth:(float)view_width viewHeight:(float)view_height
913{
914        const osg::Vec4& clear_color = Viewer->getCamera()->getClearColor();
915
916        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]];
917}
918
919// Renders to an offscreen buffer and returns a copy of the data to an NSBitmapImageRep.
920// Allows you to specify the gl_format, width and height, and the glClearColor
921// gl_format is only GL_RGB or GLRGBA.
922- (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
923{
924        // Round values and bring to closest integer.
925        int viewport_width = (int)(view_width + 0.5f);
926        int viewport_height = (int)(view_height + 0.5f);
927       
928        NSBitmapImageRep* ns_image_rep;
929        osg::ref_ptr<osg::Image> osg_image = new osg::Image;
930       
931        if(GL_RGBA == gl_format)
932        {
933                // Introduced in 10.4, but gives much better looking results if you utilize transparency
934                if([NSBitmapImageRep instancesRespondToSelector:@selector(initWithBitmapDataPlanes:pixelsWide:pixelsHigh:bitsPerSample:samplesPerPixel:hasAlpha:isPlanar:colorSpaceName:bitmapFormat:bytesPerRow:bitsPerPixel:)])
935                {
936                        ns_image_rep = [[[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
937                                                                                                  pixelsWide:viewport_width
938                                                                                                  pixelsHigh:viewport_height
939                                                                                           bitsPerSample:8
940                                                                                         samplesPerPixel:4
941                                                                                                        hasAlpha:YES
942                                                                                                        isPlanar:NO
943                                                                                          colorSpaceName:NSCalibratedRGBColorSpace
944                                                                                                bitmapFormat:NSAlphaNonpremultipliedBitmapFormat // 10.4+, gives much better looking results if you utilize transparency
945                                                                                                 bytesPerRow:osg::Image::computeRowWidthInBytes(viewport_width, GL_RGBA, GL_UNSIGNED_BYTE, 1)
946                                                                                                bitsPerPixel:32]
947                                        autorelease];
948                }
949                else // fallback for 10.0 to 10.3
950                {
951                        ns_image_rep = [[[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
952                                                                                                  pixelsWide:viewport_width
953                                                                                                  pixelsHigh:viewport_height
954                                                                                           bitsPerSample:8
955                                                                                         samplesPerPixel:4
956                                                                                                        hasAlpha:YES
957                                                                                                        isPlanar:NO
958                                                                                          colorSpaceName:NSCalibratedRGBColorSpace
959                                                                                                // bitmapFormat:NSAlphaNonpremultipliedBitmapFormat // 10.4+, gives much better looking results if you utilize transparency
960                                                                                                 bytesPerRow:osg::Image::computeRowWidthInBytes(viewport_width, GL_RGBA, GL_UNSIGNED_BYTE, 1)
961                                                                                                bitsPerPixel:32]
962                                        autorelease];
963                }
964                // This is an optimization. Instead of creating data in both an osg::Image and NSBitmapImageRep,
965                // Allocate just the memory in the NSBitmapImageRep and give the osg::Image a reference to the data.
966                // I let NSBitmapImageRep control the memory because I think it will be easier to deal with
967                // memory management in the cases where it must interact with other Cocoa mechanisms like Drag-and-drop
968                // where the memory persistence is less obvious. Make sure that you don't expect to use the osg::Image
969                // outside the scope of this function because there is no telling when the data will be removed out
970                // from under it by Cocoa since osg::Image will not retain.
971                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);
972        }
973        else if(GL_RGB == gl_format)
974        {
975                ns_image_rep = [[[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
976                                                                                                  pixelsWide:viewport_width
977                                                                                                  pixelsHigh:viewport_height
978                                                                                           bitsPerSample:8
979                                                                                         samplesPerPixel:3
980                                                                                                        hasAlpha:NO
981                                                                                                        isPlanar:NO
982                                                                                          colorSpaceName:NSCalibratedRGBColorSpace
983                                                                                                // bitmapFormat:(NSBitmapFormat)0 // 10.4+
984                                                                                                 bytesPerRow:osg::Image::computeRowWidthInBytes(viewport_width, GL_RGB, GL_UNSIGNED_BYTE, 1)
985                                                                                                bitsPerPixel:24]
986                                        autorelease];
987
988                // This is an optimization. Instead of creating data in both an osg::Image and NSBitmapImageRep,
989                // Allocate just the memory in the NSBitmapImageRep and give the osg::Image a reference to the data.
990                // I let NSBitmapImageRep control the memory because I think it will be easier to deal with
991                // memory management in the cases where it must interact with other Cocoa mechanisms like Drag-and-drop
992                // where the memory persistence is less obvious. Make sure that you don't expect to use the osg::Image
993                // outside the scope of this function because there is no telling when the data will be removed out
994                // from under it by Cocoa since osg::Image will not retain.
995                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);
996        }
997        else
998        {
999                NSLog(@"Sorry, unsupported format in renderOpenGLSceneToFramebufferAsFormat");
1000                return nil;
1001        }
1002
1003        // Can't find a way to query Viewer for the current values, so recompute current view size.
1004        NSSize original_size_in_points = [self bounds].size;
1005        NSSize original_size_in_window_coordinates = [self convertSize:original_size_in_points toView:nil];
1006//      Viewer->getEventQueue()->windowResize(0, 0, original_size_in_window_coordinates.width, original_size_in_window_coordinates.height);
1007
1008        Viewer->getEventQueue()->windowResize(0, 0, viewport_width, viewport_height);
1009        graphicsWindow->resized(0, 0, viewport_width, viewport_height);
1010
1011        /*
1012         * I want to use a Framebuffer Object because it seems to be the OpenGL sanctioned way of rendering offscreen.
1013         * Also, I want to try to decouple the image capture from the onscreen rendering. This is potentially useful
1014         * for two reasons:
1015         * 1) You may want to customize the image dimensions to best fit the situation (consider printing to a page to fit)
1016         * 2) You may want to customize the scene for the target (consider special output for a printer, or removed data for a thumbnail)
1017         * Unfortunately, I have hit two problems.
1018         * 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.
1019         * The workaround is to copy the camera attributes into another camera, and then add a second camera node into the scene.
1020         * I'm hoping OSG will simplify this in the future.
1021         * 2) I may have encountered a bug. Under some circumstances, the offscreen renderbuffer seems to get drawn into the onscreen view
1022         * 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.
1023         * So I'm wondering if OSG made the same mistake.
1024         * But the problem doesn't seem critical. It just looks bad.
1025         */
1026        //NSLog(@"Before camera glGetError: %s", gluErrorString(glGetError()));
1027        osg::Camera* root_camera = Viewer->getCamera();
1028
1029        // I originally tried the clone() method and the copy construction, but it didn't work right,
1030        // so I manually copy the attributes.
1031        osg::Camera* the_camera = new osg::Camera;
1032
1033        the_camera->setClearMask(root_camera->getClearMask());
1034        the_camera->setProjectionMatrix(root_camera->getProjectionMatrix());
1035        the_camera->setViewMatrix(root_camera->getViewMatrix());
1036        the_camera->setViewport(root_camera->getViewport());
1037        the_camera->setClearColor(
1038                osg::Vec4(
1039                        clear_red,
1040                        clear_green,
1041                        clear_blue,
1042                        clear_alpha
1043                )
1044        );
1045
1046        // This must be ABSOLUTE_RF, and not a copy of the root camera because the transforms are additive.
1047        the_camera->setReferenceFrame(osg::Transform::ABSOLUTE_RF);
1048
1049        // We need to insert the new (second) camera into the scene (below the root camera) and attach
1050        // the scene data to the new camera.
1051        osg::ref_ptr<osg::Node> root_node = Viewer->getSceneData();
1052
1053        the_camera->addChild(root_node.get());
1054        // Don't call (bypass) Viewer's setSceneData, but the underlying SceneView's setSceneData.
1055        // Otherwise, the camera position gets reset to the home position.
1056        Viewer->setSceneData(the_camera);
1057
1058        // set the camera to render before the main camera.
1059        the_camera->setRenderOrder(osg::Camera::PRE_RENDER);
1060
1061        // tell the camera to use OpenGL frame buffer object where supported.
1062        the_camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT);
1063
1064
1065        // attach the image so its copied on each frame.
1066        the_camera->attach(osg::Camera::COLOR_BUFFER, osg_image.get());
1067
1068
1069        //NSLog(@"Before frame(), glGetError: %s", gluErrorString(glGetError()));
1070
1071
1072        // Render the scene
1073        Viewer->frame();
1074
1075        // Not sure if I really need this (seems to work without it), and if so, not sure if I need flush or finish
1076        glFlush();
1077//      glFinish();
1078
1079        //NSLog(@"After flush(), glGetError: %s", gluErrorString(glGetError()));
1080
1081
1082
1083        // The image is upside-down to Cocoa, so invert it.
1084        osg_image.get()->flipVertical();
1085
1086        // Clean up everything I changed
1087//      the_camera->detach(osg::Camera::COLOR_BUFFER);
1088//      the_camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER);
1089        // Don't call (bypass) Viewer's setSceneData, but the underlying SceneView's setSceneData.
1090        // Otherwise, the camera position gets reset to the home position.
1091        Viewer->setSceneData(root_node.get());
1092        Viewer->getEventQueue()->windowResize(0, 0, original_size_in_window_coordinates.width, original_size_in_window_coordinates.height);
1093        graphicsWindow->resized(0, 0, original_size_in_window_coordinates.width, original_size_in_window_coordinates.height);
1094
1095
1096        // Ugh. Because of the bug I mentioned, I'm losing the picture in the display when I print.
1097        [self setNeedsDisplay:YES];
1098        //NSLog(@"at return, glGetError: %s", gluErrorString(glGetError()));
1099
1100        return ns_image_rep;
1101}
1102
1103// Convenience method
1104- (NSImage*)imageFromBitmapImageRep:(NSBitmapImageRep*)bitmap_image_rep
1105{
1106        if(nil == bitmap_image_rep)
1107        {
1108                return nil;
1109        }
1110        NSImage* image = [[[NSImage alloc] initWithSize:[bitmap_image_rep size]] autorelease];
1111        [image addRepresentation:bitmap_image_rep];
1112        // This doesn't seem to work as I want it to. The image only gets flipped when rendered in a regular view.
1113        // It doesn't flip for the printer view. I must actually invert the pixels.
1114//      [image setFlipped:YES];
1115        return image;
1116}
1117
1118
1119
1120
1121////////////////////////////////////////////////////////////////////////
1122/////////////////////////// End View and Draw Stuff ////////////////////
1123////////////////////////////////////////////////////////////////////////
1124
1125////////////////////////////////////////////////////////////////////////
1126/////////////////////////// For drag and drop //////////////////////////
1127////////////////////////////////////////////////////////////////////////
1128- (unsigned int) draggingEntered:(id <NSDraggingInfo>)the_sender
1129{
1130        if([the_sender draggingSource] != self)
1131        {
1132                NSPasteboard* paste_board = [the_sender draggingPasteboard];
1133                // I respond to filename types or URL types
1134                NSArray* supported_types = [NSArray arrayWithObjects:NSFilenamesPboardType, NSURLPboardType, nil];
1135                // If any of the supported types are being dragged in, activate the copy operation
1136                NSString* first_type = [paste_board availableTypeFromArray:supported_types];
1137                if(first_type != nil)
1138                {
1139                        return NSDragOperationCopy;
1140                }
1141        }
1142        // Means we don't support this type
1143        return NSDragOperationNone;
1144}
1145
1146// We're not using this method, but here it is as an example.
1147- (void) draggingExited:(id <NSDraggingInfo>)the_sender
1148{
1149}
1150
1151- (BOOL) prepareForDragOperation:(id <NSDraggingInfo>)the_sender
1152{
1153        return YES;
1154}
1155
1156- (BOOL) performDragOperation:(id <NSDraggingInfo>)the_sender
1157{
1158        NSPasteboard* paste_board = [the_sender draggingPasteboard];
1159
1160 
1161    if([[paste_board types] containsObject:NSFilenamesPboardType])
1162        {
1163        NSArray* file_names = [paste_board propertyListForType:NSFilenamesPboardType];
1164//        int number_of_files = [file_names count];
1165                // Exercise for the reader: Try loading all files in the array
1166                NSString* single_file = [file_names objectAtIndex:0];
1167            osg::ref_ptr<osg::Node> loaded_model = osgDB::readNodeFile([single_file fileSystemRepresentation]);
1168                if(!loaded_model)
1169                {
1170                        NSLog(@"File: %@ failed to load", single_file);
1171                        return NO;
1172                }
1173                Viewer->setSceneData(loaded_model.get());
1174                return YES;
1175    }
1176        else if([[paste_board types] containsObject:NSURLPboardType])
1177        {
1178                NSURL* file_url = [NSURL URLFromPasteboard:paste_board];
1179                // See if the URL is valid file path
1180                if(![file_url isFileURL])
1181                {
1182                        NSLog(@"URL: %@ needs to be a file for readNodeFile()", file_url);
1183                        return NO;
1184                }
1185                NSString* file_path = [file_url path];
1186            osg::ref_ptr<osg::Node> loaded_model = osgDB::readNodeFile([file_path fileSystemRepresentation]);
1187                if(!loaded_model)
1188                {
1189                        NSLog(@"URL: %@ failed to load, %@", file_url, file_path);
1190                        return NO;
1191                }
1192                Viewer->setSceneData(loaded_model.get());
1193                return YES;
1194        }
1195    return NO;
1196}
1197
1198// This method isn't really needed (I could move setNeedsDisplay up), but is here as an example
1199- (void) concludeDragOperation:(id <NSDraggingInfo>)the_sender
1200{
1201        [self setNeedsDisplay:YES];
1202}
1203
1204////////////////////////////////////////////////////////////////////////
1205/////////////////////////// End of drag and drop (receiver) ////////////
1206////////////////////////////////////////////////////////////////////////
1207
1208
1209//////////////////////////////////////////////////////////////////////////////////////
1210/////////////////////////// For drag and drop and copy/paste (source) ////////////////
1211//////////////////////////////////////////////////////////////////////////////////////
1212- (IBAction) copy:(id)sender
1213{
1214    NSString* type = NSTIFFPboardType;
1215    NSData* image_data = [self contentsAsDataOfType:type];
1216       
1217    if(image_data)
1218        {
1219        NSPasteboard* general_pboard = [NSPasteboard generalPasteboard];
1220        [general_pboard declareTypes:[NSArray arrayWithObjects:type, nil] owner: nil];
1221        [general_pboard setData:image_data forType:type];
1222    }
1223}
1224
1225- (NSData*) dataWithTIFFOfContentView
1226{
1227        NSBitmapImageRep * image = [self renderOpenGLSceneToFramebuffer];
1228        NSData* data = nil;
1229
1230        if(image != nil)
1231        {
1232                data = [image TIFFRepresentation];
1233        }
1234        return data;
1235}
1236
1237/* Returns a data object containing the current contents of the receiving window */
1238- (NSData*) contentsAsDataOfType:(NSString *)pboardType
1239{
1240        NSData * data = nil;
1241        if ([pboardType isEqualToString: NSTIFFPboardType] == YES)
1242        {
1243                data = [self dataWithTIFFOfContentView];
1244        }
1245    return data;
1246}
1247
1248
1249- (void) startDragAndDropAsSource:(NSEvent*)the_event
1250{
1251        NSPasteboard* drag_paste_board;
1252        NSImage* the_image;
1253        NSSize the_size;
1254        NSPoint the_point;
1255
1256        NSSize size_in_points = [self bounds].size;
1257        NSSize size_in_window_coordinates = [self convertSize:size_in_points toView:nil];
1258
1259        // Create the image that will be dragged
1260        NSString * type = NSTIFFPboardType;
1261
1262        // I want two images. One to be rendered for the target, and one as the drag-image.
1263        // I want the drag-image to be translucent.
1264        // I think this is where render GL_COLOR_ATTACHMENTn (COLOR_BUFFERn?) would be handy.
1265        // But my hardware only returns 1 for glGetIntegerv(GL_MAX_COLOR_ATTACHMENTS_EXT, maxbuffers);
1266        // So I won't bother and will just render twice.
1267        NSBitmapImageRep* bitmap_image_rep = [self renderOpenGLSceneToFramebufferAsFormat:GL_RGB viewWidth:size_in_window_coordinates.width viewHeight:size_in_window_coordinates.height];
1268        NSBitmapImageRep* bitmap_image_rep_transparent_copy = [self renderOpenGLSceneToFramebufferAsFormat:GL_RGBA viewWidth:size_in_window_coordinates.width viewHeight:size_in_window_coordinates.height];
1269//      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];
1270
1271//NSBitmapImageRep* bitmap_image_rep_transparent_copy = bitmap_image_rep;
1272
1273        // 0x32 is an arbitrary number. Basically, I want something between 0 and 0xFF.
1274        Internal_SetAlpha(bitmap_image_rep_transparent_copy, 0x32);
1275
1276        NSData* image_data = [bitmap_image_rep TIFFRepresentation];
1277
1278    if(image_data)
1279        {
1280       
1281                drag_paste_board = [NSPasteboard pasteboardWithName:NSDragPboard];
1282                // is owner:self or nil? (Hillegass says self)
1283        [drag_paste_board declareTypes: [NSArray arrayWithObjects: type, nil] owner: self];
1284        [drag_paste_board setData:image_data forType: type];
1285               
1286                // create an image from the data
1287                the_image = [[NSImage alloc] initWithData:[bitmap_image_rep_transparent_copy TIFFRepresentation]];
1288               
1289                the_point = [self convertPoint:[the_event locationInWindow] fromView:nil];
1290                the_size = [the_image size];
1291               
1292                // shift the point to the center of the image
1293                the_point.x = the_point.x - the_size.width/2.0;
1294                the_point.y = the_point.y - the_size.height/2.0;
1295
1296                // start drag
1297                [self dragImage:the_image
1298                                         at:the_point
1299                                 offset:NSMakeSize(0,0)
1300                                  event:the_event
1301                         pasteboard:drag_paste_board
1302                                 source:self
1303                          slideBack:YES];
1304                         
1305                [the_image release];
1306        }
1307        else
1308        {
1309                NSLog(@"Error, failed to create image data");
1310        }
1311       
1312}
1313
1314//////////////////////////////////////////////////////////////////////////////////////
1315/////////////////////////// For drag and drop and copy/paste (source) ////////////////
1316//////////////////////////////////////////////////////////////////////////////////////
1317
1318
1319
1320
1321
1322
1323////////////////////////////////////////////////////////////////////////
1324/////////////////////////// IBAction examples  /////////////////////////
1325////////////////////////////////////////////////////////////////////////
1326
1327// Connect a button to this to stop and reset the position.
1328- (IBAction) resetPosition:(id)the_sender
1329{
1330        //      osgGA::MatrixManipulator* camera_manipulator = Viewer->getCameraManipulator();
1331        // This only resets the position
1332        //      camera_manipulator->home(0.0);
1333       
1334        // There is no external API from Viewer that I can see that will stop movement.
1335        // So fake the 'spacebar' to stop things and reset.
1336        printf("I'am here");
1337       
1338        Viewer->home();
1339        [self setNeedsDisplay:YES];
1340}
1341
1342// Connect a NSColorWell to this to change color.
1343// A better way to do this is use Cocoa Bindings because it will automatically
1344// synchronize between your model and view, but since this demo lacks a model and controller
1345// aspect it wouldn't do much good.
1346- (IBAction) takeBackgroundColorFrom:(id)the_sender
1347{
1348        NSColor* the_color = [the_sender color];
1349
1350        Viewer->getCamera()->setClearColor(
1351                osg::Vec4(
1352                        [the_color redComponent],
1353                        [the_color greenComponent],
1354                        [the_color blueComponent],
1355                        [the_color alphaComponent]
1356                )
1357        );
1358        [self setNeedsDisplay:YES];
1359}
1360
1361////////////////////////////////////////////////////////////////////////
1362/////////////////////////// End IBAction examples  /////////////////////
1363////////////////////////////////////////////////////////////////////////
1364
1365@end
Note: See TracBrowser for help on using the browser.