Skip to content
This repository has been archived by the owner on Dec 5, 2019. It is now read-only.

Commit

Permalink
Merge remote-tracking branch 'origin/BS-1918'
Browse files Browse the repository at this point in the history
  • Loading branch information
jspahrsummers committed Apr 4, 2012
2 parents 8f15116 + 28f470d commit 0220f5d
Show file tree
Hide file tree
Showing 2 changed files with 181 additions and 56 deletions.
57 changes: 30 additions & 27 deletions Velvet/VELEventManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,29 @@ static void getEventRecognizersFromViewHierarchy (NSMutableArray *recognizers, i
}
}

/**
* Returns whether `event` is prevented from being delivered to `recognizer`
* by any other recognizer in `blockingRecognizers`.
*
* @param event The event whose delivery may be prevented.
* @param recognizer The event recognizer for which event delivery may be
* prevented.
* @param blockingRecognizers The event recognizers that have the opportunity
* to prevent event delivery.
*/
static BOOL eventIsPreventedToRecognizer(NSEvent *event, VELEventRecognizer *recognizer, NSArray *blockingRecognizers) {
for (VELEventRecognizer *blocker in blockingRecognizers) {
if (blocker == recognizer)
continue;

if ([blocker shouldPreventEventRecognizer:recognizer fromReceivingEvent:event] || [recognizer shouldBePreventedByEventRecognizer:blocker fromReceivingEvent:event]) {
return YES;
}
}

return NO;
}

/**
* Dispatches the given event to the recognizers at and after the given index.
* Returns whether the event should still be passed to the view.
Expand All @@ -81,15 +104,16 @@ static void getEventRecognizersFromViewHierarchy (NSMutableArray *recognizers, i
*
* @param event The event to dispatch.
* @param recognizers An array of event recognizers, with ancestors at the
* beginning of the array.
* beginning of the array. This will be modified by this function, so that
* prevented recognizers are removed.
* @param index The index into `recognizers` at which to begin event dispatch.
* If this index is out-of-bounds, the function returns `YES` immediately.
*/
static BOOL dispatchEventToRecognizersStartingAtIndex (NSEvent *event, NSArray *recognizers, NSUInteger index) {
static BOOL dispatchEventToRecognizersStartingAtIndex (NSEvent *event, NSMutableArray *recognizers, NSInteger index) {
NSCParameterAssert(event);
NSCParameterAssert(recognizers);

if (index >= recognizers.count)
if (index >= (NSInteger)recognizers.count)
return YES;

VELEventRecognizer *recognizer = [recognizers objectAtIndex:index];
Expand All @@ -101,7 +125,9 @@ static BOOL dispatchEventToRecognizersStartingAtIndex (NSEvent *event, NSArray *
dispatchToView &= dispatchEventToRecognizersStartingAtIndex(event, recognizers, index + 1);
}

if (!recognizer.shouldReceiveEventBlock || recognizer.shouldReceiveEventBlock(event)) {
if (eventIsPreventedToRecognizer(event, recognizer, recognizers)) {
[recognizers removeObjectAtIndex:index--];
} else if (!recognizer.shouldReceiveEventBlock || recognizer.shouldReceiveEventBlock(event)) {
if ([recognizer.eventsToIgnore containsObject:event]) {
[recognizer.eventsToIgnore removeObject:event];
} else {
Expand Down Expand Up @@ -447,29 +473,6 @@ - (BOOL)dispatchEvent:(NSEvent *)event toEventRecognizersForView:(id<VELBridgedV
if (!recognizers.count)
return YES;

// find recognizers that prevent each other (descendants first, so that they
// can prevent ancestors)
NSUInteger i = recognizers.count - 1;
do {
VELEventRecognizer *first = [recognizers objectAtIndex:i];

NSUInteger j = recognizers.count - 1;
do {
if (i == j) {
// this is the same recognizer, skip it
continue;
}

VELEventRecognizer *second = [recognizers objectAtIndex:j];
if ([first shouldPreventEventRecognizer:second fromReceivingEvent:event] || [second shouldBePreventedByEventRecognizer:first fromReceivingEvent:event]) {
[recognizers removeObjectAtIndex:j];

if (i > j)
--i;
}
} while (j-- > 0);
} while (i-- > 0);

return dispatchEventToRecognizersStartingAtIndex(event, recognizers, 0);
}

Expand Down
180 changes: 151 additions & 29 deletions VelvetTests/VELEventHandlingTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@ @interface DeduplicationTestRecognizer : VELEventRecognizer
@property (nonatomic, assign) NSUInteger expectedEventMask;
@end

@interface PreventionTestRecognizer : VELEventRecognizer
/**
* The last event that was passed to <handleEvent:> for this recognizer.
*/
@property (nonatomic, copy) NSEvent *lastEvent;
@end

@interface NSEvent (SystemDefinedEventCreation)
+ (NSEvent *)systemDefinedMouseEventAtLocation:(CGPoint)location mouseButtonStateMask:(NSUInteger)buttonStateMask mouseButtonStateChangedMask:(NSUInteger)buttonStateChangedMask;

Expand Down Expand Up @@ -166,25 +173,24 @@ @interface AlwaysKeyVELWindow : VELWindow
});
});

describe(@"deduplicating system-defined events", ^{
describe(@"event handling", ^{
__block VELWindow *window;
__block DeduplicationTestRecognizer *recognizer;

__block NSEvent *leftMouseDownEvent;
__block NSEvent *rightMouseUpEvent;
__block NSEvent *systemDefinedEvent;

void (^runEventLoop)(void) = ^{
[NSEvent performSelector:@selector(stopEventLoop) withObject:nil afterDelay:0.1];
[[NSApplication sharedApplication] run];
};

before(^{
window = [[AlwaysKeyVELWindow alloc] initWithContentRect:NSMakeRect(101, 223, 500, 1000)];
expect(window).not.toBeNil();

[window makeKeyAndOrderFront:nil];

recognizer = [[DeduplicationTestRecognizer alloc] init];
expect(recognizer).not.toBeNil();

recognizer.view = window.rootView;

__block NSInteger eventNumber = trueMouseEventNumberMinimum;

CGPoint location = CGPointMake(215, 657);
Expand Down Expand Up @@ -214,36 +220,142 @@ @interface AlwaysKeyVELWindow : VELWindow

leftMouseDownEvent = mouseEventAtWindowLocation(NSLeftMouseDown, location);
rightMouseUpEvent = mouseEventAtWindowLocation(NSRightMouseUp, location);

recognizer.expectedEventMask = NSLeftMouseDownMask | NSRightMouseUpMask;
});

after(^{
[NSEvent performSelector:@selector(stopEventLoop) withObject:nil afterDelay:0.1];
[[NSApplication sharedApplication] run];
describe(@"deduplicating system-defined events", ^{
__block DeduplicationTestRecognizer *recognizer;

// the recognizer should not be expecting any further events
expect(recognizer.expectedEventMask).toEqual(0);
before(^{
recognizer = [[DeduplicationTestRecognizer alloc] init];
expect(recognizer).not.toBeNil();

recognizer = nil;
});
recognizer.view = window.rootView;

it(@"should deduplicate an NSSystemDefined event arriving after mouse events", ^{
[NSApp postEvent:leftMouseDownEvent atStart:NO];
[NSApp postEvent:rightMouseUpEvent atStart:NO];
[NSApp postEvent:systemDefinedEvent atStart:NO];
});
recognizer.expectedEventMask = NSLeftMouseDownMask | NSRightMouseUpMask;
});

after(^{
runEventLoop();

// the recognizer should not be expecting any further events
expect(recognizer.expectedEventMask).toEqual(0);

recognizer = nil;
});

it(@"should deduplicate an NSSystemDefined event arriving after mouse events", ^{
[NSApp postEvent:leftMouseDownEvent atStart:NO];
[NSApp postEvent:rightMouseUpEvent atStart:NO];
[NSApp postEvent:systemDefinedEvent atStart:NO];
});

it(@"should deduplicate an NSSystemDefined event queued before mouse events", ^{
[NSApp postEvent:systemDefinedEvent atStart:NO];
[NSApp postEvent:leftMouseDownEvent atStart:NO];
[NSApp postEvent:rightMouseUpEvent atStart:NO];
it(@"should deduplicate an NSSystemDefined event queued before mouse events", ^{
[NSApp postEvent:systemDefinedEvent atStart:NO];
[NSApp postEvent:leftMouseDownEvent atStart:NO];
[NSApp postEvent:rightMouseUpEvent atStart:NO];
});

it(@"should deduplicate an NSSystemDefined event arriving in-between mouse events", ^{
[NSApp postEvent:leftMouseDownEvent atStart:NO];
[NSApp postEvent:systemDefinedEvent atStart:NO];
[NSApp postEvent:rightMouseUpEvent atStart:NO];
});
});

it(@"should deduplicate an NSSystemDefined event arriving in-between mouse events", ^{
[NSApp postEvent:leftMouseDownEvent atStart:NO];
[NSApp postEvent:systemDefinedEvent atStart:NO];
[NSApp postEvent:rightMouseUpEvent atStart:NO];
describe(@"event prevention", ^{
__block PreventionTestRecognizer *firstRecognizer;
__block PreventionTestRecognizer *secondRecognizer;
__block PreventionTestRecognizer *thirdRecognizer;

__block __weak PreventionTestRecognizer *weakFirstRecognizer;
__block __weak PreventionTestRecognizer *weakSecondRecognizer;

before(^{
VELView *innerView = [[VELView alloc] initWithFrame:window.rootView.bounds];
[window.rootView addSubview:innerView];

firstRecognizer = [[PreventionTestRecognizer alloc] init];
expect(firstRecognizer).not.toBeNil();
firstRecognizer.view = window.rootView;

secondRecognizer = [[PreventionTestRecognizer alloc] init];
expect(secondRecognizer).not.toBeNil();
secondRecognizer.view = innerView;

thirdRecognizer = [[PreventionTestRecognizer alloc] init];
expect(thirdRecognizer).not.toBeNil();
thirdRecognizer.view = innerView;

weakFirstRecognizer = firstRecognizer;
weakSecondRecognizer = secondRecognizer;
});

after(^{
firstRecognizer = nil;
secondRecognizer = nil;
thirdRecognizer = nil;
});

it(@"should prevent at the point of delivery", ^{
__block BOOL shouldPreventRecognizerCalledAfterHandlingEvent = NO;

firstRecognizer.shouldPreventEventRecognizerBlock = ^ BOOL (VELEventRecognizer *recognizer, NSEvent *event) {
if (recognizer == weakSecondRecognizer && [event isEqual:weakFirstRecognizer.lastEvent]) {
shouldPreventRecognizerCalledAfterHandlingEvent = YES;
return YES;
}

return NO;
};

[NSApp postEvent:leftMouseDownEvent atStart:NO];

runEventLoop();

expect(shouldPreventRecognizerCalledAfterHandlingEvent).toBeTruthy();
expect(firstRecognizer.lastEvent).not.toBeNil();
expect(secondRecognizer.lastEvent).toBeNil();
});

it(@"should be prevented at the point of delivery", ^{
__block BOOL shouldBePreventedCalledAfterHandlingEvent = NO;

secondRecognizer.shouldBePreventedByEventRecognizerBlock = ^ BOOL (VELEventRecognizer *recognizer, NSEvent *event) {
if (recognizer == weakFirstRecognizer && [event isEqual:weakFirstRecognizer.lastEvent]) {
shouldBePreventedCalledAfterHandlingEvent = YES;
return YES;
}

return NO;
};

[NSApp postEvent:leftMouseDownEvent atStart:NO];

runEventLoop();

expect(shouldBePreventedCalledAfterHandlingEvent).toBeTruthy();
expect(firstRecognizer.lastEvent).not.toBeNil();
expect(secondRecognizer.lastEvent).toBeNil();
});

it(@"prevented recognizers should not prevent others", ^{
firstRecognizer.shouldPreventEventRecognizerBlock = ^ BOOL (VELEventRecognizer *recognizer, NSEvent *event) {
return (recognizer == weakSecondRecognizer);
};

secondRecognizer.shouldPreventEventRecognizerBlock = ^ BOOL (VELEventRecognizer *recognizer, NSEvent *event) {
expect(recognizer).not.toEqual(thirdRecognizer);
return (recognizer == thirdRecognizer);
};

[NSApp postEvent:leftMouseDownEvent atStart:NO];

runEventLoop();

expect(firstRecognizer.lastEvent).not.toBeNil();
expect(secondRecognizer.lastEvent).toBeNil();
expect(thirdRecognizer.lastEvent).not.toBeNil();
});
});
});

Expand Down Expand Up @@ -316,3 +428,13 @@ - (BOOL)isKeyWindow {
return YES;
}
@end

@implementation PreventionTestRecognizer
@synthesize lastEvent = m_lastEvent;

- (BOOL)handleEvent:(NSEvent *)event {
self.lastEvent = event;
return [super handleEvent:event];
}

@end

0 comments on commit 0220f5d

Please sign in to comment.