[osx] Make osxapp document-based
Allows for multiple map windows open at a time.
This commit is contained in:
committed by
Minh Nguyễn
parent
6e4eb094bf
commit
0712ed904b
@@ -2,6 +2,7 @@
|
||||
|
||||
@interface AppDelegate : NSObject <NSApplicationDelegate>
|
||||
|
||||
@property (weak) IBOutlet NSWindow *preferencesWindow;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -1,40 +1,18 @@
|
||||
#import "AppDelegate.h"
|
||||
|
||||
#import "DroppedPinAnnotation.h"
|
||||
#import "LocationCoordinate2DTransformer.h"
|
||||
#import "MapDocument.h"
|
||||
#import "NSValue+Additions.h"
|
||||
|
||||
#import <Mapbox/Mapbox.h>
|
||||
|
||||
static NSString * const MGLMapboxAccessTokenDefaultsKey = @"MGLMapboxAccessToken";
|
||||
static NSString * const MGLDroppedPinAnnotationImageIdentifier = @"dropped";
|
||||
|
||||
static const CLLocationCoordinate2D WorldTourDestinations[] = {
|
||||
{ .latitude = 38.9131982, .longitude = -77.0325453144239 },
|
||||
{ .latitude = 37.7757368, .longitude = -122.4135302 },
|
||||
{ .latitude = 12.9810816, .longitude = 77.6368034 },
|
||||
{ .latitude = -13.15589555, .longitude = -74.2178961777998 },
|
||||
};
|
||||
|
||||
@interface AppDelegate () <NSApplicationDelegate, NSSharingServicePickerDelegate, NSMenuDelegate, MGLMapViewDelegate>
|
||||
|
||||
@property (weak) IBOutlet NSWindow *window;
|
||||
@property (weak) IBOutlet MGLMapView *mapView;
|
||||
@property (weak) IBOutlet NSMenu *mapViewContextMenu;
|
||||
|
||||
@property (weak) IBOutlet NSWindow *preferencesWindow;
|
||||
@interface AppDelegate ()
|
||||
|
||||
@end
|
||||
|
||||
@implementation AppDelegate {
|
||||
NSPoint _mouseLocationForMapViewContextMenu;
|
||||
NSUInteger _droppedPinCounter;
|
||||
NSNumberFormatter *_spellOutNumberFormatter;
|
||||
|
||||
BOOL _showsToolTipsOnDroppedPins;
|
||||
BOOL _randomizesCursorsOnDroppedPins;
|
||||
BOOL _isTouringWorld;
|
||||
}
|
||||
@implementation AppDelegate
|
||||
|
||||
#pragma mark Lifecycle
|
||||
|
||||
@@ -85,16 +63,6 @@ static const CLLocationCoordinate2D WorldTourDestinations[] = {
|
||||
[alert runModal];
|
||||
[self showPreferences:nil];
|
||||
}
|
||||
|
||||
NSURL *savedURL = [[NSUserDefaults standardUserDefaults] URLForKey:@"MBXCurrentStyleURL"];
|
||||
if (savedURL) {
|
||||
self.mapView.styleURL = savedURL;
|
||||
}
|
||||
|
||||
_spellOutNumberFormatter = [[NSNumberFormatter alloc] init];
|
||||
|
||||
NSPressGestureRecognizer *pressGestureRecognizer = [[NSPressGestureRecognizer alloc] initWithTarget:self action:@selector(handlePressGesture:)];
|
||||
[self.mapView addGestureRecognizer:pressGestureRecognizer];
|
||||
}
|
||||
|
||||
- (void)applicationWillTerminate:(NSNotification *)notification {
|
||||
@@ -106,13 +74,20 @@ static const CLLocationCoordinate2D WorldTourDestinations[] = {
|
||||
NSString *accessToken = [userDefaults stringForKey:MGLMapboxAccessTokenDefaultsKey];
|
||||
if (![accessToken isEqualToString:[MGLAccountManager accessToken]]) {
|
||||
[MGLAccountManager setAccessToken:accessToken];
|
||||
[self reload:self];
|
||||
[self.mainDocument reload:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (MapDocument *)mainDocument {
|
||||
NSDocument *mainDocument = [NSApp mainWindow].windowController.document;
|
||||
return [mainDocument isKindOfClass:[MapDocument class]] ? (MapDocument *)mainDocument : nil;
|
||||
}
|
||||
|
||||
#pragma mark Services
|
||||
|
||||
- (void)handleGetURLEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent {
|
||||
[[NSDocumentController sharedDocumentController] newDocument:self];
|
||||
|
||||
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:@"&"]) {
|
||||
@@ -126,220 +101,22 @@ static const CLLocationCoordinate2D WorldTourDestinations[] = {
|
||||
if (centerString) {
|
||||
NSArray *coordinateValues = [centerString componentsSeparatedByString:@","];
|
||||
if (coordinateValues.count == 2) {
|
||||
self.mapView.centerCoordinate = CLLocationCoordinate2DMake([coordinateValues[0] doubleValue],
|
||||
[coordinateValues[1] doubleValue]);
|
||||
self.mainDocument.mapView.centerCoordinate = CLLocationCoordinate2DMake([coordinateValues[0] doubleValue],
|
||||
[coordinateValues[1] doubleValue]);
|
||||
}
|
||||
}
|
||||
|
||||
NSString *zoomLevelString = params[@"zoom"];
|
||||
if (zoomLevelString.length) {
|
||||
self.mapView.zoomLevel = zoomLevelString.doubleValue;
|
||||
self.mainDocument.mapView.zoomLevel = zoomLevelString.doubleValue;
|
||||
}
|
||||
|
||||
NSString *directionString = params[@"bearing"];
|
||||
if (directionString.length) {
|
||||
self.mapView.direction = directionString.doubleValue;
|
||||
self.mainDocument.mapView.direction = directionString.doubleValue;
|
||||
}
|
||||
}
|
||||
|
||||
- (IBAction)showShareMenu:(id)sender {
|
||||
NSSharingServicePicker *picker = [[NSSharingServicePicker alloc] initWithItems:@[self.shareURL]];
|
||||
picker.delegate = self;
|
||||
[picker showRelativeToRect:[sender bounds] ofView:sender preferredEdge:NSMinYEdge];
|
||||
}
|
||||
|
||||
- (NSURL *)shareURL {
|
||||
NSArray *components = self.mapView.styleURL.pathComponents;
|
||||
CLLocationCoordinate2D centerCoordinate = self.mapView.centerCoordinate;
|
||||
return [NSURL URLWithString:
|
||||
[NSString stringWithFormat:@"https://api.mapbox.com/styles/v1/%@/%@.html?access_token=%@#%.2f/%.5f/%.5f/%.f",
|
||||
components[1], components[2], [MGLAccountManager accessToken],
|
||||
self.mapView.zoomLevel, centerCoordinate.latitude, centerCoordinate.longitude, self.mapView.direction]];
|
||||
}
|
||||
|
||||
#pragma mark View methods
|
||||
|
||||
- (IBAction)setStyle:(id)sender {
|
||||
NSInteger tag;
|
||||
if ([sender isKindOfClass:[NSMenuItem class]]) {
|
||||
tag = [sender tag];
|
||||
} else if ([sender isKindOfClass:[NSPopUpButton class]]) {
|
||||
tag = [sender selectedTag];
|
||||
}
|
||||
NSURL *styleURL;
|
||||
switch (tag) {
|
||||
case 1:
|
||||
styleURL = [MGLStyle streetsStyleURL];
|
||||
break;
|
||||
case 2:
|
||||
styleURL = [MGLStyle emeraldStyleURL];
|
||||
break;
|
||||
case 3:
|
||||
styleURL = [MGLStyle lightStyleURL];
|
||||
break;
|
||||
case 4:
|
||||
styleURL = [MGLStyle darkStyleURL];
|
||||
break;
|
||||
case 5:
|
||||
styleURL = [MGLStyle satelliteStyleURL];
|
||||
break;
|
||||
case 6:
|
||||
styleURL = [MGLStyle hybridStyleURL];
|
||||
break;
|
||||
default:
|
||||
NSAssert(NO, @"Cannot set style from control with tag %li", (long)tag);
|
||||
break;
|
||||
}
|
||||
self.mapView.styleURL = styleURL;
|
||||
[[NSUserDefaults standardUserDefaults] setURL:self.mapView.styleURL forKey:@"MBXCurrentStyleURL"];
|
||||
[self.window.toolbar validateVisibleItems];
|
||||
}
|
||||
|
||||
- (IBAction)chooseCustomStyle:(id)sender {
|
||||
NSAlert *alert = [[NSAlert alloc] init];
|
||||
alert.messageText = @"Apply custom style";
|
||||
alert.informativeText = @"Enter the URL to a JSON file that conforms to the Mapbox GL style specification, such as a style designed in Mapbox Studio:";
|
||||
NSTextField *textField = [[NSTextField alloc] initWithFrame:NSZeroRect];
|
||||
[textField sizeToFit];
|
||||
NSRect textFieldFrame = textField.frame;
|
||||
textFieldFrame.size.width = 300;
|
||||
textField.frame = textFieldFrame;
|
||||
NSURL *savedURL = [[NSUserDefaults standardUserDefaults] URLForKey:@"MBXCustomStyleURL"];
|
||||
if (savedURL) {
|
||||
textField.stringValue = savedURL.absoluteString;
|
||||
}
|
||||
alert.accessoryView = textField;
|
||||
[alert addButtonWithTitle:@"Apply"];
|
||||
[alert addButtonWithTitle:@"Cancel"];
|
||||
if ([alert runModal] == NSAlertFirstButtonReturn) {
|
||||
self.mapView.styleURL = [NSURL URLWithString:textField.stringValue];
|
||||
[[NSUserDefaults standardUserDefaults] setURL:self.mapView.styleURL forKey:@"MBXCustomStyleURL"];
|
||||
[[NSUserDefaults standardUserDefaults] setURL:self.mapView.styleURL forKey:@"MBXCurrentStyleURL"];
|
||||
[self.window.toolbar validateVisibleItems];
|
||||
}
|
||||
}
|
||||
|
||||
- (IBAction)zoomIn:(id)sender {
|
||||
[self.mapView setZoomLevel:self.mapView.zoomLevel + 1 animated:YES];
|
||||
}
|
||||
|
||||
- (IBAction)zoomOut:(id)sender {
|
||||
[self.mapView setZoomLevel:self.mapView.zoomLevel - 1 animated:YES];
|
||||
}
|
||||
|
||||
- (IBAction)snapToNorth:(id)sender {
|
||||
[self.mapView setDirection:0 animated:YES];
|
||||
}
|
||||
|
||||
- (IBAction)reload:(id)sender {
|
||||
[self.mapView reloadStyle:sender];
|
||||
}
|
||||
|
||||
#pragma mark Debug methods
|
||||
|
||||
- (IBAction)toggleTileBoundaries:(id)sender {
|
||||
self.mapView.debugMask ^= MGLMapDebugTileBoundariesMask;
|
||||
}
|
||||
|
||||
- (IBAction)toggleTileInfo:(id)sender {
|
||||
self.mapView.debugMask ^= MGLMapDebugTileInfoMask;
|
||||
}
|
||||
|
||||
- (IBAction)toggleTileTimestamps:(id)sender {
|
||||
self.mapView.debugMask ^= MGLMapDebugTimestampsMask;
|
||||
}
|
||||
|
||||
- (IBAction)toggleCollisionBoxes:(id)sender {
|
||||
self.mapView.debugMask ^= MGLMapDebugCollisionBoxesMask;
|
||||
}
|
||||
|
||||
- (IBAction)toggleShowsToolTipsOnDroppedPins:(id)sender {
|
||||
_showsToolTipsOnDroppedPins = !_showsToolTipsOnDroppedPins;
|
||||
}
|
||||
|
||||
- (IBAction)toggleRandomizesCursorsOnDroppedPins:(id)sender {
|
||||
_randomizesCursorsOnDroppedPins = !_randomizesCursorsOnDroppedPins;
|
||||
}
|
||||
|
||||
- (IBAction)dropManyPins:(id)sender {
|
||||
[self.mapView removeAnnotations:self.mapView.annotations];
|
||||
|
||||
NSRect bounds = self.mapView.bounds;
|
||||
NSMutableArray *annotations = [NSMutableArray array];
|
||||
for (CGFloat x = NSMinX(bounds); x < NSMaxX(bounds); x += arc4random_uniform(50)) {
|
||||
for (CGFloat y = NSMaxY(bounds); y >= NSMinY(bounds); y -= arc4random_uniform(100)) {
|
||||
[annotations addObject:[self pinAtPoint:NSMakePoint(x, y)]];
|
||||
}
|
||||
}
|
||||
|
||||
[NSTimer scheduledTimerWithTimeInterval:1/60
|
||||
target:self
|
||||
selector:@selector(dropOneOfManyPins:)
|
||||
userInfo:annotations
|
||||
repeats:YES];
|
||||
}
|
||||
|
||||
- (void)dropOneOfManyPins:(NSTimer *)timer {
|
||||
NSMutableArray *annotations = timer.userInfo;
|
||||
NSUInteger numberOfAnnotationsToAdd = 50;
|
||||
if (annotations.count < numberOfAnnotationsToAdd) {
|
||||
numberOfAnnotationsToAdd = annotations.count;
|
||||
}
|
||||
NSArray *annotationsToAdd = [annotations subarrayWithRange:
|
||||
NSMakeRange(0, numberOfAnnotationsToAdd)];
|
||||
[self.mapView addAnnotations:annotationsToAdd];
|
||||
[annotations removeObjectsInRange:NSMakeRange(0, numberOfAnnotationsToAdd)];
|
||||
if (!annotations.count) {
|
||||
[timer invalidate];
|
||||
}
|
||||
}
|
||||
|
||||
- (IBAction)removeAllPins:(id)sender {
|
||||
[self.mapView removeAnnotations:self.mapView.annotations];
|
||||
}
|
||||
|
||||
- (IBAction)startWorldTour:(id)sender {
|
||||
_isTouringWorld = YES;
|
||||
|
||||
[self removeAllPins:sender];
|
||||
NSUInteger numberOfAnnotations = sizeof(WorldTourDestinations) / sizeof(WorldTourDestinations[0]);
|
||||
NSMutableArray *annotations = [NSMutableArray arrayWithCapacity:numberOfAnnotations];
|
||||
for (NSUInteger i = 0; i < numberOfAnnotations; i++) {
|
||||
MGLPointAnnotation *annotation = [[MGLPointAnnotation alloc] init];
|
||||
annotation.coordinate = WorldTourDestinations[i];
|
||||
[annotations addObject:annotation];
|
||||
}
|
||||
[self.mapView addAnnotations:annotations];
|
||||
[self continueWorldTourWithRemainingAnnotations:annotations];
|
||||
}
|
||||
|
||||
- (void)continueWorldTourWithRemainingAnnotations:(NS_MUTABLE_ARRAY_OF(MGLPointAnnotation *) *)annotations {
|
||||
MGLPointAnnotation *nextAnnotation = annotations.firstObject;
|
||||
if (!nextAnnotation || !_isTouringWorld) {
|
||||
_isTouringWorld = NO;
|
||||
return;
|
||||
}
|
||||
|
||||
[annotations removeObjectAtIndex:0];
|
||||
MGLMapCamera *camera = [MGLMapCamera cameraLookingAtCenterCoordinate:nextAnnotation.coordinate
|
||||
fromDistance:0
|
||||
pitch:arc4random_uniform(60)
|
||||
heading:arc4random_uniform(360)];
|
||||
__weak AppDelegate *weakSelf = self;
|
||||
[self.mapView flyToCamera:camera completionHandler:^{
|
||||
AppDelegate *strongSelf = weakSelf;
|
||||
[strongSelf performSelector:@selector(continueWorldTourWithRemainingAnnotations:)
|
||||
withObject:annotations
|
||||
afterDelay:2];
|
||||
}];
|
||||
}
|
||||
|
||||
- (IBAction)stopWorldTour:(id)sender {
|
||||
_isTouringWorld = NO;
|
||||
// Any programmatic viewpoint change cancels outstanding animations.
|
||||
self.mapView.camera = self.mapView.camera;
|
||||
}
|
||||
|
||||
#pragma mark Help methods
|
||||
|
||||
- (IBAction)showShortcuts:(id)sender {
|
||||
@@ -355,13 +132,6 @@ static const CLLocationCoordinate2D WorldTourDestinations[] = {
|
||||
[alert runModal];
|
||||
}
|
||||
|
||||
- (IBAction)giveFeedback:(id)sender {
|
||||
CLLocationCoordinate2D centerCoordinate = self.mapView.centerCoordinate;
|
||||
NSURL *feedbackURL = [NSURL URLWithString:[NSString stringWithFormat:@"https://www.mapbox.com/map-feedback/#/%.5f/%.5f/%.0f",
|
||||
centerCoordinate.longitude, centerCoordinate.latitude, round(self.mapView.zoomLevel)]];
|
||||
[[NSWorkspace sharedWorkspace] openURL:feedbackURL];
|
||||
}
|
||||
|
||||
- (IBAction)showPreferences:(id)sender {
|
||||
[self.preferencesWindow makeKeyAndOrderFront:sender];
|
||||
}
|
||||
@@ -370,296 +140,16 @@ static const CLLocationCoordinate2D WorldTourDestinations[] = {
|
||||
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"https://www.mapbox.com/studio/account/tokens/"]];
|
||||
}
|
||||
|
||||
#pragma mark Mouse events
|
||||
|
||||
- (void)handlePressGesture:(NSPressGestureRecognizer *)gestureRecognizer {
|
||||
if (gestureRecognizer.state == NSGestureRecognizerStateBegan) {
|
||||
NSPoint location = [gestureRecognizer locationInView:self.mapView];
|
||||
[self dropPinAtPoint:location];
|
||||
}
|
||||
}
|
||||
|
||||
- (IBAction)dropPin:(NSMenuItem *)sender {
|
||||
[self dropPinAtPoint:_mouseLocationForMapViewContextMenu];
|
||||
}
|
||||
|
||||
- (void)dropPinAtPoint:(NSPoint)point {
|
||||
DroppedPinAnnotation *annotation = [self pinAtPoint:point];
|
||||
[self.mapView addAnnotation:annotation];
|
||||
[self.mapView selectAnnotation:annotation];
|
||||
}
|
||||
|
||||
- (DroppedPinAnnotation *)pinAtPoint:(NSPoint)point {
|
||||
DroppedPinAnnotation *annotation = [[DroppedPinAnnotation alloc] init];
|
||||
annotation.coordinate = [self.mapView convertPoint:point toCoordinateFromView:self.mapView];
|
||||
annotation.title = @"Dropped Pin";
|
||||
_spellOutNumberFormatter.numberStyle = NSNumberFormatterSpellOutStyle;
|
||||
if (_showsToolTipsOnDroppedPins) {
|
||||
NSString *formattedNumber = [_spellOutNumberFormatter stringFromNumber:@(++_droppedPinCounter)];
|
||||
annotation.toolTip = formattedNumber;
|
||||
}
|
||||
return annotation;
|
||||
}
|
||||
|
||||
- (IBAction)removePin:(NSMenuItem *)sender {
|
||||
[self removePinAtPoint:_mouseLocationForMapViewContextMenu];
|
||||
}
|
||||
|
||||
- (void)removePinAtPoint:(NSPoint)point {
|
||||
[self.mapView removeAnnotation:[self.mapView annotationAtPoint:point]];
|
||||
}
|
||||
|
||||
#pragma mark User interface validation
|
||||
|
||||
- (BOOL)validateMenuItem:(NSMenuItem *)menuItem {
|
||||
if (menuItem.action == @selector(setStyle:)) {
|
||||
NSURL *styleURL = self.mapView.styleURL;
|
||||
NSCellStateValue state;
|
||||
switch (menuItem.tag) {
|
||||
case 1:
|
||||
state = [styleURL isEqual:[MGLStyle streetsStyleURL]];
|
||||
break;
|
||||
case 2:
|
||||
state = [styleURL isEqual:[MGLStyle emeraldStyleURL]];
|
||||
break;
|
||||
case 3:
|
||||
state = [styleURL isEqual:[MGLStyle lightStyleURL]];
|
||||
break;
|
||||
case 4:
|
||||
state = [styleURL isEqual:[MGLStyle darkStyleURL]];
|
||||
break;
|
||||
case 5:
|
||||
state = [styleURL isEqual:[MGLStyle satelliteStyleURL]];
|
||||
break;
|
||||
case 6:
|
||||
state = [styleURL isEqual:[MGLStyle hybridStyleURL]];
|
||||
break;
|
||||
default:
|
||||
return NO;
|
||||
}
|
||||
menuItem.state = state;
|
||||
return YES;
|
||||
}
|
||||
if (menuItem.action == @selector(chooseCustomStyle:)) {
|
||||
menuItem.state = self.indexOfStyleInToolbarItem == NSNotFound;
|
||||
return YES;
|
||||
}
|
||||
if (menuItem.action == @selector(zoomIn:)) {
|
||||
return self.mapView.zoomLevel < self.mapView.maximumZoomLevel;
|
||||
}
|
||||
if (menuItem.action == @selector(zoomOut:)) {
|
||||
return self.mapView.zoomLevel > self.mapView.minimumZoomLevel;
|
||||
}
|
||||
if (menuItem.action == @selector(snapToNorth:)) {
|
||||
return self.mapView.direction != 0;
|
||||
}
|
||||
if (menuItem.action == @selector(reload:)) {
|
||||
return YES;
|
||||
}
|
||||
if (menuItem.action == @selector(dropPin:)) {
|
||||
BOOL isOverAnnotation = [self.mapView annotationAtPoint:_mouseLocationForMapViewContextMenu];
|
||||
menuItem.hidden = isOverAnnotation;
|
||||
return YES;
|
||||
}
|
||||
if (menuItem.action == @selector(removePin:)) {
|
||||
BOOL isOverAnnotation = [self.mapView annotationAtPoint:_mouseLocationForMapViewContextMenu];
|
||||
menuItem.hidden = !isOverAnnotation;
|
||||
return YES;
|
||||
}
|
||||
if (menuItem.action == @selector(toggleTileBoundaries:)) {
|
||||
BOOL isShown = self.mapView.debugMask & MGLMapDebugTileBoundariesMask;
|
||||
menuItem.title = isShown ? @"Hide Tile Boundaries" : @"Show Tile Boundaries";
|
||||
return YES;
|
||||
}
|
||||
if (menuItem.action == @selector(toggleTileInfo:)) {
|
||||
BOOL isShown = self.mapView.debugMask & MGLMapDebugTileInfoMask;
|
||||
menuItem.title = isShown ? @"Hide Tile Info" : @"Show Tile Info";
|
||||
return YES;
|
||||
}
|
||||
if (menuItem.action == @selector(toggleTileTimestamps:)) {
|
||||
BOOL isShown = self.mapView.debugMask & MGLMapDebugTimestampsMask;
|
||||
menuItem.title = isShown ? @"Hide Tile Timestamps" : @"Show Tile Timestamps";
|
||||
return YES;
|
||||
}
|
||||
if (menuItem.action == @selector(toggleCollisionBoxes:)) {
|
||||
BOOL isShown = self.mapView.debugMask & MGLMapDebugCollisionBoxesMask;
|
||||
menuItem.title = isShown ? @"Hide Collision Boxes" : @"Show Collision Boxes";
|
||||
return YES;
|
||||
}
|
||||
if (menuItem.action == @selector(toggleShowsToolTipsOnDroppedPins:)) {
|
||||
BOOL isShown = _showsToolTipsOnDroppedPins;
|
||||
menuItem.title = isShown ? @"Hide Tooltips on Dropped Pins" : @"Show Tooltips on Dropped Pins";
|
||||
return YES;
|
||||
}
|
||||
if (menuItem.action == @selector(toggleRandomizesCursorsOnDroppedPins:)) {
|
||||
BOOL isRandom = _randomizesCursorsOnDroppedPins;
|
||||
menuItem.title = isRandom ? @"Use Default Cursor for Dropped Pins" : @"Use Random Cursors for Dropped Pins";
|
||||
return _showsToolTipsOnDroppedPins;
|
||||
}
|
||||
if (menuItem.action == @selector(dropManyPins:)) {
|
||||
return YES;
|
||||
}
|
||||
if (menuItem.action == @selector(removeAllPins:)) {
|
||||
return self.mapView.annotations.count;
|
||||
}
|
||||
if (menuItem.action == @selector(startWorldTour:)) {
|
||||
return !_isTouringWorld;
|
||||
}
|
||||
if (menuItem.action == @selector(stopWorldTour:)) {
|
||||
return _isTouringWorld;
|
||||
}
|
||||
if (menuItem.action == @selector(showShortcuts:)) {
|
||||
return YES;
|
||||
}
|
||||
if (menuItem.action == @selector(giveFeedback:)) {
|
||||
return YES;
|
||||
}
|
||||
if (menuItem.action == @selector(showPreferences:)) {
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (NSUInteger)indexOfStyleInToolbarItem {
|
||||
if (![MGLAccountManager accessToken]) {
|
||||
return NSNotFound;
|
||||
}
|
||||
|
||||
NSArray *styleURLs = @[
|
||||
[MGLStyle streetsStyleURL],
|
||||
[MGLStyle emeraldStyleURL],
|
||||
[MGLStyle lightStyleURL],
|
||||
[MGLStyle darkStyleURL],
|
||||
[MGLStyle satelliteStyleURL],
|
||||
[MGLStyle hybridStyleURL],
|
||||
];
|
||||
return [styleURLs indexOfObject:self.mapView.styleURL];
|
||||
}
|
||||
|
||||
- (BOOL)validateToolbarItem:(NSToolbarItem *)toolbarItem {
|
||||
if (!self.mapView) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
if (toolbarItem.action == @selector(showShareMenu:)) {
|
||||
[(NSButton *)toolbarItem.view sendActionOn:NSLeftMouseDownMask];
|
||||
if (![MGLAccountManager accessToken]) {
|
||||
return NO;
|
||||
}
|
||||
NSURL *styleURL = self.mapView.styleURL;
|
||||
return ([styleURL.scheme isEqualToString:@"mapbox"]
|
||||
&& [styleURL.pathComponents.firstObject isEqualToString:@"styles"]);
|
||||
}
|
||||
if (toolbarItem.action == @selector(setStyle:)) {
|
||||
NSPopUpButton *popUpButton = (NSPopUpButton *)toolbarItem.view;
|
||||
NSUInteger index = self.indexOfStyleInToolbarItem;
|
||||
if (index == NSNotFound) {
|
||||
[popUpButton addItemWithTitle:@"Custom"];
|
||||
index = [popUpButton numberOfItems] - 1;
|
||||
}
|
||||
[popUpButton selectItemAtIndex:index];
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
#pragma mark NSApplicationDelegate methods
|
||||
|
||||
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender {
|
||||
return YES;
|
||||
}
|
||||
|
||||
#pragma mark NSSharingServicePickerDelegate methods
|
||||
|
||||
- (NS_ARRAY_OF(NSSharingService *) *)sharingServicePicker:(NSSharingServicePicker *)sharingServicePicker sharingServicesForItems:(NSArray *)items proposedSharingServices:(NS_ARRAY_OF(NSSharingService *) *)proposedServices {
|
||||
NSURL *shareURL = self.shareURL;
|
||||
NSURL *browserURL = [[NSWorkspace sharedWorkspace] URLForApplicationToOpenURL:shareURL];
|
||||
NSImage *browserIcon = [[NSWorkspace sharedWorkspace] iconForFile:browserURL.path];
|
||||
NSString *browserName = [[NSFileManager defaultManager] displayNameAtPath:browserURL.path];
|
||||
NSString *browserServiceName = [NSString stringWithFormat:@"Open in %@", browserName];
|
||||
|
||||
NSSharingService *browserService = [[NSSharingService alloc] initWithTitle:browserServiceName
|
||||
image:browserIcon
|
||||
alternateImage:nil
|
||||
handler:^{
|
||||
[[NSWorkspace sharedWorkspace] openURL:self.shareURL];
|
||||
}];
|
||||
|
||||
NSMutableArray *sharingServices = [proposedServices mutableCopy];
|
||||
[sharingServices insertObject:browserService atIndex:0];
|
||||
return sharingServices;
|
||||
}
|
||||
|
||||
#pragma mark NSMenuDelegate methods
|
||||
|
||||
- (void)menuWillOpen:(NSMenu *)menu {
|
||||
if (menu == self.mapViewContextMenu) {
|
||||
_mouseLocationForMapViewContextMenu = [self.window.contentView convertPoint:self.window.mouseLocationOutsideOfEventStream
|
||||
toView:self.mapView];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark MGLMapViewDelegate methods
|
||||
|
||||
- (BOOL)mapView:(MGLMapView *)mapView annotationCanShowCallout:(id <MGLAnnotation>)annotation {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (MGLAnnotationImage *)mapView:(MGLMapView *)mapView imageForAnnotation:(id <MGLAnnotation>)annotation {
|
||||
MGLAnnotationImage *annotationImage = [self.mapView dequeueReusableAnnotationImageWithIdentifier:MGLDroppedPinAnnotationImageIdentifier];
|
||||
if (!annotationImage) {
|
||||
NSString *imagePath = [[NSBundle bundleForClass:[MGLMapView class]]
|
||||
pathForResource:@"default_marker" ofType:@"pdf"];
|
||||
NSImage *image = [[NSImage alloc] initWithContentsOfFile:imagePath];
|
||||
NSRect alignmentRect = image.alignmentRect;
|
||||
alignmentRect.origin.y = NSMidY(alignmentRect);
|
||||
alignmentRect.size.height /= 2;
|
||||
image.alignmentRect = alignmentRect;
|
||||
annotationImage = [MGLAnnotationImage annotationImageWithImage:image
|
||||
reuseIdentifier:MGLDroppedPinAnnotationImageIdentifier];
|
||||
}
|
||||
if (_randomizesCursorsOnDroppedPins) {
|
||||
NSArray *cursors = @[
|
||||
[NSCursor IBeamCursor],
|
||||
[NSCursor crosshairCursor],
|
||||
[NSCursor pointingHandCursor],
|
||||
[NSCursor disappearingItemCursor],
|
||||
[NSCursor IBeamCursorForVerticalLayout],
|
||||
[NSCursor operationNotAllowedCursor],
|
||||
[NSCursor dragLinkCursor],
|
||||
[NSCursor dragCopyCursor],
|
||||
[NSCursor contextualMenuCursor],
|
||||
];
|
||||
annotationImage.cursor = cursors[arc4random_uniform(cursors.count)];
|
||||
} else {
|
||||
annotationImage.cursor = nil;
|
||||
}
|
||||
return annotationImage;
|
||||
}
|
||||
|
||||
- (void)mapView:(MGLMapView *)mapView didSelectAnnotation:(id <MGLAnnotation>)annotation {
|
||||
if ([annotation isKindOfClass:[DroppedPinAnnotation class]]) {
|
||||
DroppedPinAnnotation *droppedPin = annotation;
|
||||
[droppedPin resume];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)mapView:(MGLMapView *)mapView didDeselectAnnotation:(id <MGLAnnotation>)annotation {
|
||||
if ([annotation isKindOfClass:[DroppedPinAnnotation class]]) {
|
||||
DroppedPinAnnotation *droppedPin = annotation;
|
||||
[droppedPin pause];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface ValidatedToolbarItem : NSToolbarItem
|
||||
|
||||
@end
|
||||
|
||||
@implementation ValidatedToolbarItem
|
||||
|
||||
- (void)validate {
|
||||
[(AppDelegate *)self.toolbar.delegate validateToolbarItem:self];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -4,6 +4,21 @@
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>English</string>
|
||||
<key>CFBundleDocumentTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>mbx</string>
|
||||
</array>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Mapbox GL Map</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>MapDocument</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${EXECUTABLE_NAME}</string>
|
||||
<key>CFBundleGetInfoString</key>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="9531" systemVersion="15B42" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="9531" systemVersion="15C50" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="9531"/>
|
||||
</dependencies>
|
||||
@@ -17,10 +17,7 @@
|
||||
</customObject>
|
||||
<customObject id="Voe-Tx-rLC" customClass="AppDelegate">
|
||||
<connections>
|
||||
<outlet property="mapView" destination="v1Z-oA-oHU" id="70S-xO-QIP"/>
|
||||
<outlet property="mapViewContextMenu" destination="6rZ-M1-uTn" id="wl2-03-9Z8"/>
|
||||
<outlet property="preferencesWindow" destination="UWc-yQ-qda" id="Ota-aT-Mz2"/>
|
||||
<outlet property="window" destination="QvC-M9-y7g" id="gIp-Ho-8D9"/>
|
||||
</connections>
|
||||
</customObject>
|
||||
<menu title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
|
||||
@@ -544,100 +541,6 @@
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
<window title="Mapbox GL" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" frameAutosaveName="Mapbox" animationBehavior="default" id="QvC-M9-y7g">
|
||||
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES" fullSizeContentView="YES"/>
|
||||
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
||||
<rect key="contentRect" x="388" y="211" width="512" height="360"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1280" height="777"/>
|
||||
<view key="contentView" id="EiT-Mj-1SZ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="512" height="360"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<customView translatesAutoresizingMaskIntoConstraints="NO" id="v1Z-oA-oHU" customClass="MGLMapView">
|
||||
<rect key="frame" x="0.0" y="0.0" width="512" height="360"/>
|
||||
<connections>
|
||||
<outlet property="delegate" destination="Voe-Tx-rLC" id="qJ7-fL-iw1"/>
|
||||
<outlet property="menu" destination="6rZ-M1-uTn" id="4sB-UN-zuc"/>
|
||||
</connections>
|
||||
</customView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="v1Z-oA-oHU" firstAttribute="leading" secondItem="EiT-Mj-1SZ" secondAttribute="leading" id="Z3O-xb-fah"/>
|
||||
<constraint firstAttribute="trailing" secondItem="v1Z-oA-oHU" secondAttribute="trailing" id="oYw-bk-P2l"/>
|
||||
<constraint firstItem="v1Z-oA-oHU" firstAttribute="top" secondItem="EiT-Mj-1SZ" secondAttribute="top" id="r4Z-4m-bgA"/>
|
||||
<constraint firstAttribute="bottom" secondItem="v1Z-oA-oHU" secondAttribute="bottom" id="w3h-H1-baQ"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<toolbar key="toolbar" implicitIdentifier="A3AC6577-4712-4628-813D-113498171A84" allowsUserCustomization="NO" displayMode="iconOnly" sizeMode="regular" id="8nY-8B-Om5">
|
||||
<allowedToolbarItems>
|
||||
<toolbarItem implicitItemIdentifier="NSToolbarSpaceItem" id="zzK-Oz-JV8"/>
|
||||
<toolbarItem implicitItemIdentifier="NSToolbarFlexibleSpaceItem" id="SMs-8a-x2h"/>
|
||||
<toolbarItem implicitItemIdentifier="2CB58C0A-7B95-4233-8DD3-F94BFE7D3061" label="Share" paletteLabel="Share" image="NSShareTemplate" id="HtT-hs-jWY" customClass="ValidatedToolbarItem">
|
||||
<nil key="toolTip"/>
|
||||
<size key="minSize" width="40" height="32"/>
|
||||
<size key="maxSize" width="40" height="32"/>
|
||||
<button key="view" verticalHuggingPriority="750" id="l0U-ql-5hn">
|
||||
<rect key="frame" x="0.0" y="14" width="48" height="32"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<buttonCell key="cell" type="roundTextured" bezelStyle="texturedRounded" image="NSShareTemplate" imagePosition="only" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="vxZ-hj-hV2">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
</button>
|
||||
<connections>
|
||||
<action selector="showShareMenu:" target="-1" id="vDU-9Y-DkS"/>
|
||||
</connections>
|
||||
</toolbarItem>
|
||||
<toolbarItem implicitItemIdentifier="BA3542AF-D63A-4893-9CC7-8F67EF2E82B0" label="Style" paletteLabel="Style" id="ge5-0H-wyr" customClass="ValidatedToolbarItem">
|
||||
<nil key="toolTip"/>
|
||||
<size key="minSize" width="100" height="26"/>
|
||||
<size key="maxSize" width="100" height="26"/>
|
||||
<popUpButton key="view" verticalHuggingPriority="750" id="5Cd-Lw-cWm">
|
||||
<rect key="frame" x="0.0" y="14" width="100" height="26"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<popUpButtonCell key="cell" type="roundTextured" title="Streets" bezelStyle="texturedRounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="border" tag="1" imageScaling="proportionallyDown" inset="2" selectedItem="ZnV-nK-Bri" id="esr-2z-V4Y">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<menu key="menu" id="uxq-U6-EQW">
|
||||
<items>
|
||||
<menuItem title="Streets" state="on" tag="1" id="ZnV-nK-Bri"/>
|
||||
<menuItem title="Emerald" tag="2" id="LLa-jX-Dox"/>
|
||||
<menuItem title="Light" tag="3" id="jDf-NE-hMn"/>
|
||||
<menuItem title="Dark" tag="4" id="etJ-y7-M5R">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
</menuItem>
|
||||
<menuItem title="Satellite" tag="5" id="xYa-J8-bLe">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
</menuItem>
|
||||
<menuItem title="Hybrid" tag="6" id="1Uq-Nl-FSZ">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<connections>
|
||||
<action selector="setStyle:" target="-1" id="qb2-E8-7pE"/>
|
||||
</connections>
|
||||
</toolbarItem>
|
||||
</allowedToolbarItems>
|
||||
<defaultToolbarItems>
|
||||
<toolbarItem reference="HtT-hs-jWY"/>
|
||||
<toolbarItem reference="SMs-8a-x2h"/>
|
||||
<toolbarItem reference="ge5-0H-wyr"/>
|
||||
</defaultToolbarItems>
|
||||
<connections>
|
||||
<outlet property="delegate" destination="Voe-Tx-rLC" id="ciT-Nf-Dtm"/>
|
||||
</connections>
|
||||
</toolbar>
|
||||
<connections>
|
||||
<binding destination="Voe-Tx-rLC" name="title" keyPath="mapView.centerCoordinate" id="XFo-sp-PtR">
|
||||
<dictionary key="options">
|
||||
<string key="NSValueTransformerName">LocationCoordinate2DTransformer</string>
|
||||
</dictionary>
|
||||
</binding>
|
||||
</connections>
|
||||
</window>
|
||||
<window title="Preferences" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" frameAutosaveName="Preferences" animationBehavior="default" id="UWc-yQ-qda">
|
||||
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES"/>
|
||||
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
||||
@@ -700,29 +603,8 @@
|
||||
<point key="canvasLocation" x="754" y="210"/>
|
||||
</window>
|
||||
<userDefaultsController representsSharedInstance="YES" id="45S-yT-WUN"/>
|
||||
<menu title="Map View" id="6rZ-M1-uTn">
|
||||
<items>
|
||||
<menuItem title="Drop Pin" id="fxQ-3P-E7l">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="dropPin:" target="Voe-Tx-rLC" id="eDR-iw-JUG"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Remove Pin" id="rgF-cY-qgP">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="removePin:" target="Voe-Tx-rLC" id="38n-9m-8Wp"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
<connections>
|
||||
<outlet property="delegate" destination="Voe-Tx-rLC" id="MsV-YU-f4X"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="820" y="254.5"/>
|
||||
</menu>
|
||||
</objects>
|
||||
<resources>
|
||||
<image name="NSFollowLinkFreestandingTemplate" width="14" height="14"/>
|
||||
<image name="NSShareTemplate" width="11" height="16"/>
|
||||
</resources>
|
||||
</document>
|
||||
|
||||
14
platform/osx/app/MapDocument.h
Normal file
14
platform/osx/app/MapDocument.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@class MGLMapView;
|
||||
|
||||
@interface MapDocument : NSDocument
|
||||
|
||||
@property (weak) IBOutlet MGLMapView *mapView;
|
||||
|
||||
- (IBAction)setStyle:(id)sender;
|
||||
- (IBAction)chooseCustomStyle:(id)sender;
|
||||
|
||||
- (IBAction)reload:(id)sender;
|
||||
|
||||
@end
|
||||
568
platform/osx/app/MapDocument.m
Normal file
568
platform/osx/app/MapDocument.m
Normal file
@@ -0,0 +1,568 @@
|
||||
#import "MapDocument.h"
|
||||
|
||||
#import "DroppedPinAnnotation.h"
|
||||
|
||||
#import <Mapbox/Mapbox.h>
|
||||
|
||||
static NSString * const MGLDroppedPinAnnotationImageIdentifier = @"dropped";
|
||||
|
||||
static const CLLocationCoordinate2D WorldTourDestinations[] = {
|
||||
{ .latitude = 38.9131982, .longitude = -77.0325453144239 },
|
||||
{ .latitude = 37.7757368, .longitude = -122.4135302 },
|
||||
{ .latitude = 12.9810816, .longitude = 77.6368034 },
|
||||
{ .latitude = -13.15589555, .longitude = -74.2178961777998 },
|
||||
};
|
||||
|
||||
@interface MapDocument () <NSSharingServicePickerDelegate, NSMenuDelegate, MGLMapViewDelegate>
|
||||
|
||||
@property (weak) IBOutlet NSMenu *mapViewContextMenu;
|
||||
|
||||
@end
|
||||
|
||||
@implementation MapDocument {
|
||||
NSPoint _mouseLocationForMapViewContextMenu;
|
||||
NSUInteger _droppedPinCounter;
|
||||
NSNumberFormatter *_spellOutNumberFormatter;
|
||||
|
||||
BOOL _showsToolTipsOnDroppedPins;
|
||||
BOOL _randomizesCursorsOnDroppedPins;
|
||||
BOOL _isTouringWorld;
|
||||
}
|
||||
|
||||
#pragma mark Lifecycle
|
||||
|
||||
- (NSString *)windowNibName {
|
||||
return @"MapDocument";
|
||||
}
|
||||
|
||||
- (void)windowControllerDidLoadNib:(NSWindowController *)aController {
|
||||
[super windowControllerDidLoadNib:aController];
|
||||
|
||||
NSURL *savedURL = [[NSUserDefaults standardUserDefaults] URLForKey:@"MBXCurrentStyleURL"];
|
||||
if (savedURL) {
|
||||
self.mapView.styleURL = savedURL;
|
||||
}
|
||||
|
||||
_spellOutNumberFormatter = [[NSNumberFormatter alloc] init];
|
||||
|
||||
NSPressGestureRecognizer *pressGestureRecognizer = [[NSPressGestureRecognizer alloc] initWithTarget:self action:@selector(handlePressGesture:)];
|
||||
[self.mapView addGestureRecognizer:pressGestureRecognizer];
|
||||
}
|
||||
|
||||
- (NSData *)dataOfType:(NSString *)typeName error:(NSError **)outError {
|
||||
// Insert code here to write your document to data of the specified type. If outError != NULL, ensure that you create and set an appropriate error when returning nil.
|
||||
// You can also choose to override -fileWrapperOfType:error:, -writeToURL:ofType:error:, or -writeToURL:ofType:forSaveOperation:originalContentsURL:error: instead.
|
||||
if (outError) {
|
||||
*outError = [NSError errorWithDomain:NSOSStatusErrorDomain code:unimpErr userInfo:nil];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (BOOL)readFromData:(NSData *)data ofType:(NSString *)typeName error:(NSError **)outError {
|
||||
// Insert code here to read your document from the given data of the specified type. If outError != NULL, ensure that you create and set an appropriate error when returning NO.
|
||||
// You can also choose to override -readFromFileWrapper:ofType:error: or -readFromURL:ofType:error: instead.
|
||||
// If you override either of these, you should also override -isEntireFileLoaded to return NO if the contents are lazily loaded.
|
||||
if (outError) {
|
||||
*outError = [NSError errorWithDomain:NSOSStatusErrorDomain code:unimpErr userInfo:nil];
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
+ (BOOL)autosavesInPlace {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSWindow *)window {
|
||||
return self.windowControllers.firstObject.window;
|
||||
}
|
||||
|
||||
#pragma mark Services
|
||||
|
||||
- (IBAction)showShareMenu:(id)sender {
|
||||
NSSharingServicePicker *picker = [[NSSharingServicePicker alloc] initWithItems:@[self.shareURL]];
|
||||
picker.delegate = self;
|
||||
[picker showRelativeToRect:[sender bounds] ofView:sender preferredEdge:NSMinYEdge];
|
||||
}
|
||||
|
||||
- (NSURL *)shareURL {
|
||||
NSArray *components = self.mapView.styleURL.pathComponents;
|
||||
CLLocationCoordinate2D centerCoordinate = self.mapView.centerCoordinate;
|
||||
return [NSURL URLWithString:
|
||||
[NSString stringWithFormat:@"https://api.mapbox.com/styles/v1/%@/%@.html?access_token=%@#%.2f/%.5f/%.5f/%.f",
|
||||
components[1], components[2], [MGLAccountManager accessToken],
|
||||
self.mapView.zoomLevel, centerCoordinate.latitude, centerCoordinate.longitude, self.mapView.direction]];
|
||||
}
|
||||
|
||||
#pragma mark View methods
|
||||
|
||||
- (IBAction)setStyle:(id)sender {
|
||||
NSInteger tag;
|
||||
if ([sender isKindOfClass:[NSMenuItem class]]) {
|
||||
tag = [sender tag];
|
||||
} else if ([sender isKindOfClass:[NSPopUpButton class]]) {
|
||||
tag = [sender selectedTag];
|
||||
}
|
||||
NSURL *styleURL;
|
||||
switch (tag) {
|
||||
case 1:
|
||||
styleURL = [MGLStyle streetsStyleURL];
|
||||
break;
|
||||
case 2:
|
||||
styleURL = [MGLStyle emeraldStyleURL];
|
||||
break;
|
||||
case 3:
|
||||
styleURL = [MGLStyle lightStyleURL];
|
||||
break;
|
||||
case 4:
|
||||
styleURL = [MGLStyle darkStyleURL];
|
||||
break;
|
||||
case 5:
|
||||
styleURL = [MGLStyle satelliteStyleURL];
|
||||
break;
|
||||
case 6:
|
||||
styleURL = [MGLStyle hybridStyleURL];
|
||||
break;
|
||||
default:
|
||||
NSAssert(NO, @"Cannot set style from control with tag %li", (long)tag);
|
||||
break;
|
||||
}
|
||||
self.mapView.styleURL = styleURL;
|
||||
[[NSUserDefaults standardUserDefaults] setURL:self.mapView.styleURL forKey:@"MBXCurrentStyleURL"];
|
||||
[self.window.toolbar validateVisibleItems];
|
||||
}
|
||||
|
||||
- (IBAction)chooseCustomStyle:(id)sender {
|
||||
NSAlert *alert = [[NSAlert alloc] init];
|
||||
alert.messageText = @"Apply custom style";
|
||||
alert.informativeText = @"Enter the URL to a JSON file that conforms to the Mapbox GL style specification, such as a style designed in Mapbox Studio:";
|
||||
NSTextField *textField = [[NSTextField alloc] initWithFrame:NSZeroRect];
|
||||
[textField sizeToFit];
|
||||
NSRect textFieldFrame = textField.frame;
|
||||
textFieldFrame.size.width = 300;
|
||||
textField.frame = textFieldFrame;
|
||||
NSURL *savedURL = [[NSUserDefaults standardUserDefaults] URLForKey:@"MBXCustomStyleURL"];
|
||||
if (savedURL) {
|
||||
textField.stringValue = savedURL.absoluteString;
|
||||
}
|
||||
alert.accessoryView = textField;
|
||||
[alert addButtonWithTitle:@"Apply"];
|
||||
[alert addButtonWithTitle:@"Cancel"];
|
||||
if ([alert runModal] == NSAlertFirstButtonReturn) {
|
||||
self.mapView.styleURL = [NSURL URLWithString:textField.stringValue];
|
||||
[[NSUserDefaults standardUserDefaults] setURL:self.mapView.styleURL forKey:@"MBXCustomStyleURL"];
|
||||
[[NSUserDefaults standardUserDefaults] setURL:self.mapView.styleURL forKey:@"MBXCurrentStyleURL"];
|
||||
[self.window.toolbar validateVisibleItems];
|
||||
}
|
||||
}
|
||||
|
||||
- (IBAction)zoomIn:(id)sender {
|
||||
[self.mapView setZoomLevel:self.mapView.zoomLevel + 1 animated:YES];
|
||||
}
|
||||
|
||||
- (IBAction)zoomOut:(id)sender {
|
||||
[self.mapView setZoomLevel:self.mapView.zoomLevel - 1 animated:YES];
|
||||
}
|
||||
|
||||
- (IBAction)snapToNorth:(id)sender {
|
||||
[self.mapView setDirection:0 animated:YES];
|
||||
}
|
||||
|
||||
- (IBAction)reload:(id)sender {
|
||||
[self.mapView reloadStyle:sender];
|
||||
}
|
||||
|
||||
#pragma mark Debug methods
|
||||
|
||||
- (IBAction)toggleTileBoundaries:(id)sender {
|
||||
self.mapView.debugMask ^= MGLMapDebugTileBoundariesMask;
|
||||
}
|
||||
|
||||
- (IBAction)toggleTileInfo:(id)sender {
|
||||
self.mapView.debugMask ^= MGLMapDebugTileInfoMask;
|
||||
}
|
||||
|
||||
- (IBAction)toggleTileTimestamps:(id)sender {
|
||||
self.mapView.debugMask ^= MGLMapDebugTimestampsMask;
|
||||
}
|
||||
|
||||
- (IBAction)toggleCollisionBoxes:(id)sender {
|
||||
self.mapView.debugMask ^= MGLMapDebugCollisionBoxesMask;
|
||||
}
|
||||
|
||||
- (IBAction)toggleShowsToolTipsOnDroppedPins:(id)sender {
|
||||
_showsToolTipsOnDroppedPins = !_showsToolTipsOnDroppedPins;
|
||||
}
|
||||
|
||||
- (IBAction)toggleRandomizesCursorsOnDroppedPins:(id)sender {
|
||||
_randomizesCursorsOnDroppedPins = !_randomizesCursorsOnDroppedPins;
|
||||
}
|
||||
|
||||
- (IBAction)dropManyPins:(id)sender {
|
||||
[self.mapView removeAnnotations:self.mapView.annotations];
|
||||
|
||||
NSRect bounds = self.mapView.bounds;
|
||||
NSMutableArray *annotations = [NSMutableArray array];
|
||||
for (CGFloat x = NSMinX(bounds); x < NSMaxX(bounds); x += arc4random_uniform(50)) {
|
||||
for (CGFloat y = NSMaxY(bounds); y >= NSMinY(bounds); y -= arc4random_uniform(100)) {
|
||||
[annotations addObject:[self pinAtPoint:NSMakePoint(x, y)]];
|
||||
}
|
||||
}
|
||||
|
||||
[NSTimer scheduledTimerWithTimeInterval:1/60
|
||||
target:self
|
||||
selector:@selector(dropOneOfManyPins:)
|
||||
userInfo:annotations
|
||||
repeats:YES];
|
||||
}
|
||||
|
||||
- (void)dropOneOfManyPins:(NSTimer *)timer {
|
||||
NSMutableArray *annotations = timer.userInfo;
|
||||
NSUInteger numberOfAnnotationsToAdd = 50;
|
||||
if (annotations.count < numberOfAnnotationsToAdd) {
|
||||
numberOfAnnotationsToAdd = annotations.count;
|
||||
}
|
||||
NSArray *annotationsToAdd = [annotations subarrayWithRange:
|
||||
NSMakeRange(0, numberOfAnnotationsToAdd)];
|
||||
[self.mapView addAnnotations:annotationsToAdd];
|
||||
[annotations removeObjectsInRange:NSMakeRange(0, numberOfAnnotationsToAdd)];
|
||||
if (!annotations.count) {
|
||||
[timer invalidate];
|
||||
}
|
||||
}
|
||||
|
||||
- (IBAction)removeAllPins:(id)sender {
|
||||
[self.mapView removeAnnotations:self.mapView.annotations];
|
||||
}
|
||||
|
||||
- (IBAction)startWorldTour:(id)sender {
|
||||
_isTouringWorld = YES;
|
||||
|
||||
[self removeAllPins:sender];
|
||||
NSUInteger numberOfAnnotations = sizeof(WorldTourDestinations) / sizeof(WorldTourDestinations[0]);
|
||||
NSMutableArray *annotations = [NSMutableArray arrayWithCapacity:numberOfAnnotations];
|
||||
for (NSUInteger i = 0; i < numberOfAnnotations; i++) {
|
||||
MGLPointAnnotation *annotation = [[MGLPointAnnotation alloc] init];
|
||||
annotation.coordinate = WorldTourDestinations[i];
|
||||
[annotations addObject:annotation];
|
||||
}
|
||||
[self.mapView addAnnotations:annotations];
|
||||
[self continueWorldTourWithRemainingAnnotations:annotations];
|
||||
}
|
||||
|
||||
- (void)continueWorldTourWithRemainingAnnotations:(NS_MUTABLE_ARRAY_OF(MGLPointAnnotation *) *)annotations {
|
||||
MGLPointAnnotation *nextAnnotation = annotations.firstObject;
|
||||
if (!nextAnnotation || !_isTouringWorld) {
|
||||
_isTouringWorld = NO;
|
||||
return;
|
||||
}
|
||||
|
||||
[annotations removeObjectAtIndex:0];
|
||||
MGLMapCamera *camera = [MGLMapCamera cameraLookingAtCenterCoordinate:nextAnnotation.coordinate
|
||||
fromDistance:0
|
||||
pitch:arc4random_uniform(60)
|
||||
heading:arc4random_uniform(360)];
|
||||
__weak MapDocument *weakSelf = self;
|
||||
[self.mapView flyToCamera:camera completionHandler:^{
|
||||
MapDocument *strongSelf = weakSelf;
|
||||
[strongSelf performSelector:@selector(continueWorldTourWithRemainingAnnotations:)
|
||||
withObject:annotations
|
||||
afterDelay:2];
|
||||
}];
|
||||
}
|
||||
|
||||
- (IBAction)stopWorldTour:(id)sender {
|
||||
_isTouringWorld = NO;
|
||||
// Any programmatic viewpoint change cancels outstanding animations.
|
||||
self.mapView.camera = self.mapView.camera;
|
||||
}
|
||||
|
||||
#pragma mark Help methods
|
||||
|
||||
- (IBAction)giveFeedback:(id)sender {
|
||||
CLLocationCoordinate2D centerCoordinate = self.mapView.centerCoordinate;
|
||||
NSURL *feedbackURL = [NSURL URLWithString:[NSString stringWithFormat:@"https://www.mapbox.com/map-feedback/#/%.5f/%.5f/%.0f",
|
||||
centerCoordinate.longitude, centerCoordinate.latitude, round(self.mapView.zoomLevel)]];
|
||||
[[NSWorkspace sharedWorkspace] openURL:feedbackURL];
|
||||
}
|
||||
|
||||
#pragma mark Mouse events
|
||||
|
||||
- (void)handlePressGesture:(NSPressGestureRecognizer *)gestureRecognizer {
|
||||
if (gestureRecognizer.state == NSGestureRecognizerStateBegan) {
|
||||
NSPoint location = [gestureRecognizer locationInView:self.mapView];
|
||||
[self dropPinAtPoint:location];
|
||||
}
|
||||
}
|
||||
|
||||
- (IBAction)dropPin:(NSMenuItem *)sender {
|
||||
[self dropPinAtPoint:_mouseLocationForMapViewContextMenu];
|
||||
}
|
||||
|
||||
- (void)dropPinAtPoint:(NSPoint)point {
|
||||
DroppedPinAnnotation *annotation = [self pinAtPoint:point];
|
||||
[self.mapView addAnnotation:annotation];
|
||||
[self.mapView selectAnnotation:annotation];
|
||||
}
|
||||
|
||||
- (DroppedPinAnnotation *)pinAtPoint:(NSPoint)point {
|
||||
DroppedPinAnnotation *annotation = [[DroppedPinAnnotation alloc] init];
|
||||
annotation.coordinate = [self.mapView convertPoint:point toCoordinateFromView:self.mapView];
|
||||
annotation.title = @"Dropped Pin";
|
||||
_spellOutNumberFormatter.numberStyle = NSNumberFormatterSpellOutStyle;
|
||||
if (_showsToolTipsOnDroppedPins) {
|
||||
NSString *formattedNumber = [_spellOutNumberFormatter stringFromNumber:@(++_droppedPinCounter)];
|
||||
annotation.toolTip = formattedNumber;
|
||||
}
|
||||
return annotation;
|
||||
}
|
||||
|
||||
- (IBAction)removePin:(NSMenuItem *)sender {
|
||||
[self removePinAtPoint:_mouseLocationForMapViewContextMenu];
|
||||
}
|
||||
|
||||
- (void)removePinAtPoint:(NSPoint)point {
|
||||
[self.mapView removeAnnotation:[self.mapView annotationAtPoint:point]];
|
||||
}
|
||||
|
||||
#pragma mark User interface validation
|
||||
|
||||
- (BOOL)validateMenuItem:(NSMenuItem *)menuItem {
|
||||
if (menuItem.action == @selector(setStyle:)) {
|
||||
NSURL *styleURL = self.mapView.styleURL;
|
||||
NSCellStateValue state;
|
||||
switch (menuItem.tag) {
|
||||
case 1:
|
||||
state = [styleURL isEqual:[MGLStyle streetsStyleURL]];
|
||||
break;
|
||||
case 2:
|
||||
state = [styleURL isEqual:[MGLStyle emeraldStyleURL]];
|
||||
break;
|
||||
case 3:
|
||||
state = [styleURL isEqual:[MGLStyle lightStyleURL]];
|
||||
break;
|
||||
case 4:
|
||||
state = [styleURL isEqual:[MGLStyle darkStyleURL]];
|
||||
break;
|
||||
case 5:
|
||||
state = [styleURL isEqual:[MGLStyle satelliteStyleURL]];
|
||||
break;
|
||||
case 6:
|
||||
state = [styleURL isEqual:[MGLStyle hybridStyleURL]];
|
||||
break;
|
||||
default:
|
||||
return NO;
|
||||
}
|
||||
menuItem.state = state;
|
||||
return YES;
|
||||
}
|
||||
if (menuItem.action == @selector(chooseCustomStyle:)) {
|
||||
menuItem.state = self.indexOfStyleInToolbarItem == NSNotFound;
|
||||
return YES;
|
||||
}
|
||||
if (menuItem.action == @selector(zoomIn:)) {
|
||||
return self.mapView.zoomLevel < self.mapView.maximumZoomLevel;
|
||||
}
|
||||
if (menuItem.action == @selector(zoomOut:)) {
|
||||
return self.mapView.zoomLevel > self.mapView.minimumZoomLevel;
|
||||
}
|
||||
if (menuItem.action == @selector(snapToNorth:)) {
|
||||
return self.mapView.direction != 0;
|
||||
}
|
||||
if (menuItem.action == @selector(reload:)) {
|
||||
return YES;
|
||||
}
|
||||
if (menuItem.action == @selector(dropPin:)) {
|
||||
BOOL isOverAnnotation = [self.mapView annotationAtPoint:_mouseLocationForMapViewContextMenu];
|
||||
menuItem.hidden = isOverAnnotation;
|
||||
return YES;
|
||||
}
|
||||
if (menuItem.action == @selector(removePin:)) {
|
||||
BOOL isOverAnnotation = [self.mapView annotationAtPoint:_mouseLocationForMapViewContextMenu];
|
||||
menuItem.hidden = !isOverAnnotation;
|
||||
return YES;
|
||||
}
|
||||
if (menuItem.action == @selector(toggleTileBoundaries:)) {
|
||||
BOOL isShown = self.mapView.debugMask & MGLMapDebugTileBoundariesMask;
|
||||
menuItem.title = isShown ? @"Hide Tile Boundaries" : @"Show Tile Boundaries";
|
||||
return YES;
|
||||
}
|
||||
if (menuItem.action == @selector(toggleTileInfo:)) {
|
||||
BOOL isShown = self.mapView.debugMask & MGLMapDebugTileInfoMask;
|
||||
menuItem.title = isShown ? @"Hide Tile Info" : @"Show Tile Info";
|
||||
return YES;
|
||||
}
|
||||
if (menuItem.action == @selector(toggleTileTimestamps:)) {
|
||||
BOOL isShown = self.mapView.debugMask & MGLMapDebugTimestampsMask;
|
||||
menuItem.title = isShown ? @"Hide Tile Timestamps" : @"Show Tile Timestamps";
|
||||
return YES;
|
||||
}
|
||||
if (menuItem.action == @selector(toggleCollisionBoxes:)) {
|
||||
BOOL isShown = self.mapView.debugMask & MGLMapDebugCollisionBoxesMask;
|
||||
menuItem.title = isShown ? @"Hide Collision Boxes" : @"Show Collision Boxes";
|
||||
return YES;
|
||||
}
|
||||
if (menuItem.action == @selector(toggleShowsToolTipsOnDroppedPins:)) {
|
||||
BOOL isShown = _showsToolTipsOnDroppedPins;
|
||||
menuItem.title = isShown ? @"Hide Tooltips on Dropped Pins" : @"Show Tooltips on Dropped Pins";
|
||||
return YES;
|
||||
}
|
||||
if (menuItem.action == @selector(toggleRandomizesCursorsOnDroppedPins:)) {
|
||||
BOOL isRandom = _randomizesCursorsOnDroppedPins;
|
||||
menuItem.title = isRandom ? @"Use Default Cursor for Dropped Pins" : @"Use Random Cursors for Dropped Pins";
|
||||
return _showsToolTipsOnDroppedPins;
|
||||
}
|
||||
if (menuItem.action == @selector(dropManyPins:)) {
|
||||
return YES;
|
||||
}
|
||||
if (menuItem.action == @selector(removeAllPins:)) {
|
||||
return self.mapView.annotations.count;
|
||||
}
|
||||
if (menuItem.action == @selector(startWorldTour:)) {
|
||||
return !_isTouringWorld;
|
||||
}
|
||||
if (menuItem.action == @selector(stopWorldTour:)) {
|
||||
return _isTouringWorld;
|
||||
}
|
||||
if (menuItem.action == @selector(giveFeedback:)) {
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (NSUInteger)indexOfStyleInToolbarItem {
|
||||
if (![MGLAccountManager accessToken]) {
|
||||
return NSNotFound;
|
||||
}
|
||||
|
||||
NSArray *styleURLs = @[
|
||||
[MGLStyle streetsStyleURL],
|
||||
[MGLStyle emeraldStyleURL],
|
||||
[MGLStyle lightStyleURL],
|
||||
[MGLStyle darkStyleURL],
|
||||
[MGLStyle satelliteStyleURL],
|
||||
[MGLStyle hybridStyleURL],
|
||||
];
|
||||
return [styleURLs indexOfObject:self.mapView.styleURL];
|
||||
}
|
||||
|
||||
- (BOOL)validateToolbarItem:(NSToolbarItem *)toolbarItem {
|
||||
if (!self.mapView) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
if (toolbarItem.action == @selector(showShareMenu:)) {
|
||||
[(NSButton *)toolbarItem.view sendActionOn:NSLeftMouseDownMask];
|
||||
if (![MGLAccountManager accessToken]) {
|
||||
return NO;
|
||||
}
|
||||
NSURL *styleURL = self.mapView.styleURL;
|
||||
return ([styleURL.scheme isEqualToString:@"mapbox"]
|
||||
&& [styleURL.pathComponents.firstObject isEqualToString:@"styles"]);
|
||||
}
|
||||
if (toolbarItem.action == @selector(setStyle:)) {
|
||||
NSPopUpButton *popUpButton = (NSPopUpButton *)toolbarItem.view;
|
||||
NSUInteger index = self.indexOfStyleInToolbarItem;
|
||||
if (index == NSNotFound) {
|
||||
[popUpButton addItemWithTitle:@"Custom"];
|
||||
index = [popUpButton numberOfItems] - 1;
|
||||
}
|
||||
[popUpButton selectItemAtIndex:index];
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
#pragma mark NSSharingServicePickerDelegate methods
|
||||
|
||||
- (NS_ARRAY_OF(NSSharingService *) *)sharingServicePicker:(NSSharingServicePicker *)sharingServicePicker sharingServicesForItems:(NSArray *)items proposedSharingServices:(NS_ARRAY_OF(NSSharingService *) *)proposedServices {
|
||||
NSURL *shareURL = self.shareURL;
|
||||
NSURL *browserURL = [[NSWorkspace sharedWorkspace] URLForApplicationToOpenURL:shareURL];
|
||||
NSImage *browserIcon = [[NSWorkspace sharedWorkspace] iconForFile:browserURL.path];
|
||||
NSString *browserName = [[NSFileManager defaultManager] displayNameAtPath:browserURL.path];
|
||||
NSString *browserServiceName = [NSString stringWithFormat:@"Open in %@", browserName];
|
||||
|
||||
NSSharingService *browserService = [[NSSharingService alloc] initWithTitle:browserServiceName
|
||||
image:browserIcon
|
||||
alternateImage:nil
|
||||
handler:^{
|
||||
[[NSWorkspace sharedWorkspace] openURL:self.shareURL];
|
||||
}];
|
||||
|
||||
NSMutableArray *sharingServices = [proposedServices mutableCopy];
|
||||
[sharingServices insertObject:browserService atIndex:0];
|
||||
return sharingServices;
|
||||
}
|
||||
|
||||
#pragma mark NSMenuDelegate methods
|
||||
|
||||
- (void)menuWillOpen:(NSMenu *)menu {
|
||||
if (menu == self.mapViewContextMenu) {
|
||||
_mouseLocationForMapViewContextMenu = [self.window.contentView convertPoint:self.window.mouseLocationOutsideOfEventStream
|
||||
toView:self.mapView];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark MGLMapViewDelegate methods
|
||||
|
||||
- (BOOL)mapView:(MGLMapView *)mapView annotationCanShowCallout:(id <MGLAnnotation>)annotation {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (MGLAnnotationImage *)mapView:(MGLMapView *)mapView imageForAnnotation:(id <MGLAnnotation>)annotation {
|
||||
MGLAnnotationImage *annotationImage = [self.mapView dequeueReusableAnnotationImageWithIdentifier:MGLDroppedPinAnnotationImageIdentifier];
|
||||
if (!annotationImage) {
|
||||
NSString *imagePath = [[NSBundle bundleForClass:[MGLMapView class]]
|
||||
pathForResource:@"default_marker" ofType:@"pdf"];
|
||||
NSImage *image = [[NSImage alloc] initWithContentsOfFile:imagePath];
|
||||
NSRect alignmentRect = image.alignmentRect;
|
||||
alignmentRect.origin.y = NSMidY(alignmentRect);
|
||||
alignmentRect.size.height /= 2;
|
||||
image.alignmentRect = alignmentRect;
|
||||
annotationImage = [MGLAnnotationImage annotationImageWithImage:image
|
||||
reuseIdentifier:MGLDroppedPinAnnotationImageIdentifier];
|
||||
}
|
||||
if (_randomizesCursorsOnDroppedPins) {
|
||||
NSArray *cursors = @[
|
||||
[NSCursor IBeamCursor],
|
||||
[NSCursor crosshairCursor],
|
||||
[NSCursor pointingHandCursor],
|
||||
[NSCursor disappearingItemCursor],
|
||||
[NSCursor IBeamCursorForVerticalLayout],
|
||||
[NSCursor operationNotAllowedCursor],
|
||||
[NSCursor dragLinkCursor],
|
||||
[NSCursor dragCopyCursor],
|
||||
[NSCursor contextualMenuCursor],
|
||||
];
|
||||
annotationImage.cursor = cursors[arc4random_uniform(cursors.count)];
|
||||
} else {
|
||||
annotationImage.cursor = nil;
|
||||
}
|
||||
return annotationImage;
|
||||
}
|
||||
|
||||
- (void)mapView:(MGLMapView *)mapView didSelectAnnotation:(id <MGLAnnotation>)annotation {
|
||||
if ([annotation isKindOfClass:[DroppedPinAnnotation class]]) {
|
||||
DroppedPinAnnotation *droppedPin = annotation;
|
||||
[droppedPin resume];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)mapView:(MGLMapView *)mapView didDeselectAnnotation:(id <MGLAnnotation>)annotation {
|
||||
if ([annotation isKindOfClass:[DroppedPinAnnotation class]]) {
|
||||
DroppedPinAnnotation *droppedPin = annotation;
|
||||
[droppedPin pause];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface ValidatedToolbarItem : NSToolbarItem
|
||||
|
||||
@end
|
||||
|
||||
@implementation ValidatedToolbarItem
|
||||
|
||||
- (void)validate {
|
||||
[(MapDocument *)self.toolbar.delegate validateToolbarItem:self];
|
||||
}
|
||||
|
||||
@end
|
||||
129
platform/osx/app/MapDocument.xib
Normal file
129
platform/osx/app/MapDocument.xib
Normal file
@@ -0,0 +1,129 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="9531" systemVersion="15C50" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="9531"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="MapDocument">
|
||||
<connections>
|
||||
<outlet property="mapView" destination="q4d-kF-8Hi" id="7hI-dS-A5R"/>
|
||||
<outlet property="mapViewContextMenu" destination="XbX-6a-Mgy" id="YD0-1r-5N2"/>
|
||||
<outlet property="window" destination="cSv-fg-MAQ" id="TBu-Mu-79N"/>
|
||||
</connections>
|
||||
</customObject>
|
||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||
<window title="Mapbox GL" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" frameAutosaveName="Mapbox" animationBehavior="default" id="cSv-fg-MAQ">
|
||||
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES" fullSizeContentView="YES"/>
|
||||
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
||||
<rect key="contentRect" x="388" y="211" width="512" height="480"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1280" height="777"/>
|
||||
<view key="contentView" id="TuG-C5-zLS">
|
||||
<rect key="frame" x="0.0" y="0.0" width="512" height="480"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<customView translatesAutoresizingMaskIntoConstraints="NO" id="q4d-kF-8Hi" customClass="MGLMapView">
|
||||
<rect key="frame" x="0.0" y="0.0" width="512" height="480"/>
|
||||
<connections>
|
||||
<outlet property="delegate" destination="-2" id="dh2-0H-jFZ"/>
|
||||
</connections>
|
||||
</customView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="bottom" secondItem="q4d-kF-8Hi" secondAttribute="bottom" id="L2t-Be-qWL"/>
|
||||
<constraint firstItem="q4d-kF-8Hi" firstAttribute="top" secondItem="TuG-C5-zLS" secondAttribute="top" id="T8A-o3-Bhq"/>
|
||||
<constraint firstItem="q4d-kF-8Hi" firstAttribute="leading" secondItem="TuG-C5-zLS" secondAttribute="leading" id="fGH-YW-Qd3"/>
|
||||
<constraint firstAttribute="trailing" secondItem="q4d-kF-8Hi" secondAttribute="trailing" id="yfG-iG-K4C"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<toolbar key="toolbar" implicitIdentifier="A3AC6577-4712-4628-813D-113498171A84" allowsUserCustomization="NO" displayMode="iconOnly" sizeMode="regular" id="DTc-AP-Bah">
|
||||
<allowedToolbarItems>
|
||||
<toolbarItem implicitItemIdentifier="NSToolbarSpaceItem" id="bld-8W-Wgg"/>
|
||||
<toolbarItem implicitItemIdentifier="NSToolbarFlexibleSpaceItem" id="z4l-5x-MzK"/>
|
||||
<toolbarItem implicitItemIdentifier="2CB58C0A-7B95-4233-8DD3-F94BFE7D3061" label="Share" paletteLabel="Share" image="NSShareTemplate" id="XJT-Ho-tuZ" customClass="ValidatedToolbarItem">
|
||||
<nil key="toolTip"/>
|
||||
<size key="minSize" width="40" height="32"/>
|
||||
<size key="maxSize" width="48" height="32"/>
|
||||
<button key="view" verticalHuggingPriority="750" id="y6e-ev-rVL">
|
||||
<rect key="frame" x="0.0" y="14" width="48" height="32"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<buttonCell key="cell" type="roundTextured" bezelStyle="texturedRounded" image="NSShareTemplate" imagePosition="only" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="TBK-Ra-XzZ">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
</button>
|
||||
<connections>
|
||||
<action selector="showShareMenu:" target="-1" id="fCB-HP-iou"/>
|
||||
</connections>
|
||||
</toolbarItem>
|
||||
<toolbarItem implicitItemIdentifier="BA3542AF-D63A-4893-9CC7-8F67EF2E82B0" label="Style" paletteLabel="Style" id="u23-0z-Otl" customClass="ValidatedToolbarItem">
|
||||
<nil key="toolTip"/>
|
||||
<size key="minSize" width="100" height="26"/>
|
||||
<size key="maxSize" width="100" height="26"/>
|
||||
<popUpButton key="view" verticalHuggingPriority="750" id="Tzm-Cy-dQg">
|
||||
<rect key="frame" x="0.0" y="14" width="100" height="26"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<popUpButtonCell key="cell" type="roundTextured" title="Streets" bezelStyle="texturedRounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="border" tag="1" imageScaling="proportionallyDown" inset="2" selectedItem="wvt-tP-O3a" id="3PJ-qK-Oh3">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<menu key="menu" id="xf3-qk-IhF">
|
||||
<items>
|
||||
<menuItem title="Streets" state="on" tag="1" id="wvt-tP-O3a"/>
|
||||
<menuItem title="Emerald" tag="2" id="RkE-lp-fL9"/>
|
||||
<menuItem title="Light" tag="3" id="R4X-kt-HHb"/>
|
||||
<menuItem title="Dark" tag="4" id="jUC-5X-0Zx">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
</menuItem>
|
||||
<menuItem title="Satellite" tag="5" id="CTe-e2-o42">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
</menuItem>
|
||||
<menuItem title="Hybrid" tag="6" id="7ly-oA-0ND">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<connections>
|
||||
<action selector="setStyle:" target="-1" id="2Kw-9i-a3G"/>
|
||||
</connections>
|
||||
</toolbarItem>
|
||||
</allowedToolbarItems>
|
||||
<defaultToolbarItems>
|
||||
<toolbarItem reference="XJT-Ho-tuZ"/>
|
||||
<toolbarItem reference="z4l-5x-MzK"/>
|
||||
<toolbarItem reference="u23-0z-Otl"/>
|
||||
</defaultToolbarItems>
|
||||
<connections>
|
||||
<outlet property="delegate" destination="-2" id="V9D-gS-Tvu"/>
|
||||
</connections>
|
||||
</toolbar>
|
||||
<connections>
|
||||
<binding destination="-2" name="displayPatternTitle1" keyPath="mapView.centerCoordinate" id="wtz-AV-bG1">
|
||||
<dictionary key="options">
|
||||
<string key="NSDisplayPattern">%{title1}@</string>
|
||||
<string key="NSValueTransformerName">LocationCoordinate2DTransformer</string>
|
||||
</dictionary>
|
||||
</binding>
|
||||
<outlet property="delegate" destination="-2" id="HEo-Qf-o6o"/>
|
||||
</connections>
|
||||
</window>
|
||||
<menu title="Map View" id="XbX-6a-Mgy">
|
||||
<items>
|
||||
<menuItem title="Drop Pin" id="qZJ-mM-bLj">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
</menuItem>
|
||||
<menuItem title="Remove Pin" id="Zhx-30-VmE">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
</menuItem>
|
||||
</items>
|
||||
<connections>
|
||||
<outlet property="delegate" destination="-2" id="oHe-ZP-lyc"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="820" y="254.5"/>
|
||||
</menu>
|
||||
</objects>
|
||||
<resources>
|
||||
<image name="NSShareTemplate" width="11" height="16"/>
|
||||
</resources>
|
||||
</document>
|
||||
@@ -13,6 +13,7 @@
|
||||
'Credits.rtf',
|
||||
'Icon.icns',
|
||||
'MainMenu.xib',
|
||||
'MapDocument.xib',
|
||||
],
|
||||
|
||||
'dependencies': [
|
||||
@@ -26,6 +27,8 @@
|
||||
'./DroppedPinAnnotation.m',
|
||||
'./LocationCoordinate2DTransformer.h',
|
||||
'./LocationCoordinate2DTransformer.m',
|
||||
'./MapDocument.h',
|
||||
'./MapDocument.m',
|
||||
'./TimeIntervalTransformer.h',
|
||||
'./TimeIntervalTransformer.m',
|
||||
'./NSValue+Additions.h',
|
||||
|
||||
Reference in New Issue
Block a user