darling-cocotron/AppKit/NSApplication.m
2020-05-12 17:04:16 -04:00

1589 lines
50 KiB
Objective-C

/* Copyright (c) 2006-2007 Christopher J. W. Lloyd
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
#import <AppKit/NSAlert.h>
#import <AppKit/NSApplication.h>
#import <AppKit/NSColorPanel.h>
#import <AppKit/NSDisplay.h>
#import <AppKit/NSDockTile.h>
#import <AppKit/NSDocumentController.h>
#import <AppKit/NSEvent.h>
#import <AppKit/NSImage.h>
#import <AppKit/NSImageView.h>
#import <AppKit/NSMenu.h>
#import <AppKit/NSMenuItem.h>
#import <AppKit/NSModalSessionX.h>
#import <AppKit/NSNibLoading.h>
#import <AppKit/NSPageLayout.h>
#import <AppKit/NSPanel.h>
#import <AppKit/NSRaise.h>
#import <AppKit/NSScreen.h>
#import <AppKit/NSSheetContext.h>
#import <AppKit/NSSpellChecker.h>
#import <AppKit/NSSystemInfoPanel.h>
#import <AppKit/NSWindow-Private.h>
#import <AppKit/NSWorkspace.h>
#import <CoreGraphics/CGWindow.h>
#import <objc/message.h>
#import <pthread.h>
const NSRunLoopMode NSModalPanelRunLoopMode = @"NSModalPanelRunLoopMode";
const NSRunLoopMode NSEventTrackingRunLoopMode = @"NSEventTrackingRunLoopMode";
const NSNotificationName NSApplicationWillFinishLaunchingNotification =
@"NSApplicationWillFinishLaunchingNotification";
const NSNotificationName NSApplicationDidFinishLaunchingNotification =
@"NSApplicationDidFinishLaunchingNotification";
const NSNotificationName NSApplicationWillBecomeActiveNotification =
@"NSApplicationWillBecomeActiveNotification";
const NSNotificationName NSApplicationDidBecomeActiveNotification =
@"NSApplicationDidBecomeActiveNotification";
const NSNotificationName NSApplicationWillResignActiveNotification =
@"NSApplicationWillResignActiveNotification";
const NSNotificationName NSApplicationDidResignActiveNotification =
@"NSApplicationDidResignActiveNotification";
const NSNotificationName NSApplicationWillUpdateNotification =
@"NSApplicationWillUpdateNotification";
const NSNotificationName NSApplicationDidUpdateNotification =
@"NSApplicationDidUpdateNotification";
const NSNotificationName NSApplicationWillHideNotification =
@"NSApplicationWillHideNotification";
const NSNotificationName NSApplicationDidHideNotification =
@"NSApplicationDidHideNotification";
const NSNotificationName NSApplicationWillUnhideNotification =
@"NSApplicationWillUnhideNotification";
const NSNotificationName NSApplicationDidUnhideNotification =
@"NSApplicationDidUnhideNotification";
const NSNotificationName NSApplicationWillTerminateNotification =
@"NSApplicationWillTerminateNotification";
const NSNotificationName NSApplicationDidChangeScreenParametersNotification =
@"NSApplicationDidChangeScreenParametersNotification";
const NSAppKitVersion NSAppKitVersionNumber = 1504; // macOS 10.12
NSApplication *NSApp = nil;
@interface NSDocumentController (forward)
- (void) _updateRecentDocumentsMenu;
@end
@interface NSMenu (private)
- (NSMenu *) _menuWithName: (NSString *) name;
@end
@interface NSDockTile (private)
- initWithOwner: owner;
@end
@implementation NSApplication
+ (NSApplication *) sharedApplication {
if (NSApp == nil) {
[[self alloc] init]; // NSApp must be nil inside init
}
return NSApp;
}
+ (void) detachDrawingThread: (SEL) selector
toTarget: target
withObject: object
{
NSUnimplementedMethod();
}
- (void) _showSplashImage {
NSImage *image = [NSImage imageNamed: @"splash"];
if (image != nil) {
NSSize imageSize = [image size];
NSRect rect = NSMakeRect(0, 0, imageSize.width, imageSize.height);
NSWindow *splash =
[[NSWindow alloc] initWithContentRect: rect
styleMask: NSBorderlessWindowMask
backing: NSBackingStoreBuffered
defer: NO];
[splash setLevel: NSFloatingWindowLevel];
NSImageView *view = [[NSImageView alloc] initWithFrame: rect];
[view setImage: image];
[splash setContentView: view];
[view release];
[splash setReleasedWhenClosed: YES];
[splash center];
[splash orderFront: nil];
[splash display];
}
}
- (void) _closeSplashImage {
for (NSWindow *window in _windows) {
NSView *contentView = [window contentView];
if ([contentView isKindOfClass: [NSImageView class]])
if ([[[(NSImageView *) contentView image] name]
isEqual: @"splash"]) {
[window close];
return;
}
}
}
- (instancetype) init {
if (NSApp)
NSAssert(!NSApp, @"NSApplication is a singleton");
NSApp = self;
_display = [[NSDisplay currentDisplay] retain];
_windows = [NSMutableArray new];
_mainMenu = nil;
_dockTile = [[NSDockTile alloc] initWithOwner: self];
_modalStack = [NSMutableArray new];
_lock = NSZoneMalloc(NULL, sizeof(pthread_mutex_t));
CFRunLoopAddCommonMode(CFRunLoopGetCurrent(),
(CFStringRef) NSModalPanelRunLoopMode);
CFRunLoopAddCommonMode(CFRunLoopGetCurrent(),
(CFStringRef) NSEventTrackingRunLoopMode);
pthread_mutex_init(_lock, NULL);
[self _showSplashImage];
return NSApp;
}
- (NSGraphicsContext *) context {
NSUnimplementedMethod();
return nil;
}
- delegate {
return _delegate;
}
- (NSArray *) windows {
return _windows;
}
- (NSWindow *) windowWithWindowNumber: (NSInteger) number {
for (NSWindow *window in _windows) {
if ([window windowNumber] == number) {
return window;
}
}
return nil;
}
- (NSMenu *) mainMenu {
return _mainMenu;
}
- (NSMenu *) menu {
return [self mainMenu];
}
- (NSMenu *) windowsMenu {
if (_windowsMenu == nil) {
_windowsMenu = [[NSApp mainMenu] _menuWithName: @"_NSWindowsMenu"];
NSMenuItem *lastItem = [[_windowsMenu itemArray] lastObject];
if (_windowsMenu && ![lastItem isSeparatorItem])
[_windowsMenu addItem: [NSMenuItem separatorItem]];
}
return _windowsMenu;
}
- (NSWindow *) mainWindow {
return _mainWindow;
}
- (void) _setMainWindow: (NSWindow *) window {
_mainWindow = window;
}
- (NSWindow *) keyWindow {
return _keyWindow;
}
- (void) _setKeyWindow: (NSWindow *) window {
_keyWindow = window;
}
- (NSImage *) applicationIconImage {
return _applicationIconImage;
}
- (BOOL) isActiveExcludingWindow: (NSWindow *) exclude {
int count = [_windows count];
while (--count >= 0) {
NSWindow *check = [_windows objectAtIndex: count];
if (check == exclude)
continue;
if ([check _isActive])
return YES;
}
return NO;
}
- (BOOL) isActive {
return [self isActiveExcludingWindow: nil];
}
- (BOOL) isHidden {
return _isHidden;
}
- (BOOL) isRunning {
return _isRunning;
}
- (NSWindow *) makeWindowsPerform: (SEL) selector inOrder: (BOOL) inOrder {
NSUnimplementedMethod();
return nil;
}
- (void) miniaturizeAll: sender {
int count = [_windows count];
while (--count >= 0)
[[_windows objectAtIndex: count] miniaturize: sender];
}
- (NSArray *) orderedDocuments {
NSMutableArray *result = [NSMutableArray array];
NSArray *orderedWindows = [self orderedWindows];
for (NSWindow *checkWindow in orderedWindows) {
NSDocument *checkDocument = [[checkWindow windowController] document];
if (checkDocument != nil)
[result addObject: checkDocument];
}
return result;
}
- (NSArray *) orderedWindows {
NSMutableArray *result = [NSMutableArray array];
NSArray *numbers = [_display orderedWindowNumbers];
for (NSNumber *number in numbers) {
NSWindow *window = [self windowWithWindowNumber: [number integerValue]];
if (window != nil && ![window isKindOfClass: [NSPanel class]])
[result addObject: window];
}
return result;
}
- (void) preventWindowOrdering {
NSUnimplementedMethod();
}
- (void) unregisterDelegate {
if ([_delegate respondsToSelector: @selector
(applicationWillFinishLaunching:)]) {
[[NSNotificationCenter defaultCenter]
removeObserver: _delegate
name: NSApplicationWillFinishLaunchingNotification
object: self];
}
if ([_delegate respondsToSelector: @selector
(applicationDidFinishLaunching:)]) {
[[NSNotificationCenter defaultCenter]
removeObserver: _delegate
name: NSApplicationDidFinishLaunchingNotification
object: self];
}
if ([_delegate
respondsToSelector: @selector(applicationDidBecomeActive:)]) {
[[NSNotificationCenter defaultCenter]
removeObserver: _delegate
name: NSApplicationDidBecomeActiveNotification
object: self];
}
if ([_delegate respondsToSelector: @selector(applicationWillTerminate:)]) {
[[NSNotificationCenter defaultCenter]
removeObserver: _delegate
name: NSApplicationWillTerminateNotification
object: self];
}
}
- (void) registerDelegate {
if ([_delegate respondsToSelector: @selector
(applicationWillFinishLaunching:)]) {
[[NSNotificationCenter defaultCenter]
addObserver: _delegate
selector: @selector(applicationWillFinishLaunching:)
name: NSApplicationWillFinishLaunchingNotification
object: self];
}
if ([_delegate respondsToSelector: @selector
(applicationDidFinishLaunching:)]) {
[[NSNotificationCenter defaultCenter]
addObserver: _delegate
selector: @selector(applicationDidFinishLaunching:)
name: NSApplicationDidFinishLaunchingNotification
object: self];
}
if ([_delegate
respondsToSelector: @selector(applicationDidBecomeActive:)]) {
[[NSNotificationCenter defaultCenter]
addObserver: _delegate
selector: @selector(applicationDidBecomeActive:)
name: NSApplicationDidBecomeActiveNotification
object: self];
}
if ([_delegate respondsToSelector: @selector(applicationWillTerminate:)]) {
[[NSNotificationCenter defaultCenter]
addObserver: _delegate
selector: @selector(applicationWillTerminate:)
name: NSApplicationWillTerminateNotification
object: self];
}
}
- (void) setDelegate: delegate {
if (delegate != _delegate) {
[self unregisterDelegate];
_delegate = delegate;
[self registerDelegate];
}
}
- (void) setMainMenu: (NSMenu *) menu {
int i, count = [_windows count];
[_mainMenu autorelease];
_mainMenu = [menu retain];
for (i = 0; i < count; i++) {
NSWindow *window = [_windows objectAtIndex: i];
if (![window isKindOfClass: [NSPanel class]])
[window setMenu: _mainMenu];
}
}
- (void) setMenu: (NSMenu *) menu {
[self setMainMenu: menu];
}
- (void) setApplicationIconImage: (NSImage *) image {
image = [image retain];
[_applicationIconImage release];
_applicationIconImage = image;
[image setName: NSImageNameApplicationIcon];
}
- (NSApplicationActivationPolicy) activationPolicy {
// TODO: Implement
return NSApplicationActivationPolicyRegular;
}
- (BOOL) setActivationPolicy: (NSApplicationActivationPolicy) activationPolicy {
// TODO: Implement
return NO;
}
- (void) setWindowsMenu: (NSMenu *) menu {
[_windowsMenu autorelease];
_windowsMenu = [menu retain];
}
- (void) addWindowsItem: (NSWindow *) window
title: (NSString *) title
filename: (BOOL) isFilename
{
NSMenuItem *item;
if ([[self windowsMenu]
indexOfItemWithTarget: window
andAction: @selector(makeKeyAndOrderFront:)] != -1)
return;
if (isFilename)
title = [NSString
stringWithFormat: @"%@ -- %@", [title lastPathComponent],
[title stringByDeletingLastPathComponent]];
item = [[[NSMenuItem alloc] initWithTitle: title
action: @selector(makeKeyAndOrderFront:)
keyEquivalent: @""] autorelease];
[item setTarget: window];
[[self windowsMenu] addItem: item];
}
- (void) changeWindowsItem: (NSWindow *) window
title: (NSString *) title
filename: (BOOL) isFilename
{
if ([title length] == 0) {
// Windows with no name aren't in the Windows menu
[self removeWindowsItem: window];
} else {
int itemIndex = [[self windowsMenu]
indexOfItemWithTarget: window
andAction: @selector(makeKeyAndOrderFront:)];
if (itemIndex != -1) {
NSMenuItem *item = [[self windowsMenu] itemAtIndex: itemIndex];
if (isFilename)
title = [NSString
stringWithFormat:
@"%@ -- %@", [title lastPathComponent],
[title stringByDeletingLastPathComponent]];
[item setTitle: title];
[[self windowsMenu] itemChanged: item];
} else
[self addWindowsItem: window title: title filename: isFilename];
}
}
- (void) removeWindowsItem: (NSWindow *) window {
int itemIndex = [[self windowsMenu]
indexOfItemWithTarget: window
andAction: @selector(makeKeyAndOrderFront:)];
if (itemIndex != -1) {
[[self windowsMenu] removeItemAtIndex: itemIndex];
if ([[[[self windowsMenu] itemArray] lastObject] isSeparatorItem]) {
[[self windowsMenu]
removeItem: [[[self windowsMenu] itemArray] lastObject]];
}
}
}
- (void) updateWindowsItem: (NSWindow *) window {
#if 0
NSUnimplementedMethod();
#else
NSMenu *menu = [self windowsMenu];
int itemIndex = [[self windowsMenu]
indexOfItemWithTarget: window
andAction: @selector(makeKeyAndOrderFront:)];
if (itemIndex != -1) {
NSMenuItem *item = [menu itemAtIndex: itemIndex];
}
#endif
}
- (BOOL) openFiles {
BOOL opened = NO;
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
id nsOpen = [defaults objectForKey: @"NSOpen"];
NSArray *openFiles = nil;
if ([nsOpen isKindOfClass: [NSString class]] && [nsOpen length]) {
openFiles = [NSArray arrayWithObject: nsOpen];
} else if ([nsOpen isKindOfClass: [NSArray class]]) {
openFiles = nsOpen;
}
if ([openFiles count] == 0) {
return NO;
}
if ([openFiles count] == 1 &&
[_delegate respondsToSelector: @selector(application:openFile:)]) {
opened = [_delegate application: self openFile: [openFiles lastObject]];
} else if ([_delegate respondsToSelector: @selector(application:
openFiles:)]) {
[_delegate application: self openFiles: openFiles];
opened = YES;
} else {
id target = _delegate;
if (![_delegate respondsToSelector: @selector(application:openFile:)]) {
target = [NSDocumentController sharedDocumentController];
}
for (NSString *aFile in openFiles) {
opened |= [target application: self openFile: aFile];
}
}
[defaults removeObjectForKey: @"NSOpen"];
return opened;
}
- (void) finishLaunching {
NSAutoreleasePool *pool = [NSAutoreleasePool new];
BOOL needsUntitled = YES;
NS_DURING [[NSNotificationCenter defaultCenter]
postNotificationName: NSApplicationWillFinishLaunchingNotification
object: self];
NS_HANDLER [self reportException: localException];
NS_ENDHANDLER
// Load the application icon if we have one
NSString *iconName = [[[NSBundle mainBundle] infoDictionary]
objectForKey: @"CFBundleIconFile"];
if (iconName) {
iconName = [iconName stringByAppendingPathExtension: @"icns"];
NSImage *image = [NSImage imageNamed: iconName];
[self setApplicationIconImage: image];
}
// Give us a first event
[NSTimer scheduledTimerWithTimeInterval: 0.1
target: nil
selector: NULL
userInfo: nil
repeats: NO];
[self _closeSplashImage];
NSDocumentController *controller = nil;
id types = [[[NSBundle mainBundle] infoDictionary]
objectForKey: @"CFBundleDocumentTypes"];
if ([types count] > 0)
controller = [NSDocumentController sharedDocumentController];
if ([self openFiles]) {
needsUntitled = NO;
}
if (needsUntitled && _delegate &&
[_delegate respondsToSelector: @selector
(applicationShouldOpenUntitledFile:)]) {
needsUntitled = [_delegate applicationShouldOpenUntitledFile: self];
}
if (needsUntitled && _delegate &&
[_delegate
respondsToSelector: @selector(applicationOpenUntitledFile:)]) {
needsUntitled = ![_delegate applicationOpenUntitledFile: self];
}
if (needsUntitled && controller &&
![controller documentClassForType: [controller defaultType]]) {
needsUntitled = NO;
}
if (needsUntitled && controller) {
[controller _updateRecentDocumentsMenu];
[controller newDocument: self];
}
NS_DURING [[NSNotificationCenter defaultCenter]
postNotificationName: NSApplicationDidFinishLaunchingNotification
object: self];
NS_HANDLER [self reportException: localException];
NS_ENDHANDLER
[pool release];
}
- (void) _checkForReleasedWindows {
int count = [_windows count];
while (--count >= 0) {
NSWindow *check = [_windows objectAtIndex: count];
if ([check retainCount] == 1) {
// Use the setters here - give a chance to the observer to notice
// something happened
if (check == _keyWindow) {
[self _setKeyWindow: nil];
}
if (check == _mainWindow) {
[self _setMainWindow: nil];
}
[_windows removeObjectAtIndex: count];
}
}
}
- (void) _checkForTerminate {
int count = [_windows count];
while (--count >= 0) {
NSWindow *check = [_windows objectAtIndex: count];
if (![check isKindOfClass: [NSPanel class]] && [check isVisible]) {
return;
}
}
[self terminate: self];
}
- (void) _checkForAppActivation {
#if 1
if ([self isActive])
[_windows makeObjectsPerformSelector: @selector(_showForActivation)];
else {
[_windows makeObjectsPerformSelector: @selector(_hideForDeactivation)];
}
#endif
}
- (void) run {
static BOOL didlaunch = NO;
NSAutoreleasePool *pool;
_isRunning = YES;
if (!didlaunch) {
didlaunch = YES;
pool = [NSAutoreleasePool new];
[self finishLaunching];
[pool release];
}
do {
pool = [NSAutoreleasePool new];
NSEvent *event;
event = [self nextEventMatchingMask: NSAnyEventMask
untilDate: [NSDate distantFuture]
inMode: NSDefaultRunLoopMode
dequeue: YES];
NS_DURING [self sendEvent: event];
NS_HANDLER [self reportException: localException];
NS_ENDHANDLER
[self _checkForReleasedWindows];
[self _checkForTerminate];
[pool release];
} while (_isRunning);
}
- (BOOL) _performKeyEquivalent: (NSEvent *) event {
if (event.charactersIgnoringModifiers.length > 0) {
/* order is important here, views may want to handle the event before
* menu*/
if ([[self keyWindow] performKeyEquivalent: event])
return YES;
if ([[self mainWindow] performKeyEquivalent: event])
return YES;
if ([[self mainMenu] performKeyEquivalent: event])
return YES;
}
// documentation says to send it to all windows
return NO;
}
- (void) sendEvent: (NSEvent *) event {
if ([event type] == NSKeyDown) {
unsigned modifierFlags = [event modifierFlags];
if (modifierFlags & (NSCommandKeyMask | NSAlternateKeyMask))
if ([self _performKeyEquivalent: event])
return;
}
[[event window] sendEvent: event];
}
// This method is used by NSWindow
- (void) _displayAllWindowsIfNeeded {
[[NSApp windows] makeObjectsPerformSelector: @selector(displayIfNeeded)];
}
- (NSEvent *) nextEventMatchingMask: (NSEventMask) mask
untilDate: (NSDate *) untilDate
inMode: (NSRunLoopMode) mode
dequeue: (BOOL) dequeue
{
NSEvent *nextEvent = nil;
do {
NSAutoreleasePool *pool = [NSAutoreleasePool new];
NS_DURING [NSClassFromString(@"Win32RunningCopyPipe")
performSelector: @selector(createRunningCopyPipe)];
// This should happen before _makeSureIsOnAScreen so we don't reposition
// done windows
[self _checkForReleasedWindows];
[[NSApp windows]
makeObjectsPerformSelector: @selector(_makeSureIsOnAScreen)];
[self _checkForAppActivation];
[self _displayAllWindowsIfNeeded];
nextEvent = [[_display nextEventMatchingMask: mask
untilDate: untilDate
inMode: mode
dequeue: dequeue] retain];
if ([nextEvent type] == NSAppKitSystem) {
[nextEvent release];
nextEvent = nil;
}
NS_HANDLER [self reportException: localException];
NS_ENDHANDLER
[pool release];
} while (nextEvent == nil && [untilDate timeIntervalSinceNow] > 0);
if (nextEvent != nil) {
nextEvent = [nextEvent retain];
pthread_mutex_lock(_lock);
[_currentEvent release];
_currentEvent = nextEvent;
pthread_mutex_unlock(_lock);
}
return [nextEvent autorelease];
}
- (NSEvent *) currentEvent {
/* Apps do use currentEvent from secondary threads and it doesn't crash on
* OS X, so we need to be safe here too. */
NSEvent *result;
pthread_mutex_lock(_lock);
result = [_currentEvent retain];
pthread_mutex_unlock(_lock);
return [result autorelease];
}
- (void) discardEventsMatchingMask: (NSEventMask) mask
beforeEvent: (NSEvent *) event
{
[_display discardEventsMatchingMask: mask beforeEvent: event];
}
- (void) postEvent: (NSEvent *) event atStart: (BOOL) atStart {
[_display postEvent: event atStart: atStart];
}
- _searchForAction: (SEL) action responder: target {
// Search a responder chain
while (target != nil) {
if ([target respondsToSelector: action])
return target;
if ([target respondsToSelector: @selector(nextResponder)])
target = [target nextResponder];
else
break;
}
return nil;
}
- _searchForAction: (SEL) action window: (NSWindow *) window {
// Search a windows responder chain and window
// The window check is done seperately from the responder chain
// in case the responder chain is broken
// FIXME: should a windows delegate and windowController be checked if a
// window is found in a responder chain too ? Document based facts:
// An NSWindow's next responder should be the window controller
// An NSWindow's delegate should be the document
// - This probably means the windowController check is duplicative, but need
// to make the next responder is window controller
id check = [self _searchForAction: action
responder: [window firstResponder]];
if (check != nil)
return check;
if ([[window delegate] respondsToSelector: action])
return [window delegate];
if ([[window windowController] respondsToSelector: action])
return [window windowController];
return nil;
}
- targetForAction: (SEL) action {
return [self targetForAction: action to: nil from: nil];
}
- targetForAction: (SEL) action to: target from: sender {
if (target == nil) {
target = [self _searchForAction: action window: [self keyWindow]];
if (target)
return target;
if ([self mainWindow] != [self keyWindow]) {
target = [self _searchForAction: action window: [self mainWindow]];
if (target)
return target;
}
} else {
target = [self _searchForAction: action responder: target];
if (target)
return target;
}
NSDocumentController *documentController =
[NSDocumentController sharedDocumentController];
if ([[documentController currentDocument] respondsToSelector: action])
return [documentController currentDocument];
if ([self respondsToSelector: action])
return self;
if ([[self delegate] respondsToSelector: action])
return [self delegate];
if ([documentController respondsToSelector: action])
return documentController;
return nil;
}
- (BOOL) sendAction: (SEL) action to: target from: sender {
if ([target respondsToSelector: action]) {
[target performSelector: action withObject: sender];
return YES;
}
target = [self targetForAction: action to: target from: sender];
if (target != nil) {
[target performSelector: action withObject: sender];
return YES;
}
return NO;
}
- (BOOL) tryToPerform: (SEL) selector with: object {
if ([self respondsToSelector: selector]) {
[self performSelector: selector withObject: object];
return YES;
}
if ([[self delegate] respondsToSelector: selector]) {
[[self delegate] performSelector: selector withObject: object];
return YES;
}
return NO;
}
- (void) setWindowsNeedUpdate: (BOOL) value {
_windowsNeedUpdate = value;
NSUnimplementedMethod();
}
- (void) updateWindows {
[_windows makeObjectsPerformSelector: @selector(update)];
}
- (void) activateIgnoringOtherApps: (BOOL) flag {
NSUnimplementedMethod();
}
- (void) deactivate {
NSUnimplementedMethod();
}
- (NSWindow *) modalWindow {
return [[_modalStack lastObject] modalWindow];
}
- (NSModalSession) beginModalSessionForWindow: (NSWindow *) window {
NSModalSessionX *session = [NSModalSessionX sessionWithWindow: window];
[_modalStack addObject: session];
[window _hideMenuViewIfNeeded];
if (![window isVisible]) {
[window center];
}
[window makeKeyAndOrderFront: self];
return session;
}
- (NSModalResponse) runModalSession: (NSModalSession) session {
while ([session stopCode] == NSRunContinuesResponse) {
NSAutoreleasePool *pool = [NSAutoreleasePool new];
NSEvent *event = [self nextEventMatchingMask: NSAnyEventMask
untilDate: [NSDate date]
inMode: NSModalPanelRunLoopMode
dequeue: YES];
if (event == nil) {
[pool release];
break;
}
NSWindow *window = [event window];
// In theory this could get weird, but all we want is the ESC-cancel
// keybinding, afaik NSApp doesn't respond to any other
// doCommandBySelectors...
if ([event type] == NSKeyDown && window == [session modalWindow])
[self interpretKeyEvents: @[ event ]];
if (window == [session modalWindow] || [window worksWhenModal])
[self sendEvent: event];
else if ([event type] == NSLeftMouseDown)
[[session modalWindow] makeKeyAndOrderFront: self];
else {
// We need to preserve some events which are not processed in the
// modal loop and requeue them. The particular case we need to
// handle is mouse down. run modal. then actually receive the mouse
// up when the modal is done. So we know this works in Cocoa, save
// the mouse up here. We don't want to save mouse moved or such.
// There is kind of adhoc, probably a better way to do it, find out
// which combinations should work (e.g. mouse enter, do we get mouse
// exit?)
if ([[session unprocessedEvents] count] == 0) {
switch ([event type]) {
case NSLeftMouseUp:
case NSRightMouseUp:
[session addUnprocessedEvent: event];
break;
default:
// don't save
break;
}
}
}
[pool release];
}
return [session stopCode];
}
- (void) endModalSession: (NSModalSession) session {
if (session != [_modalStack lastObject])
[NSException
raise: NSInvalidArgumentException
format: @"-[%@ %s] modal session %@ is not the current one %@",
[self class], sel_getName(_cmd), session,
[_modalStack lastObject]];
for (NSEvent *requeue in [session unprocessedEvents]) {
[self postEvent: requeue atStart: YES];
}
[[session modalWindow] _showMenuViewIfNeeded];
[_modalStack removeLastObject];
}
- (void) stopModalWithCode: (NSModalResponse) code {
// This should silently ignore any attempt to end a session when there is
// none.
[[_modalStack lastObject] stopModalWithCode: code];
}
- (void) _mainThreadRunModalForWindow: (NSMutableDictionary *) values {
NSWindow *window = [values objectForKey: @"NSWindow"];
NSModalSession session = [self beginModalSessionForWindow: window];
NSModalResponse result;
while ((result = [NSApp runModalSession: session]) ==
NSRunContinuesResponse)
;
[self endModalSession: session];
[values setObject: [NSNumber numberWithInteger: result] forKey: @"result"];
}
- (NSModalResponse) runModalForWindow: (NSWindow *) window {
NSMutableDictionary *values = [NSMutableDictionary dictionary];
values[@"NSWindow"] = window;
[self performSelectorOnMainThread: @selector(_mainThreadRunModalForWindow:)
withObject: values
waitUntilDone: YES
modes: @[
NSDefaultRunLoopMode,
NSModalPanelRunLoopMode
]];
NSNumber *result = values[@"result"];
return [result integerValue];
}
- (void) stopModal {
[self stopModalWithCode: NSRunStoppedResponse];
}
- (void) abortModal {
[self stopModalWithCode: NSRunAbortedResponse];
}
// cancel modal windows
- (void) cancel: sender {
if ([self modalWindow] != nil)
[self abortModal];
}
- (void) beginSheet: (NSWindow *) sheet
modalForWindow: (NSWindow *) window
modalDelegate: (id) modalDelegate
didEndSelector: (SEL) didEndSelector
contextInfo: (void *) contextInfo
{
NSSheetContext *context =
[NSSheetContext sheetContextWithSheet: sheet
modalDelegate: modalDelegate
didEndSelector: didEndSelector
contextInfo: contextInfo
frame: [sheet frame]];
if ([[NSUserDefaults standardUserDefaults]
boolForKey: @"NSRunAllSheetsAsModalPanel"]) {
// Center the sheet on the window.
NSPoint windowCenter =
NSMakePoint(NSMidX([window frame]), NSMidY([window frame]));
NSPoint sheetCenter =
NSMakePoint(NSMidX([sheet frame]), NSMidY([sheet frame]));
NSPoint origin = [sheet frame].origin;
origin.x += windowCenter.x - sheetCenter.x;
origin.y += windowCenter.y - sheetCenter.y;
[sheet setFrameOrigin: origin];
[sheet _setSheetContext: context];
[sheet setLevel: NSModalPanelWindowLevel];
NSModalSession session = [self beginModalSessionForWindow: sheet];
[context setModalSession: session];
while ([NSApp runModalSession: session] == NSRunContinuesResponse) {
[[NSRunLoop currentRunLoop] runMode: NSModalPanelRunLoopMode
beforeDate: [NSDate distantFuture]];
}
[self endModalSession: session];
} else {
[window _attachSheetContextOrderFrontAndAnimate: context];
}
}
- (void) endSheet: (NSWindow *) sheet returnCode: (NSModalResponse) returnCode {
if ([[NSUserDefaults standardUserDefaults]
boolForKey: @"NSRunAllSheetsAsModalPanel"]) {
NSSheetContext *context = [sheet _sheetContext];
NSModalSession session = [context modalSession];
[session stopModalWithCode: NSRunStoppedResponse];
IMP function = [[context modalDelegate]
methodForSelector: [context didEndSelector]];
if (function != NULL) {
function([context modalDelegate], [context didEndSelector], sheet,
returnCode, [context contextInfo]);
}
[sheet _setSheetContext: nil];
} else {
NSUInteger count = [_windows count];
while (--count >= 0) {
NSWindow *check = _windows[count];
NSSheetContext *context = [check _sheetContext];
IMP function;
if ([context sheet] == sheet) {
[[context retain] autorelease];
[check _detachSheetContextAnimateAndOrderOut];
function = [[context modalDelegate]
methodForSelector: [context didEndSelector]];
if (function != NULL)
function([context modalDelegate], [context didEndSelector],
sheet, returnCode, [context contextInfo]);
return;
}
}
}
}
- (void) endSheet: (NSWindow *) sheet {
[self endSheet: sheet returnCode: 0];
}
- (void) reportException: (NSException *) exception {
NSLog(@"NSApplication got exception: %@", exception);
}
- (void) _attentionTimer: (NSTimer *) timer {
[_windows makeObjectsPerformSelector: @selector(_flashWindow)];
}
- (int) requestUserAttention: (NSRequestUserAttentionType) attentionType {
[_attentionTimer invalidate];
_attentionTimer =
[NSTimer scheduledTimerWithTimeInterval: 3
target: self
selector: @selector(_attentionTimer:)
userInfo: nil
repeats: YES];
return 0;
}
- (void) cancelUserAttentionRequest: (int) requestNumber {
NSUnimplementedMethod();
}
- (void) runPageLayout: sender {
[[NSPageLayout pageLayout] runModal];
}
- (void) orderFrontColorPanel: (id) sender {
[[NSColorPanel sharedColorPanel] orderFront: sender];
}
- (void) orderFrontCharacterPalette: sender {
NSUnimplementedMethod();
}
- (void) hide: (id) sender {
// Deactivates the application and hides all windows.
if (_isHidden) {
return;
}
[[NSNotificationCenter defaultCenter]
postNotificationName: NSApplicationWillHideNotification
object: self];
// Do no use orderOut here ist causes the application to quit if no window
// is visible.
[_windows
makeObjectsPerformSelector: @selector(_forcedHideForDeactivation)];
[[NSNotificationCenter defaultCenter]
postNotificationName: NSApplicationDidHideNotification
object: self];
_isHidden = YES;
}
- (void) hideOtherApplications: sender {
NSUnimplementedMethod();
}
- (void) unhide: sender {
if (_isHidden) {
[[NSNotificationCenter defaultCenter]
postNotificationName: NSApplicationWillUnhideNotification
object: self];
[_windows makeObjectsPerformSelector: @selector
(_showForActivation)]; // only shows previously hidden windows
[[NSNotificationCenter defaultCenter]
postNotificationName: NSApplicationDidUnhideNotification
object: self];
}
_isHidden = NO;
//[self activateIgnoringOtherApps:NO]
}
- (void) unhideAllApplications: sender {
NSUnimplementedMethod();
}
- (void) unhideWithoutActivation {
if (_isHidden) {
[[NSNotificationCenter defaultCenter]
postNotificationName: NSApplicationWillUnhideNotification
object: self];
[_windows makeObjectsPerformSelector: @selector
(_showForActivation)]; // only shows previously hidden windows
[[NSNotificationCenter defaultCenter]
postNotificationName: NSApplicationDidUnhideNotification
object: self];
}
_isHidden = NO;
}
- (void) stop: sender {
if ([_modalStack lastObject] != nil) {
[self stopModal];
return;
}
_isRunning = NO;
}
- (void) terminate: sender {
[[NSDocumentController sharedDocumentController]
closeAllDocumentsWithDelegate: self
didCloseAllSelector: @selector
(_documentController:didCloseAll:contextInfo:)
contextInfo: NULL];
}
- (void) _documentController: (NSDocumentController *) docController
didCloseAll: (BOOL) didCloseAll
contextInfo: (void *) info
{
// callback method for terminate:
if (didCloseAll) {
if ([_delegate
respondsToSelector: @selector(applicationShouldTerminate:)])
[self replyToApplicationShouldTerminate:
[_delegate applicationShouldTerminate: self] ==
NSTerminateNow];
else
[self replyToApplicationShouldTerminate: YES];
}
}
- (void) replyToApplicationShouldTerminate: (BOOL) terminate {
if (terminate == YES) {
[[NSNotificationCenter defaultCenter]
postNotificationName: NSApplicationWillTerminateNotification
object: self];
[NSClassFromString(@"Win32RunningCopyPipe")
performSelector: @selector(invalidateRunningCopyPipe)];
exit(0);
}
}
- (void) replyToOpenOrPrint: (NSApplicationDelegateReply) reply {
NSUnimplementedMethod();
}
- (void) arrangeInFront: sender {
#define CASCADE_DELTA 20 // ? isn't there a call for this?
NSMutableArray *visibleWindows = [NSMutableArray new];
NSRect rect = [[[NSScreen screens] objectAtIndex: 0] frame], winRect;
NSArray *windowsItems = [[self windowsMenu] itemArray];
int i, count = [windowsItems count];
for (i = 0; i < count; ++i) {
id target = [[windowsItems objectAtIndex: i] target];
if ([target isKindOfClass: [NSWindow class]])
[visibleWindows addObject: target];
}
count = [visibleWindows count];
if (count == 0)
return;
// find screen center.
// subtract window w,h /2
winRect = [[visibleWindows objectAtIndex: 0] frame];
rect.origin.x = (rect.size.width / 2) - (winRect.size.width / 2);
rect.origin.x -= count * CASCADE_DELTA / 2;
rect.origin.x = floor(rect.origin.x);
rect.origin.y = (rect.size.height / 2) + (winRect.size.height / 2);
rect.origin.y += count * CASCADE_DELTA / 2;
rect.origin.y = floor(rect.origin.y);
for (i = 0; i < count; ++i) {
[[visibleWindows objectAtIndex: i] setFrameTopLeftPoint: rect.origin];
[[visibleWindows objectAtIndex: i] orderFront: nil];
rect.origin.x += CASCADE_DELTA;
rect.origin.y -= CASCADE_DELTA;
}
}
// This is a method removed from official headers in 10.4,
// but it still exists and is widely used.
- (void) setAppleMenu: (NSMenu *) menu {
NSUnimplementedMethod();
}
- (NSMenu *) servicesMenu {
return [[NSApp mainMenu] _menuWithName: @"_NSServicesMenu"];
}
- (void) setServicesMenu: (NSMenu *) menu {
NSUnimplementedMethod();
}
- servicesProvider {
return nil;
}
- (void) setServicesProvider: provider {
}
- (void) registerServicesMenuSendTypes: (NSArray *) sendTypes
returnTypes: (NSArray *) returnTypes
{
// tiredofthesewarnings NSUnsupportedMethod();
}
- validRequestorForSendType: (NSString *) sendType
returnType: (NSString *) returnType
{
NSUnimplementedMethod();
return nil;
}
- (void) orderFrontStandardAboutPanel: sender {
[self orderFrontStandardAboutPanelWithOptions: nil];
}
- (void) orderFrontStandardAboutPanelWithOptions: (NSDictionary *) options {
NSSystemInfoPanel *standardAboutPanel =
[[NSSystemInfoPanel standardAboutPanel] retain];
[standardAboutPanel showInfoPanel: self withOptions: options];
}
- (void) activateContextHelpMode: sender {
NSUnimplementedMethod();
}
- (void) showGuessPanel: sender {
[[[NSSpellChecker sharedSpellChecker] spellingPanel]
makeKeyAndOrderFront: self];
}
- (void) showHelp: sender {
NSString *helpBookFolder = [[[NSBundle mainBundle] infoDictionary]
objectForKey: @"CFBundleHelpBookFolder"];
if (helpBookFolder != nil) {
BOOL isDir;
NSString *folder =
[[NSBundle mainBundle] pathForResource: helpBookFolder
ofType: nil];
if (folder != nil &&
[[NSFileManager defaultManager] fileExistsAtPath: folder
isDirectory: &isDir] &&
isDir) {
NSBundle *helpBundle = [NSBundle bundleWithPath: folder];
if (helpBundle) {
NSString *helpBookName = [[helpBundle infoDictionary]
objectForKey: @"CFBundleHelpTOCFile"];
if (helpBookName != nil) {
NSString *helpFilePath =
[helpBundle pathForResource: helpBookName
ofType: nil];
if (helpFilePath) {
if ([[NSWorkspace sharedWorkspace]
openFile: helpFilePath
withApplication: @"Help Viewer"] == YES) {
return;
}
}
}
// Perhaps there's an index.html file that'll be usable?
NSString *helpFilePath = [helpBundle pathForResource: @"index"
ofType: @"html"];
if (helpFilePath) {
if ([[NSWorkspace sharedWorkspace]
openFile: helpFilePath
withApplication: @"Help Viewer"] == YES) {
return;
}
}
}
}
}
NSString *processName = [[NSProcessInfo processInfo] processName];
NSAlert *alert = [[NSAlert alloc] init];
[alert setMessageText: NSLocalizedStringFromTableInBundle(
@"Help", nil,
[NSBundle bundleForClass: [NSApplication
class]],
@"Help alert title")];
[alert setInformativeText:
[NSString stringWithFormat:
NSLocalizedStringFromTableInBundle(
@"Help isn't available for %@.",
nil,
[NSBundle bundleForClass:
[NSApplication
class]],
@""),
processName]];
[alert runModal];
[alert release];
}
- (NSDockTile *) dockTile {
return _dockTile;
}
- (void) doCommandBySelector: (SEL) selector {
if ([_delegate respondsToSelector: selector])
[_delegate performSelector: selector withObject: nil];
else
[super doCommandBySelector: selector];
}
- (void) _addWindow: (NSWindow *) window {
[_windows addObject: window];
}
- (void) _windowWillBecomeActive: (NSWindow *) window {
[_attentionTimer invalidate];
_attentionTimer = nil;
if (![self isActive]) {
[[NSNotificationCenter defaultCenter]
postNotificationName: NSApplicationWillBecomeActiveNotification
object: self];
}
}
- (void) _windowDidBecomeActive: (NSWindow *) window {
if (![self isActiveExcludingWindow: window]) {
[[NSNotificationCenter defaultCenter]
postNotificationName: NSApplicationDidBecomeActiveNotification
object: self];
}
}
- (void) _windowWillBecomeDeactive: (NSWindow *) window {
if (![self isActiveExcludingWindow: window]) {
[[NSNotificationCenter defaultCenter]
postNotificationName: NSApplicationWillResignActiveNotification
object: self];
}
}
- (void) _windowDidBecomeDeactive: (NSWindow *) window {
if (![self isActive]) {
// Exposed menus are running tight event tracking loops and would remain
// visible when the app deactivates (making the UI less than community
// minded) - unfortunately because they're in these tracking loops
// they're waiting on events and even though they could receive the
// notification sent here they can't deal with it until an event is
// received to let them proceed. This special event type was added to
// help them get unstuck and remove the menu on deactivation
NSEvent *appKitEvent =
[NSEvent otherEventWithType: NSAppKitDefined
location: NSZeroPoint
modifierFlags: 0
timestamp: 0
windowNumber: 0
context: nil
subtype: NSApplicationDeactivated
data1: 0
data2: 0];
[self postEvent: appKitEvent atStart: YES];
[[NSNotificationCenter defaultCenter]
postNotificationName: NSApplicationDidResignActiveNotification
object: self];
}
}
// private method called when the application is reopened
- (void) _reopen {
BOOL doReopen = YES;
if ([_delegate respondsToSelector: @selector
(applicationShouldHandleReopen:hasVisibleWindows:)])
doReopen = [_delegate applicationShouldHandleReopen: self
hasVisibleWindows: !_isHidden];
if (!doReopen)
return;
if (_isHidden)
[self unhide: nil];
}
@end
int NSApplicationMain(int argc, const char *argv[]) {
#ifndef DARLING
__NSInitializeProcess(argc, argv);
#endif
NSAutoreleasePool *pool = [NSAutoreleasePool new];
NSBundle *bundle = [NSBundle mainBundle];
Class class = [bundle principalClass];
NSString *nibFile = [bundle infoDictionary][@"NSMainNibFile"];
#ifndef DARLING
if (argc > 1) {
NSMutableArray *arguments =
[NSMutableArray arrayWithCapacity: arg c - 1];
for (int i = 1; i < argc; i++)
if (argv[i][0] != '-')
[arguments addObject: [NSString stringWithUTF8String: argv[i]]];
else if (argv[i][1] == '-' && argv[i][2] == '\0')
break;
else // (argv[i][0] == '-' && argv[i] != "--")
if (*(int64_t *) argv[i] != *(int64_t *) "-NSOpen")
i++;
if ((argc = [arguments count]))
[[NSUserDefaults standardUserDefaults]
setObject: ((argc == 1) ? [arguments lastObject]
: arguments)
forKey: @"NSOpen"];
}
[NSClassFromString(@"Win32RunningCopyPipe")
performSelector: @selector(startRunningCopyPipe)];
#endif
if (class == Nil)
class = [NSApplication class];
[class sharedApplication];
nibFile = [nibFile stringByDeletingPathExtension];
if (![NSBundle loadNibNamed: nibFile owner: NSApp])
NSLog(@"Unable to load main nib file %@", nibFile);
[pool release];
[NSApp run];
return 0;
}
void NSUpdateDynamicServices(void) {
NSUnimplementedFunction();
}
BOOL NSPerformService(NSString *itemName, NSPasteboard *pasteboard) {
NSUnimplementedFunction();
return NO;
}