FIXED: CPView received multiple -viewDidHide/Unhide messages

FIXED: when a view is hidden by a hidden superview and added to a non
hidden superview, the -viewDidUnhide message is sent.

With test : CPViewTest.j
This commit is contained in:
cacaodev
2016-06-18 11:29:47 +02:00
parent e134f910aa
commit 2b0a642827
2 changed files with 190 additions and 63 deletions
+53 -35
View File
@@ -172,6 +172,7 @@ var CPViewHighDPIDrawingEnabled = YES;
CPArray _registeredDraggedTypesArray;
BOOL _isHidden;
BOOL _isHiddenOrHasHiddenAncestor;
BOOL _hitTests;
BOOL _clipsToBounds;
@@ -372,6 +373,7 @@ var CPViewHighDPIDrawingEnabled = YES;
_opacity = 1.0;
_isHidden = NO;
_isHiddenOrHasHiddenAncestor = NO;
_hitTests = YES;
_hierarchyScaleSize = CGSizeMake(1.0 , 1.0);
@@ -598,7 +600,7 @@ var CPViewHighDPIDrawingEnabled = YES;
[aSubview _removeFromSuperview];
// Set ourselves as the superview.
aSubview._superview = self;
[aSubview _setSuperview:self];
}
if (anIndex === CPNotFound || anIndex >= count)
@@ -623,11 +625,6 @@ var CPViewHighDPIDrawingEnabled = YES;
[aSubview setNextResponder:self];
[aSubview _scaleSizeUnitSquareToSize:[self _hierarchyScaleSize]];
// If the subview is not hidden and one of its ancestors is hidden,
// notify the subview that it is now hidden.
if (![aSubview isHidden] && [self isHiddenOrHasHiddenAncestor])
[aSubview _notifyViewDidHide];
[aSubview viewDidMoveToSuperview];
// Set the subview's window to our own.
@@ -697,13 +694,10 @@ var CPViewHighDPIDrawingEnabled = YES;
// If the view is not hidden and one of its ancestors is hidden,
// notify the view that it is now unhidden.
if (!_isHidden && [_superview isHiddenOrHasHiddenAncestor])
[self _notifyViewDidUnhide];
[self _setSuperview:nil];
[self _notifyWindowDidResignKey];
[self _notifyViewDidResignFirstResponder];
_superview = nil;
}
/*!
@@ -1599,7 +1593,7 @@ var CPViewHighDPIDrawingEnabled = YES;
// FIXME: Should we return to visibility? This breaks in FireFox, Opera, and IE.
// _DOMElement.style.visibility = (_isHidden = aFlag) ? "hidden" : "visible";
_isHidden = aFlag;
#if PLATFORM(DOM)
_DOMElement.style.display = _isHidden ? "none" : "block";
@@ -1622,33 +1616,61 @@ var CPViewHighDPIDrawingEnabled = YES;
while (view = [view superview]);
}
[self _notifyViewDidHide];
[self _recursiveGainedHiddenAncestor];
}
else
{
[self setNeedsDisplay:YES];
[self _notifyViewDidUnhide];
[self _recursiveLostHiddenAncestor];
}
_isHidden = aFlag;
}
- (void)_notifyViewDidHide
- (void)_setSuperview:(CPView)aSuperview
{
[self viewDidHide];
var hasOldSuperview = (_superview !== nil),
hasNewSuperview = (aSuperview !== nil),
oldSuperviewIsHidden = hasOldSuperview && [_superview isHiddenOrHasHiddenAncestor],
newSuperviewIsHidden = hasNewSuperview && [aSuperview isHiddenOrHasHiddenAncestor];
var count = [_subviews count];
if (!newSuperviewIsHidden && oldSuperviewIsHidden)
[self _recursiveLostHiddenAncestor];
while (count--)
[_subviews[count] _notifyViewDidHide];
if (newSuperviewIsHidden && !oldSuperviewIsHidden)
[self _recursiveGainedHiddenAncestor];
_superview = aSuperview;
}
- (void)_notifyViewDidUnhide
- (void)_recursiveLostHiddenAncestor
{
[self viewDidUnhide];
if (_isHiddenOrHasHiddenAncestor)
{
_isHiddenOrHasHiddenAncestor = NO;
[self viewDidUnhide];
}
var count = [_subviews count];
[_subviews enumerateObjectsUsingBlock:function(view, idx, stop)
{
[view _recursiveLostHiddenAncestor];
}];
}
while (count--)
[_subviews[count] _notifyViewDidUnhide];
- (void)_recursiveGainedHiddenAncestor
{
if (!_isHidden)
{
[self viewDidHide];
}
_isHiddenOrHasHiddenAncestor = YES;
[_subviews enumerateObjectsUsingBlock:function(view, idx, stop)
{
[view _recursiveGainedHiddenAncestor];
}];
}
/*!
@@ -1718,12 +1740,7 @@ var CPViewHighDPIDrawingEnabled = YES;
*/
- (BOOL)isHiddenOrHasHiddenAncestor
{
var view = self;
while (view && ![view isHidden])
view = [view superview];
return view !== nil;
return _isHiddenOrHasHiddenAncestor;
}
/*!
@@ -3494,14 +3511,14 @@ setBoundsOrigin:
Invoked automatically when the views geometry changes such that its tracking areas need to be recalculated.
You should override this method to remove out of date tracking areas and add recomputed tracking areas;
Cocoa calls this on every view, whereas they have tracking area(s) or not.
Cappuccino behaves differently :
- updateTrackingAreas is called when placing a view in the view hierarchy (that is in a window)
- if you have only CPTrackingInVisibleRect tracking areas attached to a view, it will not be called again (until you move the view in the hierarchy)
- if you have at least one non-CPTrackingInVisibleRect tracking area attached, it will be called every time the view geometry could be modified
You don't have to touch to CPTrackingInVisibleRect tracking areas, they will be automatically updated
Please note that it is the owner of a tracking area who is called for updateTrackingAreas.
But, if a view without any tracking area is inserted in the view hierarchy (that is, in a window), the view is called for updateTrackingAreas.
This enables you to use updateTrackingArea to initially attach your tracking areas to the view.
@@ -3513,16 +3530,16 @@ setBoundsOrigin:
/*!
This utility method is intended for CPView subclasses overriding updateTrackingAreas
Typical use would be :
- (void)updateTrackingAreas
{
[self removeAllTrackingAreas];
... add your specific updated tracking areas ...
}
*/
- (void)removeAllTrackingAreas
{
@@ -3707,6 +3724,7 @@ var CPViewAutoresizingMaskKey = @"CPViewAutoresizingMask",
#endif
[self setHidden:[aCoder decodeBoolForKey:CPViewIsHiddenKey]];
_isHiddenOrHasHiddenAncestor = NO;
if ([aCoder containsValueForKey:CPViewOpacityKey])
[self setAlphaValue:[aCoder decodeIntForKey:CPViewOpacityKey]];
+137 -28
View File
@@ -895,70 +895,167 @@ var updateTrackingAreasCalls,
[self assert:nil equals:[view effectiveAppearance]];
}
- (void)testViewDidHideDidUnhide
{
var expectedResult = [@"viewDidHide_view1", @"viewDidUnhide_view1"];
[view1 setHidden:YES];
[view1 setHidden:NO];
[self assert:expectedResult equals:methodCalled];
}
- (void)testViewGainedHiddenAncestor
{
var expectedResult = [@"viewDidHide_view1",
@"viewWillMoveToSuperview_view3",
@"viewDidMoveToSuperview_view3",
@"viewWillMoveToSuperview_view2",
@"viewDidHide_view2",
@"viewDidHide_view3",
@"viewDidMoveToSuperview_view2"];
[view1 setHidden:YES];
[view2 addSubview:view3];
[view1 addSubview:view2];
[self assert:expectedResult equals:methodCalled];
[self assertTrue: [view2 isHiddenOrHasHiddenAncestor] message:@"Expected isHiddenOrHasHiddenAncestor = YES"];
[self assertTrue: [view3 isHiddenOrHasHiddenAncestor] message:@"Expected isHiddenOrHasHiddenAncestor = YES"];
[self assertFalse:[view2 isHidden]];
[self assertFalse:[view3 isHidden]];
}
- (void)testRemoveViewsHiddenByAncestor
{
var expectedResult = @[
@"viewWillMoveToSuperview_view2",
@"viewDidUnhide_view2",
@"viewDidUnhide_view3",
@"viewDidMoveToSuperview_view2",
@"viewWillMoveToWindow_view2",
@"viewWillMoveToWindow_view3",
@"viewDidMoveToWindow_view3",
@"viewDidMoveToWindow_view2"
];
[view1 setHidden:YES];
[view1 addSubview:view2];
[view2 addSubview:view3];
[self assertTrue: [view2 isHiddenOrHasHiddenAncestor] message:@"Expected isHiddenOrHasHiddenAncestor = YES" ];
[self assertFalse:[view2 isHidden]];
[self assertTrue: [view3 isHiddenOrHasHiddenAncestor] message:@"Expected isHiddenOrHasHiddenAncestor = YES" ];
[self assertFalse:[view3 isHidden]];
methodCalled = [];
[view2 removeFromSuperview];
[self assertFalse: [view2 isHiddenOrHasHiddenAncestor] message:@"Expected isHiddenOrHasHiddenAncestor = NO" ];
[self assertFalse: [view3 isHiddenOrHasHiddenAncestor] message:@"Expected isHiddenOrHasHiddenAncestor = NO" ];
[self assert:expectedResult equals:methodCalled];
}
- (void)testRemoveHiddenView
{
var expectedResult = [@"viewWillMoveToSuperview_view2", @"viewDidMoveToSuperview_view2", @"viewWillMoveToWindow_view2", @"viewDidMoveToWindow_view2"];
[view1 addSubview:view2];
[view2 setHidden:YES];
methodCalled = [];
[view2 removeFromSuperview];
[self assertTrue: [view2 isHiddenOrHasHiddenAncestor] message:@"Expected isHiddenOrHasHiddenAncestor = YES" ];
[self assert:expectedResult equals:methodCalled];
}
- (void)testLostHiddenAncestorAfterMovingToNewSuperview
{
var expectedResult = @[
@"viewWillMoveToSuperview_view2",
@"viewDidMoveToSuperview_view2",
@"viewDidHide_view1",
@"viewDidHide_view2",
@"viewWillMoveToSuperview_view2",
@"viewDidUnhide_view2",
@"viewDidMoveToSuperview_view2"
];
[view1 addSubview:view2];
[view1 setHidden:YES];
[view3 addSubview:view2];
[self assert:expectedResult equals:methodCalled];
}
// TrackingAreaAdditions
- (void)testTrackingAreas
{
var trackingArea = [[CPTrackingArea alloc] initWithRect:CGRectMakeZero() options:CPTrackingMouseEnteredAndExited | CPTrackingActiveInKeyWindow | CPTrackingInVisibleRect owner:self userInfo:nil];
[self assert:0 equals:[[view trackingAreas] count] message:@"Initially, a view has no tracking area"];
//
[view addTrackingArea:trackingArea];
[self assert:1 equals:[[view trackingAreas] count] message:@"After adding a tracking area"];
[self assert:view equals:[trackingArea view] message:@"Tracking area should be linked to view"];
//
[view removeTrackingArea:trackingArea];
[self assert:0 equals:[[view trackingAreas] count] message:@"After removing the only tracking area"];
[self assert:nil equals:[trackingArea view] message:@"Tracking area should be unlinked"];
//
[view addTrackingArea:trackingArea];
[view addTrackingArea:trackingArea];
[view addTrackingArea:trackingArea];
[self assert:1 equals:[[view trackingAreas] count] message:@"Adding the same tracking area multiple times should add it once"];
[self assert:view equals:[trackingArea view] message:@"Tracking area should be linked to view"];
var trackingArea2 = [[CPTrackingArea alloc] initWithRect:CGRectMakeZero() options:CPTrackingMouseEnteredAndExited | CPTrackingActiveInKeyWindow owner:self userInfo:nil];
//
[view addTrackingArea:trackingArea2];
[self assert:2 equals:[[view trackingAreas] count] message:@"After adding a second tracking area"];
[self assert:view equals:[trackingArea2 view] message:@"Tracking area should be linked to view"];
//
[view removeAllTrackingAreas];
[self assert:0 equals:[[view trackingAreas] count] message:@"After removing all tracking areas"];
[self assert:nil equals:[trackingArea view] message:@"Tracking area should be unlinked"];
[self assert:nil equals:[trackingArea2 view] message:@"Tracking area should be unlinked"];
//
[view addTrackingArea:trackingArea];
var contentView = [window contentView];
[contentView addSubview:view];
[self assert:0 equals:updateTrackingAreasCalls message:@"Putting a view with a CPTrackingAreaInVisibleRect in a window should not call updateTrackingAreas"];
[view removeFromSuperview];
//
[view addTrackingArea:trackingArea2];
[contentView addSubview:view];
[self assert:1 equals:updateTrackingAreasCalls message:@"Putting a view with a non CPTrackingAreaInVisibleRect in a window should call updateTrackingAreas"];
[view removeAllTrackingAreas];
//
var viewTA = [[CPTrackingAreaView alloc] initWithFrame:CGRectMakeZero()];
updateTrackingAreasCalls = 0;
@@ -966,17 +1063,17 @@ var updateTrackingAreasCalls,
[self assert:1 equals:updateTrackingAreasCalls message:@"Putting a view with no tracking areas in a window should call updateTrackingAreas"];
//
updateTrackingAreasCalls = 0;
[viewTA addTrackingArea:trackingArea];
[viewTA setFrame:CGRectMake(10, 10, 10, 10)];
[self assert:0 equals:updateTrackingAreasCalls message:@"Changing geometry of a view with a CPTrackingAreaInVisibleRect should not call updateTrackingAreas"];
//
updateTrackingAreasCalls = 0;
[viewTA addTrackingArea:trackingArea2];
[viewTA setFrame:CGRectMake(20, 20, 20, 20)];
[self assert:1 equals:updateTrackingAreasCalls message:@"Changing geometry of a view with a non CPTrackingAreaInVisibleRect should call updateTrackingAreas"];
@@ -1048,7 +1145,7 @@ var updateTrackingAreasCalls,
[viewTA removeAllTrackingAreas];
[viewTA addTrackingArea:trackingAreaAllWithDrag];
// Mouse enters the tracking area while dragging (option set)
[self moveMouseAtPoint:CGPointMake(21, 21) dragging:YES];
@@ -1103,7 +1200,7 @@ var updateTrackingAreasCalls,
[self assert:0 equals:mouseExitedCalls message:@"Mouse entering inner tracking area should not call mouseExited"];
[self assert:1 equals:mouseMovedCalls message:@"Mouse entering inner tracking area should call mouseMoved"];
[self assert:1 equals:cursorUpdateCalls message:@"Mouse entering inner tracking area should call cursorUpdate"];
// Mouse moves in inner view
[self moveMouseAtPoint:CGPointMake(27, 27) dragging:NO];
@@ -1112,7 +1209,7 @@ var updateTrackingAreasCalls,
[self assert:0 equals:mouseExitedCalls message:@"Mouse moving in inner tracking area should not call mouseExited"];
[self assert:2 equals:mouseMovedCalls message:@"Mouse moving in inner tracking area should call mouseMoved for both views"];
[self assert:0 equals:cursorUpdateCalls message:@"Mouse moving in inner tracking area should not call cursorUpdate"];
// Mouse leaves inner view but remains in outer view
[self moveMouseAtPoint:CGPointMake(36, 36) dragging:NO];
@@ -1121,7 +1218,7 @@ var updateTrackingAreasCalls,
[self assert:1 equals:mouseExitedCalls message:@"Mouse moving from inner to outer tracking area should call mouseExited (for inner)"];
[self assert:1 equals:mouseMovedCalls message:@"Mouse moving from inner to outer tracking area should call mouseMoved (for outer)"];
[self assert:1 equals:cursorUpdateCalls message:@"Mouse moving from inner to outer tracking area should call cursorUpdate (for outer)"];
[self assert:innerViewTA equals:involvedViewForMouseExited message:@"Inner view should receive mouseExited"];
[self assert:viewTA equals:involvedViewForCursorUpdate message:@"Outer view should receive cursorUpdate"];
@@ -1278,7 +1375,7 @@ var updateTrackingAreasCalls,
[self mouseUpAtPoint:CGPointMake(10, 10)];
[self assert:[CPCursor arrowCursor] equals:[CPCursor currentCursor] message:@"Step 1.7 : cursor should be an arrow"];
//
[self moveMouseAtPoint:CGPointMake(1, 1) dragging:NO];
@@ -1292,7 +1389,7 @@ var updateTrackingAreasCalls,
[self moveMouseAtPoint:CGPointMake(5, 5) dragging:NO];
[self assert:[CPCursor arrowCursor] equals:[CPCursor currentCursor] message:@"Step 2.1 : cursor should be an arrow"];
// Step 2.2 : inside the superview / outside the subview
[self moveMouseAtPoint:CGPointMake(15, 15) dragging:NO];
@@ -1310,7 +1407,7 @@ var updateTrackingAreasCalls,
[self moveMouseAtPoint:CGPointMake(15, 15) dragging:NO];
[self assert:[CPCursor crosshairCursor] equals:[CPCursor currentCursor] message:@"Step 2.4 : cursor should be a crosshair"];
// Step 2.5 : outside the superview
[self moveMouseAtPoint:CGPointMake(5, 5) dragging:NO];
@@ -1355,14 +1452,14 @@ var updateTrackingAreasCalls,
[self moveMouseAtPoint:CGPointMake(5, 5) dragging:NO];
[self assert:[CPCursor arrowCursor] equals:[CPCursor currentCursor] message:@"Step 3.5 : cursor should be an arrow"];
}
- (void)testTrackingAreasLiveViewHierarchyModification
{
// 1. viewB inside viewA with mouseEntered removing itself
var viewA = [[CPTrackingAreaViewWithCursorUpdate alloc] initWithFrame:CGRectMake(20, 20, 40, 40)]
var viewA = [[CPTrackingAreaViewWithCursorUpdate alloc] initWithFrame:CGRectMake(20, 20, 40, 40)],
viewB = [[CPTrackingAreaViewLiveRemoval alloc] initWithFrame:CGRectMake(10, 10, 20, 20)];
[[window contentView] setSubviews:[CPArray arrayWithObject:viewA]];
@@ -1691,6 +1788,18 @@ var updateTrackingAreasCalls,
[methodCalled addObject:string];
}
- (void)viewDidUnhide
{
var string = _cmd + @"_" + [self identifier];
[methodCalled addObject:string];
}
- (void)viewDidHide
{
var string = _cmd + @"_" + [self identifier];
[methodCalled addObject:string];
}
- (BOOL)acceptsFirstResponder
{
return YES;