Skip to content

Instantly share code, notes, and snippets.

@beccadax
Created July 14, 2013 03:46
Show Gist options
  • Save beccadax/5993128 to your computer and use it in GitHub Desktop.
Save beccadax/5993128 to your computer and use it in GitHub Desktop.
Better sheet APIs for 10.8. Any resemblance to Mavericks is purely coincidental.
//
// NSWindow+BetterSheets.h
// Gistapo
//
// Created by Brent Royal-Gordon on 7/12/13.
// Copyright (c) 2013 Architechies. All rights reserved.
//
#import <Cocoa/Cocoa.h>
#ifndef AVAILABLE_MAC_OS_VERSION_10_9_AND_LATER
enum {
NSModalResponseStop = (-1000), // Also used as the default response for sheets
NSModalResponseAbort = (-1001),
NSModalResponseContinue = (-1002),
};
typedef NSInteger NSModalResponse;
#endif
@interface NSWindow (BetterSheets)
- (void)beginSheet:(NSWindow *)sheetWindow completionHandler:(void (^)(NSModalResponse returnCode))handler;
- (void)beginCriticalSheet:(NSWindow *)sheetWindow completionHandler:(void (^)(NSModalResponse returnCode))handler __attribute__((unavailable ("-beginCriticalSheet:completionHandler: not implemented by NSWindow+BetterSheets")));
- (void)endSheet:(NSWindow *)sheetWindow;
- (void)endSheet:(NSWindow *)sheetWindow returnCode:(NSModalResponse)returnCode;
- (NSArray *)sheets; // An ordered array of the sheets on the window. This consists of the presented sheets in top-to-bottom order, followed by queued sheets in the order they were queued. This does not include nested/sub-sheets.
- (NSWindow *)sheetParent;
@end
//
// NSWindow+BetterSheets.m
// Gistapo
//
// Created by Brent Royal-Gordon on 7/12/13.
// Copyright (c) 2013 Architechies. All rights reserved.
//
#import "NSWindow+BetterSheets.h"
@interface ArchSheetManager : NSObject
@property (weak) NSWindow * window;
@property (strong) NSMutableArray * enqueuedSheets;
@property (strong) NSMutableArray * enqueuedCompletionHandlers;
- (void)enqueueSheet:(NSWindow*)sheet completionHandler:(void (^)(NSModalResponse))completion;
- (void)enqueueCriticalSheet:(NSWindow*)sheet completionHandler:(void (^)(NSModalResponse))completion;
- (void)dequeueSheet:(NSWindow*)sheet returnCode:(NSModalResponse)returnCode;
- (void)sheetDidEnd:(NSWindow *)sheet returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo;
@end
#import <objc/runtime.h>
@implementation NSWindow (BetterSheetsConditional)
- (ArchSheetManager*)arch_sheetManager {
ArchSheetManager * manager = objc_getAssociatedObject(self, _cmd);
if(!manager) {
manager = [ArchSheetManager new];
manager.window = self;
self.arch_sheetManager = manager;
}
return manager;
}
- (void)setArch_sheetManager:(ArchSheetManager*)sheetManager {
objc_setAssociatedObject(self, @selector(arch_sheetManager), sheetManager, OBJC_ASSOCIATION_RETAIN);
}
- (void)arch_beginSheet:(NSWindow *)sheetWindow completionHandler:(void (^)(NSModalResponse))handler {
[self.arch_sheetManager enqueueSheet:sheetWindow completionHandler:handler];
}
- (void)arch_beginCriticalSheet:(NSWindow *)sheetWindow completionHandler:(void (^)(NSModalResponse))handler {
[self.arch_sheetManager enqueueCriticalSheet:sheetWindow completionHandler:handler];
}
- (void)arch_endSheet:(NSWindow *)sheetWindow {
[self endSheet:sheetWindow returnCode:NSModalResponseStop];
}
- (void)arch_endSheet:(NSWindow *)sheetWindow returnCode:(NSModalResponse)returnCode {
[self.arch_sheetManager dequeueSheet:sheetWindow returnCode:returnCode];
}
- (NSWindow *)arch_sheetParent {
if(self.arch_sheetManager.window == self) {
return nil;
}
return self.arch_sheetManager.window;
}
- (NSArray *)arch_sheets {
return self.arch_sheetManager.enqueuedSheets;
}
static void ArchInstallMethodUnlessExists(Class class, SEL final) {
if(class_respondsToSelector(class, final)) {
return;
}
SEL original = NSSelectorFromString([NSString stringWithFormat:@"arch_%@", NSStringFromSelector(final)]);
Method m = class_getInstanceMethod(class, original);
class_addMethod(class, final, method_getImplementation(m), method_getTypeEncoding(m));
}
+ (void)load {
ArchInstallMethodUnlessExists(self, @selector(beginSheet:completionHandler:));
ArchInstallMethodUnlessExists(self, @selector(beginCriticalSheet:completionHandler:));
ArchInstallMethodUnlessExists(self, @selector(endSheet:));
ArchInstallMethodUnlessExists(self, @selector(endSheet:returnCode:));
ArchInstallMethodUnlessExists(self, @selector(sheetParent));
ArchInstallMethodUnlessExists(self, @selector(sheets));
}
@end
@implementation ArchSheetManager
- (id)init {
if((self = [super init])) {
self.enqueuedSheets = [NSMutableArray new];
self.enqueuedCompletionHandlers = [NSMutableArray new];
}
return self;
}
- (void)addSheetToQueue:(NSWindow *)sheet completionHandler:(void (^)(NSModalResponse))completion {
if(!completion) {
completion = ^(NSModalResponse response){};
}
[self.enqueuedSheets addObject:sheet];
[self.enqueuedCompletionHandlers addObject:[completion copy]];
sheet.arch_sheetManager = self;
}
- (void)enqueueSheet:(NSWindow *)sheet completionHandler:(void (^)(NSModalResponse))completion {
[self addSheetToQueue:sheet completionHandler:completion];
if(sheet == self.enqueuedSheets[0]) {
[NSApp beginSheet:sheet modalForWindow:self.window modalDelegate:self didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) contextInfo:NULL];
}
}
- (void)enqueueCriticalSheet:(NSWindow *)sheet completionHandler:(void (^)(NSModalResponse))completion {
NSAssert(NO, @"Critical sheets NYI");
}
- (void)dequeueSheet:(NSWindow *)sheet returnCode:(NSModalResponse)returnCode {
if(self.enqueuedSheets[0] == sheet) {
[NSApp endSheet:sheet returnCode:returnCode];
}
else {
[self doDequeueSheet:sheet returnCode:returnCode];
}
}
- (void)doDequeueSheet:(NSWindow *)sheet returnCode:(NSModalResponse)returnCode {
NSUInteger index = [self.enqueuedSheets indexOfObject:sheet];
NSAssert(index != NSNotFound, @"Can't dismiss a sheet that hasn't been enqueued");
void (^completion)(NSModalResponse) = self.enqueuedCompletionHandlers[index];
completion(returnCode);
sheet.arch_sheetManager = nil;
[self.enqueuedCompletionHandlers removeObjectAtIndex:index];
[self.enqueuedSheets removeObjectAtIndex:index];
}
- (void)sheetDidEnd:(NSWindow *)sheet returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo {
NSParameterAssert(sheet == self.enqueuedSheets[0]);
[sheet orderOut:self.window];
[self doDequeueSheet:sheet returnCode:returnCode];
if(self.enqueuedSheets.count) {
[NSApp beginSheet:self.enqueuedSheets[0] modalForWindow:self.window modalDelegate:self didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) contextInfo:NULL];
}
}
@end
@mikeabdullah
Copy link

Hi, are you willing to place this code under a BSD or MIT license or similar?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment