1/* 2 * tracker/cocoa/AppDelegate.mm 3 * 4 * Copyright 2014 Dale Whinham 5 * 6 * This file is part of Milkytracker. 7 * 8 * Milkytracker is free software: you can redistribute it and/or modify 9 * it under the terms of the GNU General Public License as published by 10 * the Free Software Foundation, either version 3 of the License, or 11 * (at your option) any later version. 12 * 13 * Milkytracker is distributed in the hope that it will be useful, 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 * GNU General Public License for more details. 17 * 18 * You should have received a copy of the GNU General Public License 19 * along with Milkytracker. If not, see <http://www.gnu.org/licenses/>. 20 * 21 */ 22 23// -------- Cocoa/OS X -------- 24#import <Cocoa/Cocoa.h> 25#import <dispatch/dispatch.h> 26 27// ---------- Tracker --------- 28#import "DisplayDevice_COCOA.h" 29#import "MidiReceiver_CoreMIDI.h" 30#import "PPMutex.h" 31#import "Screen.h" 32#import "Tracker.h" 33 34@implementation AppDelegate 35 36// ---------- Display --------- 37@synthesize myWindow; 38@synthesize myTrackerView; 39@synthesize myProgressWindow; 40@synthesize myProgressIndicator; 41 42// ------ Tracker Globals ----- 43static PPScreen* myTrackerScreen; 44static Tracker* myTracker; 45static PPDisplayDevice* myDisplayDevice; 46static MidiReceiver* myMidiReceiver; 47 48static PPMutex* globalMutex; 49 50static BOOL startupAfterFullScreen; 51static BOOL startupComplete; 52static NSMutableArray* filesToLoad; 53 54static CVDisplayLinkRef displayLink; 55 56// TODO: Crash handler 57 58void RaiseEventSynchronized(PPEvent* event) 59{ 60 if (myTrackerScreen && globalMutex->tryLock()) 61 { 62 myTrackerScreen->raiseEvent(event); 63 globalMutex->unlock(); 64 } 65} 66 67static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp* now, 68 const CVTimeStamp* outputTime, CVOptionFlags flagsIn, 69 CVOptionFlags* flagsOut, void* displayLinkContext) 70{ 71 // Raise the event on the main thread 72 dispatch_async(dispatch_get_main_queue(), ^ 73 { 74 if (!myTrackerScreen) 75 return; 76 77 PPEvent e = PPEvent(eTimer); 78 RaiseEventSynchronized(&e); 79 }); 80 81 return kCVReturnSuccess; 82} 83 84- (void)initTracker 85{ 86 [myWindow setTitle:@"Loading MilkyTracker..."]; 87 [myWindow display]; 88 89 // Instantiate the tracker 90 myTracker = new Tracker(); 91 92 // Retrieve and apply display settings 93 PPSize windowSize = myTracker->getWindowSizeFromDatabase(); 94 pp_int32 scaleFactor = myTracker->getScreenScaleFactorFromDatabase(); 95 bool fullScreen = myTracker->getFullScreenFlagFromDatabase(); 96 97 // Bring up display device 98 myDisplayDevice = new PPDisplayDevice(myWindow, 99 myTrackerView, 100 windowSize.width, 101 windowSize.height, 102 scaleFactor, 103 32); 104 105 // Enable fullscreen mode if necessary 106 myDisplayDevice->goFullScreen(fullScreen); 107 108 // Should we wait for fullscreen transition before completing startup? 109 startupAfterFullScreen = fullScreen; 110 111 // Attach display to tracker 112 myTrackerScreen = new PPScreen(myDisplayDevice, myTracker); 113 myTracker->setScreen(myTrackerScreen); 114 115 // Init MIDI 116 myMidiReceiver = new MidiReceiver(*myTracker, *globalMutex); 117 myMidiReceiver->init(); 118} 119 120- (void)trackerStartUp 121{ 122 // Force immediate screen updates during splash screen because Cocoa loop is blocked 123 myDisplayDevice->setImmediateUpdates(true); 124 125 // Perform startup 126 myTracker->startUp(); 127 128 // Allow Cocoa to handle refresh again (keeps event processing smooth and responsive) 129 myDisplayDevice->setImmediateUpdates(false); 130 131 // CVDisplayLink gives us a callback synchronised with vertical blanking 132 CVDisplayLinkCreateWithActiveCGDisplays(&displayLink); 133 CVDisplayLinkSetOutputCallback(displayLink, &DisplayLinkCallback, NULL); 134 CVDisplayLinkStart(displayLink); 135 136 // Signal startup complete 137 startupComplete = YES; 138 139 // Handle deferred file loading 140 for (NSString* filename in filesToLoad) 141 [self application: NSApp openFile:filename]; 142 143 [filesToLoad removeAllObjects]; 144 filesToLoad = nil; 145} 146 147- (void)timerCallback:(NSTimer*)theTimer 148{ 149 if (!myTrackerScreen) 150 return; 151 152 PPEvent e = PPEvent(eTimer); 153 RaiseEventSynchronized(&e); 154} 155 156- (void)applicationDidFinishLaunching:(NSNotification *)aNotification 157{ 158 // Initialisation 159 globalMutex = new PPMutex(); 160 [self initTracker]; 161 162 if (!startupAfterFullScreen) 163 [self trackerStartUp]; 164} 165 166#pragma mark Progress window 167- (void)showProgress:(BOOL)yes 168{ 169 if (yes) 170 { 171 [myProgressWindow makeKeyAndOrderFront:nil]; 172 [myProgressIndicator startAnimation:nil]; 173 } 174 else 175 { 176 [myProgressIndicator stopAnimation:nil]; 177 [myProgressWindow orderOut:nil]; 178 } 179} 180 181#pragma mark Application events 182- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender 183{ 184 return myTracker->shutDown() ? NSTerminateNow : NSTerminateCancel; 185} 186 187- (void)applicationWillTerminate:(NSNotification *)aNotification 188{ 189 myMidiReceiver->close(); 190 delete myMidiReceiver; 191 192 CVDisplayLinkStop(displayLink); 193 CVDisplayLinkRelease(displayLink); 194 195 delete myTracker; 196 delete myTrackerScreen; 197 delete myDisplayDevice; 198 delete globalMutex; 199} 200 201#pragma mark File open events 202- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename 203{ 204 // Startup not complete; hold onto the file path and load it later 205 if (!startupComplete) 206 { 207 if (!filesToLoad) 208 filesToLoad = [[NSMutableArray alloc] initWithObjects:filename, nil]; 209 else 210 [filesToLoad addObject:filename]; 211 } 212 else 213 { 214 // Temp buffer for file path 215 char filePath[PATH_MAX + 1]; 216 217 // Convert to C string 218 [filename getCString:filePath maxLength:PATH_MAX encoding:NSUTF8StringEncoding]; 219 220 // Create system string from C string 221 PPSystemString sysString(filePath); 222 PPSystemString* sysStrPtr = &sysString; 223 224 // Raise file drop event 225 PPEvent event(eFileDragDropped, &sysStrPtr, sizeof(PPSystemString*)); 226 RaiseEventSynchronized(&event); 227 } 228 229 return YES; 230} 231 232- (void)application:(NSApplication *)theApplication openFiles:(NSArray *)filenames 233{ 234 // Call the single openFile delegate method when multiple files are opened 235 for (NSString* filename in filenames) 236 [[NSApp delegate] application:NSApp openFile:filename]; 237} 238 239#pragma mark Window events 240- (BOOL)windowShouldClose:(id)sender 241{ 242 [NSApp terminate:self]; 243 return NO; 244} 245 246- (void)windowDidResignKey:(NSNotification *)notification 247{ 248 // Clear modifier keys if window loses focus 249 clearKeyModifier(KeyModifierCTRL); 250 clearKeyModifier(KeyModifierALT); 251 clearKeyModifier(KeyModifierSHIFT); 252} 253 254- (void)windowWillEnterFullScreen:(NSNotification *)notification 255{ 256 PPEvent event(eFullScreen); 257 RaiseEventSynchronized(&event); 258} 259 260- (void)windowWillExitFullScreen:(NSNotification *)notification 261{ 262 PPEvent event(eFullScreen); 263 RaiseEventSynchronized(&event); 264} 265 266- (void)windowDidEnterFullScreen:(NSNotification *)notification 267{ 268 if (startupAfterFullScreen) 269 { 270 [self trackerStartUp]; 271 startupAfterFullScreen = NO; 272 } 273} 274@end 275