diff options
Diffstat (limited to 'platform/macos/app/AppDelegate.m')
-rw-r--r-- | platform/macos/app/AppDelegate.m | 270 |
1 files changed, 270 insertions, 0 deletions
diff --git a/platform/macos/app/AppDelegate.m b/platform/macos/app/AppDelegate.m new file mode 100644 index 0000000000..b7860cf130 --- /dev/null +++ b/platform/macos/app/AppDelegate.m @@ -0,0 +1,270 @@ +#import "AppDelegate.h" + +#import "MapDocument.h" + +NSString * const MGLMapboxAccessTokenDefaultsKey = @"MGLMapboxAccessToken"; +NSString * const MGLLastMapCameraDefaultsKey = @"MGLLastMapCamera"; +NSString * const MGLLastMapStyleURLDefaultsKey = @"MGLLastMapStyleURL"; +NSString * const MGLLastMapDebugMaskDefaultsKey = @"MGLLastMapDebugMask"; + +/** + Some convenience methods to make offline pack properties easier to bind to. + */ +@implementation MGLOfflinePack (Additions) + ++ (NSSet *)keyPathsForValuesAffectingStateImage { + return [NSSet setWithObjects:@"state", nil]; +} + +- (NSImage *)stateImage { + switch (self.state) { + case MGLOfflinePackStateComplete: + return [NSImage imageNamed:@"NSMenuOnStateTemplate"]; + + case MGLOfflinePackStateActive: + return [NSImage imageNamed:@"NSFollowLinkFreestandingTemplate"]; + + default: + return nil; + } +} + ++ (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingCountOfResourcesCompleted { + return [NSSet setWithObjects:@"progress", nil]; +} + +- (uint64_t)countOfResourcesCompleted { + return self.progress.countOfResourcesCompleted; +} + ++ (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingCountOfResourcesExpected { + return [NSSet setWithObjects:@"progress", nil]; +} + +- (uint64_t)countOfResourcesExpected { + return self.progress.countOfResourcesExpected; +} + ++ (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingCountOfBytesCompleted { + return [NSSet setWithObjects:@"progress", nil]; +} + +- (uint64_t)countOfBytesCompleted { + return self.progress.countOfBytesCompleted; +} + +@end + +@interface AppDelegate () + +@property (weak) IBOutlet NSArrayController *offlinePacksArrayController; +@property (weak) IBOutlet NSPanel *offlinePacksPanel; + +@end + +@implementation AppDelegate + +#pragma mark Lifecycle + ++ (void)load { + // Set access token, unless MGLAccountManager already read it in from Info.plist. + if (![MGLAccountManager accessToken]) { + NSString *accessToken = [NSProcessInfo processInfo].environment[@"MAPBOX_ACCESS_TOKEN"]; + if (accessToken) { + // Store to preferences so that we can launch the app later on without having to specify + // token. + [[NSUserDefaults standardUserDefaults] setObject:accessToken forKey:MGLMapboxAccessTokenDefaultsKey]; + } else { + // Try to retrieve from preferences, maybe we've stored them there previously and can reuse + // the token. + accessToken = [[NSUserDefaults standardUserDefaults] stringForKey:MGLMapboxAccessTokenDefaultsKey]; + } + [MGLAccountManager setAccessToken:accessToken]; + } +} + +- (void)applicationWillFinishLaunching:(NSNotification *)notification { + [[NSAppleEventManager sharedAppleEventManager] setEventHandler:self + andSelector:@selector(handleGetURLEvent:withReplyEvent:) + forEventClass:kInternetEventClass + andEventID:kAEGetURL]; + + if (![[NSUserDefaults standardUserDefaults] boolForKey:@"NSQuitAlwaysKeepsWindows"]) { + NSData *cameraData = [[NSUserDefaults standardUserDefaults] objectForKey:MGLLastMapCameraDefaultsKey]; + if (cameraData) { + NSKeyedUnarchiver *coder = [[NSKeyedUnarchiver alloc] initForReadingWithData:cameraData]; + self.pendingZoomLevel = -1; + self.pendingCamera = [[MGLMapCamera alloc] initWithCoder:coder]; + } + NSString *styleURLString = [[NSUserDefaults standardUserDefaults] objectForKey:MGLLastMapStyleURLDefaultsKey]; + if (styleURLString) { + self.pendingStyleURL = [NSURL URLWithString:styleURLString]; + } + self.pendingDebugMask = [[NSUserDefaults standardUserDefaults] integerForKey:MGLLastMapDebugMaskDefaultsKey]; + } +} + +- (void)applicationDidFinishLaunching:(NSNotification *)aNotification { + // Set access token, unless MGLAccountManager already read it in from Info.plist. + if (![MGLAccountManager accessToken]) { + NSAlert *alert = [[NSAlert alloc] init]; + alert.messageText = @"Access token required"; + alert.informativeText = @"To load Mapbox-hosted tiles and styles, enter your Mapbox access token in Preferences."; + [alert addButtonWithTitle:@"Open Preferences"]; + [alert runModal]; + [self showPreferences:nil]; + } + + [self.offlinePacksArrayController bind:@"content" toObject:[MGLOfflineStorage sharedOfflineStorage] withKeyPath:@"packs" options:nil]; +} + +- (void)applicationWillTerminate:(NSNotification *)notification { + if (![[NSUserDefaults standardUserDefaults] boolForKey:@"NSQuitAlwaysKeepsWindows"]) { + NSDocument *currentDocument = [NSDocumentController sharedDocumentController].currentDocument; + if ([currentDocument isKindOfClass:[MapDocument class]]) { + MGLMapView *mapView = [(MapDocument *)currentDocument mapView]; + NSMutableData *cameraData = [NSMutableData data]; + NSKeyedArchiver *coder = [[NSKeyedArchiver alloc] initForWritingWithMutableData:cameraData]; + [mapView.camera encodeWithCoder:coder]; + [coder finishEncoding]; + [[NSUserDefaults standardUserDefaults] setObject:cameraData forKey:MGLLastMapCameraDefaultsKey]; + [[NSUserDefaults standardUserDefaults] setObject:mapView.styleURL.absoluteString forKey:MGLLastMapStyleURLDefaultsKey]; + [[NSUserDefaults standardUserDefaults] setInteger:mapView.debugMask forKey:MGLLastMapDebugMaskDefaultsKey]; + } + } + + [self.offlinePacksArrayController unbind:@"content"]; +} + +#pragma mark Services + +- (void)handleGetURLEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent { + // mapboxgl://?center=29.95,-90.066667&zoom=14&bearing=45&pitch=30 + NSURL *url = [NSURL URLWithString:[event paramDescriptorForKeyword:keyDirectObject].stringValue]; + NS_MUTABLE_DICTIONARY_OF(NSString *, NSString *) *params = [[NSMutableDictionary alloc] init]; + for (NSString *param in [url.query componentsSeparatedByString:@"&"]) { + NSArray *parts = [param componentsSeparatedByString:@"="]; + if (parts.count >= 2) { + params[parts[0]] = [parts[1] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; + } + } + + MGLMapCamera *camera = [MGLMapCamera camera]; + NSString *zoomLevelString = params[@"zoom"]; + self.pendingZoomLevel = zoomLevelString.length ? zoomLevelString.doubleValue : -1; + + NSString *directionString = params[@"bearing"]; + if (directionString.length) { + camera.heading = directionString.doubleValue; + } + + NSString *centerString = params[@"center"]; + if (centerString) { + NSArray *coordinateValues = [centerString componentsSeparatedByString:@","]; + if (coordinateValues.count == 2) { + camera.centerCoordinate = CLLocationCoordinate2DMake([coordinateValues[0] doubleValue], + [coordinateValues[1] doubleValue]); + } + } + + NSString *pitchString = params[@"pitch"]; + if (pitchString.length) { + camera.pitch = pitchString.doubleValue; + } + + self.pendingCamera = camera; + [[NSDocumentController sharedDocumentController] openUntitledDocumentAndDisplay:YES error:NULL]; +} + +#pragma mark Offline pack management + +- (IBAction)showOfflinePacksPanel:(id)sender { + [self.offlinePacksPanel makeKeyAndOrderFront:sender]; + + for (MGLOfflinePack *pack in self.offlinePacksArrayController.arrangedObjects) { + [pack requestProgress]; + } +} + +- (IBAction)delete:(id)sender { + for (MGLOfflinePack *pack in self.offlinePacksArrayController.selectedObjects) { + [[MGLOfflineStorage sharedOfflineStorage] removePack:pack withCompletionHandler:^(NSError * _Nullable error) { + if (error) { + [[NSAlert alertWithError:error] runModal]; + } + }]; + } +} + +- (IBAction)chooseOfflinePack:(id)sender { + for (MGLOfflinePack *pack in self.offlinePacksArrayController.selectedObjects) { + switch (pack.state) { + case MGLOfflinePackStateComplete: + { + if ([pack.region isKindOfClass:[MGLTilePyramidOfflineRegion class]]) { + MGLTilePyramidOfflineRegion *region = (MGLTilePyramidOfflineRegion *)pack.region; + self.pendingVisibleCoordinateBounds = region.bounds; + self.pendingMinimumZoomLevel = region.minimumZoomLevel; + self.pendingMaximumZoomLevel = region.maximumZoomLevel; + [[NSDocumentController sharedDocumentController] openUntitledDocumentAndDisplay:YES error:NULL]; + } + break; + } + + case MGLOfflinePackStateInactive: + [pack resume]; + break; + + case MGLOfflinePackStateActive: + [pack suspend]; + break; + + default: + break; + } + } +} + +#pragma mark Help methods + +- (IBAction)showShortcuts:(id)sender { + NSAlert *alert = [[NSAlert alloc] init]; + alert.messageText = @"Mapbox GL Help"; + alert.informativeText = @"\ +• To scroll, swipe with two fingers on a trackpad, or drag the cursor, or press the arrow keys.\n\ +• To zoom in, pinch two fingers apart on a trackpad, or double-click, or hold down Shift while dragging the cursor down, or hold down Option while pressing the up key.\n\ +• To zoom out, pinch two fingers together on a trackpad, or double-tap on a mouse, or hold down Shift while dragging the cursor up, or hold down Option while pressing the down key.\n\ +• To rotate, move two fingers opposite each other in a circle on a trackpad, or hold down Option while dragging the cursor left and right, or hold down Option while pressing the left and right arrow keys.\n\ +• To tilt, hold down Option while dragging the cursor up and down.\n\ +• To drop a pin, click and hold.\ +"; + [alert runModal]; +} + +- (IBAction)showPreferences:(id)sender { + [self.preferencesWindow makeKeyAndOrderFront:sender]; +} + +- (IBAction)openAccessTokenManager:(id)sender { + [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"https://www.mapbox.com/studio/account/tokens/"]]; +} + +#pragma mark User interface validation + +- (BOOL)validateMenuItem:(NSMenuItem *)menuItem { + if (menuItem.action == @selector(showShortcuts:)) { + return YES; + } + if (menuItem.action == @selector(showPreferences:)) { + return YES; + } + if (menuItem.action == @selector(showOfflinePacksPanel:)) { + return YES; + } + if (menuItem.action == @selector(delete:)) { + return self.offlinePacksArrayController.selectedObjects.count; + } + return NO; +} + +@end |