Application.m
/* |
File: Application.m |
Abstract: Application class. |
Version: 1.1 |
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple |
Inc. ("Apple") in consideration of your agreement to the following |
terms, and your use, installation, modification or redistribution of |
this Apple software constitutes acceptance of these terms. If you do |
not agree with these terms, please do not use, install, modify or |
redistribute this Apple software. |
In consideration of your agreement to abide by the following terms, and |
subject to these terms, Apple grants you a personal, non-exclusive |
license, under Apple's copyrights in this original Apple software (the |
"Apple Software"), to use, reproduce, modify and redistribute the Apple |
Software, with or without modifications, in source and/or binary forms; |
provided that if you redistribute the Apple Software in its entirety and |
without modifications, you must retain this notice and the following |
text and disclaimers in all such redistributions of the Apple Software. |
Neither the name, trademarks, service marks or logos of Apple Inc. may |
be used to endorse or promote products derived from the Apple Software |
without specific prior written permission from Apple. Except as |
expressly stated in this notice, no other rights or licenses, express or |
implied, are granted by Apple herein, including but not limited to any |
patent rights that may be infringed by your derivative works or by other |
works in which the Apple Software may be incorporated. |
The Apple Software is provided by Apple on an "AS IS" basis. APPLE |
MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION |
THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS |
FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND |
OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. |
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL |
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, |
MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED |
AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), |
STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE |
POSSIBILITY OF SUCH DAMAGE. |
Copyright (C) 2009 Apple Inc. All Rights Reserved. |
*/ |
/* We use CGL macros to ensure optimal performances and avoid having to deal with the current OpenGL context */ |
#import <OpenGL/CGLMacro.h> |
#import "Application.h" |
//CONSTANTS: |
#define kOpenURLPortKey @"openURL" |
//CLASS INTERFACES: |
@interface Application (Internal) |
- (BOOL) _hasURLPort; |
- (NSURL*) _renderWithArguments:(NSDictionary*)arguments; |
- (NSMenu*) _contextualMenu; |
@end |
//CLASS IMPLEMENTATIONS: |
@implementation ApplicationView |
- (BOOL) acceptsFirstMouse:(NSEvent*)theEvent |
{ |
/* We want the first click to be interpreted */ |
return YES; |
} |
- (NSMenu*) menuForEvent:(NSEvent*)event |
{ |
/* Simply return menu from application's nib */ |
return [(Application*)NSApp _contextualMenu]; |
} |
- (void) drawRect:(NSRect)rect |
{ |
/* Clear the view with transparency */ |
[[NSColor clearColor] set]; |
NSRectFill(rect); |
} |
- (void) mouseDown:(NSEvent*)event |
{ |
NSRect bounds = [self bounds]; |
NSPoint mouse; |
NSURL* openURL; |
/* Make sure the current composition has an "openURL" output String port and we have a double-click */ |
if(![(Application*)NSApp _hasURLPort] || ([event clickCount] != 2)) { |
[super mouseDown:event]; |
return; |
} |
/* Normalize the mouse coordinates */ |
mouse = [self convertPoint:[event locationInWindow] fromView:nil]; |
mouse.x /= bounds.size.width; |
mouse.y /= bounds.size.height; |
/* Render a single frame immediately passing the mouse coordinates and the event, and if a URL is returned, open it */ |
openURL = [(Application*)NSApp _renderWithArguments:[NSDictionary dictionaryWithObjectsAndKeys:[NSValue valueWithPoint:mouse], QCRendererMouseLocationKey, event, QCRendererEventKey, nil]]; |
if(openURL) |
[[NSWorkspace sharedWorkspace] openURL:openURL]; |
} |
- (void) mouseUp:(NSEvent*)event |
{ |
NSRect bounds = [self bounds]; |
NSPoint mouse; |
/* Make sure the current composition has an "openURL" output String port and we have a double-click */ |
if(![(Application*)NSApp _hasURLPort] || ([event clickCount] != 2)) { |
[super mouseDown:event]; |
return; |
} |
/* Normalize the mouse coordinates */ |
mouse = [self convertPoint:[event locationInWindow] fromView:nil]; |
mouse.x /= bounds.size.width; |
mouse.y /= bounds.size.height; |
/* Render a single frame immediately passing the mouse coordinates and the event */ |
[(Application*)NSApp _renderWithArguments:[NSDictionary dictionaryWithObjectsAndKeys:[NSValue valueWithPoint:mouse], QCRendererMouseLocationKey, event, QCRendererEventKey, nil]]; |
} |
@end |
@implementation Application |
+ (void) initialize |
{ |
NSMutableDictionary* defaults = [NSMutableDictionary dictionary]; |
/* Preset our user defaults */ |
[defaults setObject:[NSNumber numberWithInteger:30] forKey:kUserDefaultKey_RenderPeriod]; |
[defaults setObject:[NSNumber numberWithInteger:256] forKey:kUserDefaultKey_RenderWidth]; |
[defaults setObject:[NSNumber numberWithInteger:256] forKey:kUserDefaultKey_RenderHeight]; |
[[NSUserDefaults standardUserDefaults] registerDefaults:defaults]; |
} |
- (BOOL) validateMenuItem:(NSMenuItem*)menuItem |
{ |
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; |
/* Set checkmark for render frequency menu items */ |
if([menuItem action] == @selector(setRefreshFrequency:)) { |
if([menuItem tag] == [defaults integerForKey:kUserDefaultKey_RenderPeriod]) |
[menuItem setState:NSOnState]; |
else |
[menuItem setState:NSOffState]; |
} |
else if([menuItem action] == @selector(setParameters:)) |
return [parametersView hasParameters]; |
return [super validateMenuItem:menuItem]; |
} |
- (NSMenu*) _contextualMenu |
{ |
return contextualMenu; |
} |
- (BOOL) _hasURLPort |
{ |
return _hasURLPort; |
} |
- (NSURL*) _renderWithArguments:(NSDictionary*)arguments |
{ |
CGLContextObj cgl_ctx = [_glContext CGLContextObj]; |
NSTimeInterval time = [NSDate timeIntervalSinceReferenceDate]; |
NSURL* url = nil; |
NSString* string; |
/* Compute rendering time */ |
if(_startTime <= 0.0) |
_startTime = time; |
time -= _startTime; |
/* Render a frame from the composition or paint with red on error */ |
if([_renderer renderAtTime:time arguments:arguments]) { |
/* If the composition has an output port with key "OpenURL" and of type String, try to make a URL out of its current value */ |
if(_hasURLPort) { |
string = [_renderer valueForOutputKey:kOpenURLPortKey]; |
if([string length]) |
url = [NSURL URLWithString:string]; |
} |
} |
else { |
glClearColor(1.0, 0.0, 0.0, 1.0); |
glClear(GL_COLOR_BUFFER_BIT); |
} |
[_glContext flushBuffer]; |
return url; |
} |
- (void) _renderTimer:(NSTimer*)timer |
{ |
[self _renderWithArguments:nil]; |
} |
- (void) _viewGlobalFrameDidChange:(NSNotification*)notification |
{ |
NSRect frame = [[_window contentView] frame]; |
CGLContextObj cgl_ctx = [_glContext CGLContextObj]; |
/* Make sure to update the OpenGL context */ |
[_glContext update]; |
glViewport(0, 0, frame.size.width, frame.size.height); |
/* Re-render immediately */ |
[self _renderWithArguments:nil]; |
} |
- (void) finishLaunching |
{ |
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; |
NSOpenGLPixelFormatAttribute attributes[] = {NSOpenGLPFAAccelerated, NSOpenGLPFADoubleBuffer, NSOpenGLPFADepthSize, 24, 0}; |
GLint value; |
NSString* path; |
/* Call super */ |
[super finishLaunching]; |
/* Create and configure main window */ |
_window = [[NSWindow alloc] initWithContentRect:NSMakeRect(100, 100, 256, 256) styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:YES]; |
[_window setFrameAutosaveName:@"mainWindow"]; |
[_window setHasShadow:NO]; |
[_window setAlphaValue:1.0]; |
[_window setBackgroundColor:[NSColor clearColor]]; |
[_window setOpaque:NO]; |
[_window setDelegate:self]; |
[_window setMovableByWindowBackground:YES]; |
[_window setLevel:kCGDesktopWindowLevel]; |
[_window setContentView:[[[ApplicationView alloc] initWithFrame:NSMakeRect(0, 0, 100, 100)] autorelease]]; |
[_window setContentSize:NSMakeSize((CGFloat)[defaults integerForKey:kUserDefaultKey_RenderWidth], (CGFloat)[defaults integerForKey:kUserDefaultKey_RenderHeight])]; |
/* Create and configure OpenGL context */ |
_glFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attributes]; |
_glContext = [[NSOpenGLContext alloc] initWithFormat:_glFormat shareContext:nil]; |
if(_glContext == nil) { |
NSLog(@"Failed creating OpenGL context"); |
[NSApp terminate:nil]; |
} |
value = 1; |
[_glContext setValues:&value forParameter:NSOpenGLCPSwapInterval]; |
value = 0; |
[_glContext setValues:&value forParameter:NSOpenGLCPSurfaceOpacity]; |
/* Show main window and attach OpenGL context */ |
[_window makeKeyAndOrderFront:nil]; |
[_glContext setView:[_window contentView]]; |
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_viewGlobalFrameDidChange:) name:NSViewGlobalFrameDidChangeNotification object:[_window contentView]]; |
/* Create QCRenderer from selected composition file or fail to default composition file on error */ |
if(path = [defaults objectForKey:kUserDefaultKey_CompositionPath]) { |
_renderer = [[QCRenderer alloc] initWithOpenGLContext:_glContext pixelFormat:_glFormat file:path]; |
[_renderer setInputValuesWithPropertyList:[defaults objectForKey:kUserDefaultKey_CompositionParameters]]; |
} |
if(_renderer == nil) |
_renderer = [[QCRenderer alloc] initWithOpenGLContext:_glContext pixelFormat:_glFormat file:[[NSBundle mainBundle] pathForResource:@"Default Composition" ofType:@"qtz"]]; |
if(_renderer == nil) { |
[NSApp terminate:nil]; |
NSLog(@"Failed creating QCRenderer"); |
} |
_hasURLPort = ([[_renderer outputKeys] containsObject:kOpenURLPortKey] && [[[[_renderer attributes] objectForKey:kOpenURLPortKey] objectForKey:QCPortAttributeTypeKey] isEqualToString:QCPortTypeString]); |
[parametersView setCompositionRenderer:_renderer]; |
/* Render a frame immediately */ |
[self _renderWithArguments:nil]; |
/* Create rendering timer */ |
_timer = [[NSTimer scheduledTimerWithTimeInterval:(NSTimeInterval)[defaults integerForKey:kUserDefaultKey_RenderPeriod] target:self selector:@selector(_renderTimer:) userInfo:nil repeats:YES] retain]; |
} |
@end |
@implementation Application (Actions) |
- (IBAction) selectComposition:(id)sender |
{ |
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; |
NSOpenPanel* openPanel = [NSOpenPanel openPanel]; |
QCRenderer* renderer; |
/* Allow user to select a composition file and create a QCRenderer from it */ |
if([openPanel runModalForTypes:[NSArray arrayWithObject:@"qtz"]] == NSFileHandlingPanelOKButton) { |
if((renderer = [[QCRenderer alloc] initWithOpenGLContext:_glContext pixelFormat:_glFormat file:[openPanel filename]])) { |
/* Replace current QCRenderer with new one */ |
[_renderer release]; |
_renderer = renderer; |
_hasURLPort = ([[_renderer outputKeys] containsObject:kOpenURLPortKey] && [[[[_renderer attributes] objectForKey:kOpenURLPortKey] objectForKey:QCPortAttributeTypeKey] isEqualToString:QCPortTypeString]); |
[parametersView setCompositionRenderer:_renderer]; |
/* Update defaults */ |
[defaults setObject:[openPanel filename] forKey:kUserDefaultKey_CompositionPath]; |
[defaults removeObjectForKey:kUserDefaultKey_CompositionParameters]; |
/* Re-render immediately */ |
_startTime = 0.0; |
[self _renderWithArguments:nil]; |
} |
else |
NSBeep(); |
} |
} |
- (void) _parametersSheetDidEnd:(NSWindow*)sheet returnCode:(NSInteger)returnCode contextInfo:(void*)contextInfo |
{ |
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; |
/* Dismiss parameters window */ |
[sheet orderOut:nil]; |
/* Save parameters to defaults */ |
[defaults setObject:[_renderer propertyListFromInputValues] forKey:kUserDefaultKey_CompositionParameters]; |
/* Re-render immediately */ |
[self _renderWithArguments:nil]; |
} |
- (IBAction) setParameters:(id)sender |
{ |
/* Show or hide parameters window sheet */ |
if([sender isKindOfClass:[NSMenuItem class]]) |
[NSApp beginSheet:parametersWindow modalForWindow:_window modalDelegate:self didEndSelector:@selector(_parametersSheetDidEnd:returnCode:contextInfo:) contextInfo:NULL]; |
else |
[NSApp endSheet:parametersWindow]; |
} |
- (void) _dimensionsSheetDidEnd:(NSWindow*)sheet returnCode:(NSInteger)returnCode contextInfo:(void*)contextInfo |
{ |
/* Dismiss dimensions window */ |
[sheet orderOut:nil]; |
} |
- (IBAction) updateDimensions:(id)sender |
{ |
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; |
NSRect frame = [_window frame]; |
/* Update main window dimensions */ |
[_window setFrame:NSMakeRect(frame.origin.x, frame.origin.y + frame.size.height - (CGFloat)[defaults integerForKey:kUserDefaultKey_RenderHeight], (CGFloat)[defaults integerForKey:kUserDefaultKey_RenderWidth], (CGFloat)[defaults integerForKey:kUserDefaultKey_RenderHeight]) display:YES animate:NO]; |
/* Force update as NSViewGlobalFrameDidChangeNotification is not always posted */ |
[self _viewGlobalFrameDidChange:nil]; |
} |
- (IBAction) setDimensions:(id)sender |
{ |
/* Show or hide dimensions window sheet */ |
if([sender isKindOfClass:[NSMenuItem class]]) |
[NSApp beginSheet:dimensionsWindow modalForWindow:_window modalDelegate:self didEndSelector:@selector(_dimensionsSheetDidEnd:returnCode:contextInfo:) contextInfo:NULL]; |
else |
[NSApp endSheet:dimensionsWindow]; |
} |
- (IBAction) setRefreshFrequency:(id)sender |
{ |
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; |
/* Update rendering period */ |
[defaults setInteger:[(NSMenuItem*)sender tag] forKey:kUserDefaultKey_RenderPeriod]; |
/* Reset rendering timer */ |
[_timer invalidate]; |
[_timer release]; |
_timer = [[NSTimer scheduledTimerWithTimeInterval:(NSTimeInterval)[defaults integerForKey:kUserDefaultKey_RenderPeriod] target:self selector:@selector(_renderTimer:) userInfo:nil repeats:YES] retain]; |
/* Re-render immediately */ |
[self _renderWithArguments:nil]; |
} |
@end |
Copyright © 2009 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2009-10-27