subclass NSApplication
That way you can catch preprocess NSEvents, collect the information you need,
and then your NSControl subclass can retrieve that information.
In my case, I use this method to avoid dangling field editors in my very large multi-screen UI.
@interface NSApplicationEventCatcher : NSApplication
{
}
- (void)sendEventDirectly:(NSEvent *)event;
+(void)setExcludedResponder:(NSResponder *)iResponder;
@end
- (void)sendEvent:(NSEvent *)event
{
// do some checking here (see example code below)
[super sendEvent:event];
}
in main(), instantiate NSApplicationEventCatcher first,
[NSApplicationEventCatcher sharedApplication];
before calling NSApplicationMain()
NSApplicationMain(argc, (const char **) argv);
now, here's some of the checking that I do in NSApplicationEventCatcher sendEvent override.
However, this is only one small part of that solution.
if ( [event type] == NSLeftMouseDown )
{
gVAppCancelAction = kVAppCancelOtherWindow;
//NSLog( @"before mouse down window %@ first responder %@", [[event window] description], [[[event window] firstResponder] description] );
if ( [event window] )
{
gVAppCancelAction = kVAppCancelMouseDown;
NSTextView *theFirstResponder = (NSTextView *)[[event window] firstResponder];
if ( theFirstResponder && sExcludedResponder != theFirstResponder )
sExcludedResponder = nil; // reset
if ( [theFirstResponder isKindOfClass:[NSTextView class]] )
{
NSPoint clickLocation;
// convert the mouse-down location into the view coords
clickLocation = [theFirstResponder convertPoint:[event locationInWindow]
fromView:nil];
// did the mouse-down occur in the item?
BOOL itemHit = NSPointInRect(clickLocation, [theFirstResponder bounds]);
id delegate = [(NSTextView *)theFirstResponder delegate];
if ( [delegate isKindOfClass: [NSComboBox class]] )
{
itemHit |= NSPointInRect(clickLocation, [delegate bounds]);
}
if (itemHit)
{
VLog::Log( kLogDbgNoteType, @"clicked on first responder %@", [[[event window] firstResponder] description] );
excludeResponder = theFirstResponder;
}
else
{
NSView *theContentView = [[event window] contentView];
if ( [theContentView isKindOfClass:[NSView class]] )
{
NSView *theHitView = [theContentView hitTest:[event locationInWindow]];
if ( theHitView == nil || theHitView == theContentView )
{
gVAppCancelAction = kVAppCancelLayerView;
}
else
{
gVAppCancelAction = kVAppCancelMouseDown;
if ( sExcludedResponder == theFirstResponder )
excludeResponder = theFirstResponder;
/*
if ( [theHitView isKindOfClass:[LayerView class]] )
{
NSView *theSuperview = [theHitView superview];
if ( theSuperview && [theSuperview isKindOfClass:[LayerView class]] )
{
// ignore VNumericKeypad-like views which are like pop-up dialog views on
// top of a LayerView superview.
gVAppCancelAction = kVAppCancelMouseDown;
if ( sExcludedResponder == theFirstResponder )
excludeResponder = theFirstResponder;
}
else
gVAppCancelAction = kVAppCancelLayerView;
}
else
{
if ( sExcludedResponder == theFirstResponder )
excludeResponder = theFirstResponder;
}
*/
}
}
}
}
}
}
And here is a related part:
for ( NSWindow *theWindow in [self windows] )
{
NSResponder *theResponder = [theWindow firstResponder];
if ( theResponder != theWindow && theResponder && theResponder != excludeResponder )
{
// tbd could also check for [theResponder isKindOfClass:[NSControl class]] and call abortEditing
if ( [theResponder isKindOfClass:[NSTextView class]] && [(NSTextView *)theResponder isFieldEditor] )
{
NSWindow *evwindow = [event window];
NSArray *childwindows = [theWindow childWindows];
if ( evwindow && [childwindows containsObject:evwindow] )
{
// pass through clicks on attached NSMenu or NSComboBox
VLog::Log( kLogDbgNoteType, @"clicked child event window %@, my window %@", evwindow, theWindow );
break;
}
VLog::Log( kLogDbgNoteType, @"NSApplicationEventCatcher before cancel first responder %@", [theResponder description] );
BOOL cancelSucceeded;
if ( evwindow != theWindow && gVAppCancelAction == kVAppCancelMouseDown )
{
gVAppCancelAction = kVAppCancelOtherWindow;
cancelSucceeded = [theWindow makeFirstResponder:theWindow];
gVAppCancelAction = kVAppCancelMouseDown;
}
else
cancelSucceeded =[theWindow makeFirstResponder:theWindow];
if ( !cancelSucceeded )
{
VLog::Log( kLogDbgNoteType, @"Application about to FORCE cancel field editor %@", [[theWindow firstResponder] description] );
[theWindow endEditingFor:nil];
}
VLog::Log( kLogDbgNoteType, @"NSApplicationEventCatcher after cancel first responder %@", [[theWindow firstResponder] description] );
}
}
}