GLUTWindow.m

 
/* Copyright (c) Dietmar Planitzer, 1998, 2002 - 2003 */
 
/* This program is freely distributable without licensing fees 
   and is provided without guarantee or warrantee expressed or 
   implied. This program is -not- in the public domain. */
 
#import "macx_glut.h"
#import "GLUTWindow.h"
#import "GLUTView.h"
#import "GLUTApplication.h"
 
 
NSString *GLUTWindowFrame = @"GLUTWindowFrame";
 
 
@interface GLUTView(GLUTPrivate)
- (void)_commonReshape;
@end
 
 
@interface GLUTWindow(GLUTPrivate)
- (id)_initWithContentRect: (NSRect)rect styleMask: (unsigned int)mask contentView: (GLUTView *)aView;
- (id)_initWithWindow: (GLUTWindow *)aWindow operation: (int)op arguments: (NSDictionary *)operands;
- (NSWindow *)_windowWithTIFFInsideRect: (NSRect)rect;
- (NSData *)_dataWithTIFFOfContentView;
- (NSData *)_dataWithRTFDOfContentView;
@end
 
 
/////////////////////////////////////////////
#pragma mark -
 
 
@implementation GLUTWindow
 
static BOOL         gInitialized = NO;
static NSArray *    gServicesTypes = nil;
 
 
+ (void)initialize
{   
    if(!gInitialized) {
        gInitialized = YES;
        
        gServicesTypes = [[NSArray arrayWithObjects: NSTIFFPboardType, NSRTFDPboardType, nil] retain];
        [NSApp registerServicesMenuSendTypes: gServicesTypes returnTypes: nil];
    }
}
 
+ (id)windowByMorphingWindow: (GLUTWindow *)aWindow operation: (int)op arguments: (NSDictionary *)dict
{
   return [[[self alloc] _initWithWindow: aWindow operation: op arguments: dict] autorelease];
}
 
 
/* Designated initializer */
- (id)_initWithContentRect: (NSRect)rect styleMask: (unsigned int)mask contentView: (GLUTView *)aView
{
   if((self = [super initWithContentRect: rect styleMask: mask backing: NSBackingStoreBuffered defer: NO]) != nil) {
      [self setReleasedWhenClosed: NO];
      [self setMinSize: NSMakeSize(80.0, 80.0)];
      [self setShowsResizeIndicator:NO]; // turn off the grow box.
      [self setContentView: aView];
      [self makeFirstResponder: aView];
      [self setDelegate: self];
      return self;
   }
   return nil;
}
 
- (id)initWithContentRect: (NSRect)rect pixelFormat: (NSOpenGLPixelFormat *)pixelFormat
            windowID: (int)winid gameMode: (BOOL)gameMode fullscreenStereo: (BOOL)pfStereo treatAsSingle: (BOOL)treatAsSingle
{
   unsigned int mask;
   GLUTView *       view = nil;
   
   if(gameMode) { // set to fill screen
      float             offsetY = 0.0f;
      /* XXX NSScreen BUG WORKAROUND
         The purpose of the following code is to workaround a bug in the NSScreen class.
         The problem is that this class doesn't realize that we switched from the original
         screen mode to another mode via the CGDisplaySwitchToMode() function and thus
         keeps on reporting now out-dated screen attributes like screen width & height.
         This however has the unfortunate consequence that our NSWindow would be positioned
         incorrectly if the new mode has a smaller Y resolution than the old one. I.e.
         if the old Y res was 870 and the new is 480 the window would be placed by 390 pixels
         too far below.
      */
      NSScreen *    screen = [[NSScreen screens] objectAtIndex: 0];
      NSSize        screenSize = [screen frame].size;
      
      if(screenSize.height - rect.size.height > 0.0f)
         offsetY = screenSize.height - rect.size.height;
      rect.origin.y = offsetY;
      mask = NSBorderlessWindowMask;
   } else {
      mask = (NSTitledWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask);
   }
   
   /* create and configure content view */
   view = [[[GLUTView alloc]    initWithFrame: rect
                              pixelFormat: pixelFormat
                              windowID: winid
                              treatAsSingle: treatAsSingle
                              isSubwindow: NO
                              fullscreenStereo:pfStereo 
                              isVBLSynced: __glutSyncToVBL] autorelease];
   if(view)
      return [self _initWithContentRect: rect styleMask: mask contentView: view];
   else
      return nil;
}
 
- (id)_initWithWindow: (GLUTWindow *)aWindow operation: (int)op arguments: (NSDictionary *)dict
{
   GLUTView *       contentView = nil;
   NSResponder *    savedFirstResponder;
   NSRect           rect = {{0.0f, 0.0f}, {0.0f, 0.0f}};
   unsigned int     mask = 0;
   int              level = 0;
   
   switch(op) {
      case kGLUTMorphOperationFullscreen:
            /* Make a fullscreen window */
            if (NO == __glutUseExtendedDesktop) {
                rect = [[aWindow screen] frame];
            } else { // look at all screens
                NSEnumerator *enumerator = [[NSScreen screens] objectEnumerator];
                NSScreen *  screen = nil;
                while (nil != (screen = (NSScreen *)[enumerator nextObject])) {
                    if([screen frame].origin.x < rect.origin.x)
                        rect.origin.x = [screen frame].origin.x;
                    if([screen frame].origin.y < rect.origin.y)
                        rect.origin.y = [screen frame].origin.y;
                    if(([screen frame].origin.x + [screen frame].size.width - rect.origin.x) > rect.size.width)
                        rect.size.width = [screen frame].origin.x + [screen frame].size.width - rect.origin.x;
                    if(([screen frame].origin.y + [screen frame].size.height -  rect.origin.y) > rect.size.height)
                        rect.size.height = [screen frame].origin.y + [screen frame].size.height -  rect.origin.y;
                }
            }
            mask = NSBorderlessWindowMask;
            level = GLUT_FULLSCREEN_LEVEL;
 
            break;
            
      case kGLUTMorphOperationRegular:
            /* Make a standard window */
            rect = [[dict objectForKey: GLUTWindowFrame] rectValue];
            mask = (NSTitledWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask);
            level = GLUT_NORMAL_LEVEL;
            break;
   }
   
   savedFirstResponder = [aWindow firstResponder];
   contentView = (GLUTView *) [[aWindow contentView] retain];
   [contentView recursiveWillBeginMorph: op];
   [aWindow setContentView: nil];
   
   if((self = [self _initWithContentRect: rect styleMask: mask contentView: contentView]) != nil) {
      [self setLevel: level];
 
      switch(op) {
         case kGLUTMorphOperationFullscreen: // new window is full screen
            _isFullscreen = YES;
            /* put window on fullscreen window list */
            _nextFullscreenWindow = __glutFullscreenWindows;
            __glutFullscreenWindows = self;
               break;
         case kGLUTMorphOperationRegular: // new window is not full screen
            _isFullscreen = NO;
               break;
      }
      
 
      _imagePath = [aWindow->_imagePath copy];
      _enabledMouseMovedEvents = aWindow->_enabledMouseMovedEvents;
      if(_enabledMouseMovedEvents > 0)
         [self setAcceptsMouseMovedEvents: YES];
      [contentView recursiveDidEndMorph: op];
      [self makeFirstResponder: savedFirstResponder];
      // Call _commonReshape on the window's content view in order
      // to simulate a resize event (we don't automatically get one
      // because we just moved the content view from one window to
      // to another one...)
      [contentView _commonReshape];
      [contentView release];
      
      return self;
   }
   return nil;
}
 
- (void)dealloc
{
    // remove from full screen window list
   if(_isFullscreen) {
      GLUTWindow *  prev = nil;
      GLUTWindow *  cur = __glutFullscreenWindows;
      
      while(cur != nil && cur != self) {
         prev = cur;
         cur = cur->_nextFullscreenWindow;
      }
      
      if(prev)
         prev->_nextFullscreenWindow = _nextFullscreenWindow;
      else
         __glutFullscreenWindows = _nextFullscreenWindow;
   }
   
   [super dealloc];
}
 
- (void)finalize
{
   if(_isFullscreen) {
      GLUTWindow *  prev = nil;
      GLUTWindow *  cur = __glutFullscreenWindows;
      while(cur != nil && cur != self) {
         prev = cur;
         cur = cur->_nextFullscreenWindow;
      }
      if(prev)
         prev->_nextFullscreenWindow = _nextFullscreenWindow;
      else
         __glutFullscreenWindows = _nextFullscreenWindow;
   }
   [super finalize];
}
 
- (BOOL)isFullscreen
{
   return _isFullscreen;
}
 
/* Returns YES if the window 'me' is either above or below the frame rectangle
   of any fullscreen window and NO otherwise */
- (BOOL)isAffectedByFullscreenWindow
{
   NSRect           frame = [self frame];
   GLUTWindow * cur = __glutFullscreenWindows;
   
   while(cur != nil) {
      NSRect    othFrame = [cur frame];
      
      if(NSContainsRect(othFrame, frame) ||
         NSEqualRects(othFrame, frame) ||
         NSIntersectsRect(othFrame, frame))
         return YES;
         
      cur = cur->_nextFullscreenWindow;
   }
   return NO;
}
 
 
/////////////////////////////////////////////
#pragma mark -
#pragma mark Events
#pragma mark -
 
 
- (void)enableMouseMovedEvents
{
   _enabledMouseMovedEvents++;
   if(_enabledMouseMovedEvents == 1)
      [self setAcceptsMouseMovedEvents: YES];
}
 
- (void)disableMouseMovedEvents
{
   NSAssert(_enabledMouseMovedEvents >= 0, @"bogus -disableMouseMovedEvents");
   _enabledMouseMovedEvents--;
   if(_enabledMouseMovedEvents == 0)
      [self setAcceptsMouseMovedEvents: NO];
}
 
- (BOOL)canBecomeKeyWindow
{
   return (!__glutGameModeWindow && !_isFullscreen) ? [super canBecomeKeyWindow] : YES;
}
 
- (void)sendEvent: (NSEvent *)event
{
   [super sendEvent: event];
   if(__glutMappedMenu) {
      /* use mapped menu to determine if menu finishing needs to be done, regardless of button */
         __glutFinishMenu([NSEvent mouseLocation]); /* sets mapped menu to nil */
         __glutMenuWindow = nil;
    }
}
 
- (BOOL)validateMenuItem: (NSMenuItem *)menuItem
{
   SEL  action = [menuItem action];
   
   if(action == @selector(save:) || action == @selector(saveAs:))
      return (!__glutDisableGrabbing) ? [self isDocumentEdited] : NO;
   
   if(action == @selector(copy:))
      return (!__glutDisableGrabbing);
   
   if(__glutDisablePrinting) {
      if(action == @selector(runPageLayout:) || action == @selector(print:))
         return NO;
   }
   return [super validateMenuItem: menuItem];
}
 
- (IBAction)save: (id)sender
{
   if(_imagePath) {
      NSData *  data = [self contentsAsDataOfType: NSTIFFPboardType];
 
      if(!data || !__glutWriteDataToFile(data, _imagePath, 'TIFF')) {
         NSBundle * bdl = __glutGetFrameworkBundle();
         
         NSRunCriticalAlertPanel(NSLocalizedStringFromTableInBundle(@"Save Error", @"GLUTUI",
                              bdl, @"Save Error"),
                              NSLocalizedStringFromTableInBundle(@"Unable to save current window contents.", @"GLUTUI",
                              bdl, @"Unable to save current window contents."),
                              @"OK", nil, nil);
      }
      [self setDocumentEdited: NO];
   } else {
        [self saveAs: sender];
   }
}
 
   /* Save As */
- (void)savePanelDidEnd: (NSWindow *)sheet returnCode: (int)returnCode contextInfo: (id)savePanel
{
   if(returnCode == NSOKButton) {
      NSData *  data = [self contentsAsDataOfType: NSTIFFPboardType];
      
      [_imagePath release];
      _imagePath = [[savePanel filename] copy];
      
      if(!data || !__glutWriteDataToFile(data, _imagePath, 'TIFF')) {
         NSBundle * bdl = __glutGetFrameworkBundle();
         
         NSRunCriticalAlertPanel(NSLocalizedStringFromTableInBundle(@"Save Error", @"GLUTUI",
                              bdl, @"Save Error"),
                              NSLocalizedStringFromTableInBundle(@"Unable to save current window contents.", @"GLUTUI",
                              bdl, @"Unable to save current window contents."),
                              @"OK", nil, nil);
      }
 
      [self setDocumentEdited: NO];
   }
}
 
- (IBAction)saveAs: (id)sender
{
   NSSavePanel *    savePanel = [NSSavePanel savePanel];
   NSString *       imageDirectory, *imageName;
   
   if(!_imagePath) {
      _imagePath = [[[NSFileManager defaultManager] currentDirectoryPath] copy];
      imageDirectory = _imagePath;
      imageName = @"";
   } else {
      imageDirectory = _imagePath;
      imageName = [[_imagePath lastPathComponent] stringByDeletingPathExtension];
   }
   
   [savePanel setCanSelectHiddenExtension: YES];
   [savePanel setRequiredFileType: @"tiff"];
   [savePanel   beginSheetForDirectory: imageDirectory
               file: imageName
               modalForWindow: self
               modalDelegate: self
               didEndSelector: @selector(savePanelDidEnd:returnCode:contextInfo:)
               contextInfo: savePanel];
}
 
- (IBAction)copy: (id)sender
{
   NSString *   type = NSTIFFPboardType;
   NSData *     imageData = [self contentsAsDataOfType: type];
    
   if(imageData) {
      NSPasteboard *    generalPboard = [NSPasteboard generalPasteboard];
      
      [generalPboard declareTypes: [NSArray arrayWithObjects: type, nil] owner: nil];
      [generalPboard setData: imageData forType: type];
   }
}
 
   /* Page Layout */
- (void)pageLayoutDidEnd: (NSPageLayout *)pageLayout returnCode: (int)returnCode contextInfo: (id)printInfo
{
   if(returnCode == NSOKButton) {
      [NSPrintInfo setSharedPrintInfo: printInfo];
   }
}
 
- (void)runPageLayout: (id)sender
{
   NSPageLayout *   pageLayout = [NSPageLayout pageLayout];
   NSPrintInfo *    printInfo = [NSPrintInfo sharedPrintInfo];
   
   [pageLayout  beginSheetWithPrintInfo: printInfo
               modalForWindow: self
               delegate: self
               didEndSelector: @selector(pageLayoutDidEnd:returnCode:contextInfo:)
               contextInfo: printInfo];
}
 
   /* Print Panel */
- (void)printOperationDidRun: (NSPrintOperation *)printOperation success: (BOOL)success contextInfo: (id)window
{
   [window release];
}
 
- (void)print: (id)sender
{
   NSWindow *   window = [[self _windowWithTIFFInsideRect: NSZeroRect] retain];
   
   if(window) {
      NSPrintOperation *    printOperation = [NSPrintOperation printOperationWithView: [window contentView]];
      
      [printOperation   runOperationModalForWindow: self
                        delegate: self
                        didRunSelector: @selector(printOperationDidRun:success:contextInfo:)
                        contextInfo: window];
   } else {
      NSBundle *    bdl = __glutGetFrameworkBundle();
      
      NSRunCriticalAlertPanel(NSLocalizedStringFromTableInBundle(@"Print Error", @"GLUTUI",
                              bdl, @"Print Error"),
                              NSLocalizedStringFromTableInBundle(@"Could not generate PDF data for printing.", @"GLUTUI",
                              bdl, @"Could not generate PDF data for printing."),
                              @"OK", nil, nil);
   }
}
 
- (void)zoom:(id)sender
{
   NSMutableSet *   views = [NSMutableSet set];
   GLUTView *       view = (GLUTView *) [self contentView];
   
   [views unionSet: [view coveredViews]];
   [views addObject: view];
   
   [super zoom: sender];
   
   [views unionSet: [view coveredViews]];
   [GLUTView evaluateVisibilityOfViews: views];
}
 
 
/////////////////////////////////////////////
#pragma mark -
#pragma mark Services
#pragma mark -
 
 
- (id)validRequestorForSendType:(NSString *)sendType returnType:(NSString *)returnType
{
   if([gServicesTypes containsObject: sendType])
      return self;
   
   return [super validRequestorForSendType: sendType returnType: returnType];
}
 
- (BOOL)writeSelectionToPasteboard:(NSPasteboard *)pboard types:(NSArray *)types
{
   unsigned i, count = [types count];
   
   for(i = 0; i < count; i++) {
      NSString *    pboardType = [types objectAtIndex: i];
      NSData *      imageData = [self contentsAsDataOfType: pboardType];
      
      if(imageData) {
         [pboard declareTypes: [NSArray arrayWithObject: pboardType] owner: nil];
         [pboard setData: imageData forType: pboardType];
         return YES;
      }
   }
   
   return NO;
}
 
 
/////////////////////////////////////////////
#pragma mark -
#pragma mark Image Data Creation
#pragma mark -
 
 
- (NSWindow *)_windowWithTIFFInsideRect: (NSRect)rect
{
   NSImage *        image = nil;
   NSImageView *    imageView = nil;
   NSWindow *       window = nil;
   GLUTView *       view = (GLUTView *) [self contentView];
   
   if(NSIsEmptyRect(rect))
      rect = [view bounds];
   
   if((imageView = [[NSImageView alloc] initWithFrame: rect]) == nil)
      return nil;
   
   if((window = [[NSWindow alloc]   initWithContentRect: rect
                                                styleMask: NSBorderlessWindowMask
                                                backing: NSBackingStoreNonretained
                                                defer: NO]) == nil) {
      [imageView release];
      return nil;
   }
      
   [window setContentView: imageView];
   [imageView release];
   
   if((image = [view imageWithTIFFInsideRect: rect]) == nil) {
      [window release];
      return nil;
   }    
   [imageView setImage: image];
   
   return [window autorelease];
}
 
- (NSData *)_dataWithTIFFOfContentView
{
   NSImage *    image = [(GLUTView *) [self contentView] imageWithTIFFInsideRect: NSZeroRect];
   NSData *     data = nil;
   
   if(image != nil) {
      data = [image TIFFRepresentation];
   }
   return data;
}
 
- (NSData *)_dataWithRTFDOfContentView
{
    static int              generationCounter = 1;
    NSAttributedString *    myString = nil;
    NSFileWrapper *     myFileWrapper = nil;
    NSTextAttachment *  myTextAttachment = nil;
    NSData *                    tiffData = [self _dataWithTIFFOfContentView];
        
        // create file wrapper
    if((myFileWrapper = [[NSFileWrapper alloc] initRegularFileWithContents: tiffData]) == nil)
      return nil;
    [myFileWrapper setPreferredFilename: [NSString stringWithFormat: @"GLUT Picture No.%d.tiff", generationCounter++]];
    
        // create the text attachment
    if((myTextAttachment = [[NSTextAttachment alloc] initWithFileWrapper: myFileWrapper]) == nil) {
      [myFileWrapper release];
      return nil;
   }
    [myFileWrapper release];
    
        // create the attributed string
    if((myString = [NSAttributedString attributedStringWithAttachment: myTextAttachment]) == nil) {
      [myTextAttachment release];
      return nil;
   }
    [myTextAttachment release];
        
        // return the flattend data
    return [myString RTFDFromRange: NSMakeRange(0, [myString length]) documentAttributes: nil];
}
 
    /* Returns a data object containing the current contents of the receiving window */
- (NSData *)contentsAsDataOfType: (NSString *)pboardType
{
   NSData * data = nil;
   
   if([pboardType isEqualToString: NSTIFFPboardType] == YES) {
        data = [self _dataWithTIFFOfContentView];
    } else if([pboardType isEqualToString: NSRTFDPboardType] == YES) {
        data = [self _dataWithRTFDOfContentView];
    }
    
    return data;
}
 
 
/////////////////////////////////////////////
#pragma mark -
#pragma mark Delegate 
#pragma mark -
 
 
- (BOOL)windowShouldClose: (id)sender
{
   GLUTView *       view = (GLUTView *) [self contentView];
   GLUTwmcloseCB    closeFunc = [view wmCloseCallback];
   
   /* Enable special behavior of glutDestroyWindow() while we're here */
   __glutInsideWindowShouldClose = YES;
   __glutShouldWindowClose = NO;
   __glutSetWindow(view);
   closeFunc();
   __glutInsideWindowShouldClose = NO;
   
   /* Only return with YES, if the application called glutDestroyWindow().
      Return NO otherwise. */
   return __glutShouldWindowClose;
}
 
    /* Update the window status of the receiver and all of it's sub windows. */
- (void)windowWillMiniaturize:(NSNotification *)notification
{
   GLUTView *   view = (GLUTView *) [self contentView];
 
   /* Miniaturizing a window with an OpenGL content view in it is a bit tricky,
      because the OpenGL graphics is drawn in its own surface which floats above
      the Quartz window and thus is not directly accessible to the latter.
      In order to get around this, we tell our GLUTView to copy its OpenGL pixels
      from its associated surface into its Quartz context. The copied pixels will
      finally end up in the window's backing store where they can be easily
      grabbed from by the window minaturization engine. */
   [view prepareForMiniaturization];
   _viewStorage = [[NSMutableSet alloc] init];
   [_viewStorage unionSet: [view coveredViews]];
   [_viewStorage addObject: view];
}
 
- (void)windowDidMiniaturize:(NSNotification *)notification
{
   [GLUTView evaluateVisibilityOfViews: _viewStorage];
   [_viewStorage release];
   _viewStorage = nil;
   [(GLUTView *) [self contentView] setShown: NO];
}
 
- (void)windowWillMove:(NSNotification *)notification
{
   GLUTView *   view = (GLUTView *) [self contentView];
 
   _viewStorage = [[NSMutableSet alloc] init];
   [_viewStorage unionSet: [view coveredViews]];
   [_viewStorage addObject: view];
}
 
- (void)windowDidMove:(NSNotification *)notification
{
   [_viewStorage unionSet: [(GLUTView *) [self contentView] coveredViews]];
   [GLUTView evaluateVisibilityOfViews: _viewStorage];
   [_viewStorage release];
   _viewStorage = nil;
}
 
- (void)orderWindow:(NSWindowOrderingMode)place relativeTo:(int)otherWin
{
   GLUTView *   view = (GLUTView *) [self contentView];
 
   if(view != nil) {
      NSMutableSet *    views = [NSMutableSet set];
      
      if(place == NSWindowOut) {
         [views unionSet: [view coveredViews]];
         [views addObject: view];
      }
  
      [view setShown: (place != NSWindowOut)];
      [super orderWindow: place relativeTo: otherWin];
      
      if(place != NSWindowOut) {
         [views unionSet: [view coveredViews]];
         [view recursiveCollectViewsIntoSet: views];
      }
      
      [GLUTView evaluateVisibilityOfViews: views];
   } else {
      [super orderWindow: place relativeTo: otherWin];
   }
}
 
@end