Permalink
Browse files

Merge remote-tracking branch 'origin/BS-1918'

  • Loading branch information...
jspahrsummers committed Apr 4, 2012
2 parents 8f15116 + 28f470d commit 0220f5d851b6118e5fdb7c3969f2579c1968f59c
Showing with 181 additions and 56 deletions.
  1. +30 −27 Velvet/VELEventManager.m
  2. +151 −29 VelvetTests/VELEventHandlingTests.m
View
@@ -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.
@@ -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];
@@ -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 {
@@ -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);
}
@@ -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;
@@ -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);
@@ -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();
+ });
});
});
@@ -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.