diff --git a/AppKit/CPView.j b/AppKit/CPView.j index a142e82a9..dd7304a50 100644 --- a/AppKit/CPView.j +++ b/AppKit/CPView.j @@ -112,6 +112,11 @@ CPViewHeightSizable = 16; */ CPViewMaxYMargin = 32; +_CPViewWillAppearNotification = @"CPViewWillAppearNotification"; +_CPViewDidAppearNotification = @"CPViewDidAppearNotification"; +_CPViewWillDisappearNotification = @"CPViewWillDisappearNotification"; +_CPViewDidDisappearNotification = @"CPViewDidDisappearNotification"; + CPViewBoundsDidChangeNotification = @"CPViewBoundsDidChangeNotification"; CPViewFrameDidChangeNotification = @"CPViewFrameDidChangeNotification"; @@ -404,7 +409,6 @@ var CPViewHighDPIDrawingEnabled = YES; return self; } - /*! Sets the tooltip for the receiver. @@ -599,6 +603,7 @@ var CPViewHighDPIDrawingEnabled = YES; // Remove the view from its previous superview. [aSubview _removeFromSuperview]; + [aSubview _postViewWillAppearNotification]; // Set ourselves as the superview. [aSubview _setSuperview:self]; } @@ -685,6 +690,7 @@ var CPViewHighDPIDrawingEnabled = YES; [[self window] _dirtyKeyViewLoop]; [_superview willRemoveSubview:self]; + [self _postViewWillDisappearNotification]; [_superview._subviews removeObjectIdenticalTo:self]; @@ -1616,18 +1622,40 @@ var CPViewHighDPIDrawingEnabled = YES; while (view = [view superview]); } + [self _postViewWillDisappearNotification]; [self _recursiveGainedHiddenAncestor]; } else { [self setNeedsDisplay:YES]; + [self _postViewWillAppearNotification]; [self _recursiveLostHiddenAncestor]; } _isHidden = aFlag; } +- (void)_postViewWillAppearNotification +{ + [[CPNotificationCenter defaultCenter] postNotificationName:_CPViewWillAppearNotification object:self userInfo:nil]; +} + +- (void)_postViewDidAppearNotification +{ + [[CPNotificationCenter defaultCenter] postNotificationName:_CPViewDidAppearNotification object:self userInfo:nil]; +} + +- (void)_postViewWillDisappearNotification +{ + [[CPNotificationCenter defaultCenter] postNotificationName:_CPViewWillDisappearNotification object:self userInfo:nil]; +} + +- (void)_postViewDidDisappearNotification +{ + [[CPNotificationCenter defaultCenter] postNotificationName:_CPViewDidDisappearNotification object:self userInfo:nil]; +} + - (void)_setSuperview:(CPView)aSuperview { var hasOldSuperview = (_superview !== nil), @@ -1636,12 +1664,18 @@ var CPViewHighDPIDrawingEnabled = YES; newSuperviewIsHidden = hasNewSuperview && [aSuperview isHiddenOrHasHiddenAncestor]; if (!newSuperviewIsHidden && oldSuperviewIsHidden) - [self _recursiveLostHiddenAncestor]; + [self _recursiveLostHiddenAncestor]; if (newSuperviewIsHidden && !oldSuperviewIsHidden) [self _recursiveGainedHiddenAncestor]; _superview = aSuperview; + + if (hasOldSuperview) + [self _postViewDidDisappearNotification]; + + if (hasNewSuperview) + [self _postViewDidAppearNotification]; } - (void)_recursiveLostHiddenAncestor @@ -3669,7 +3703,6 @@ var CPViewAutoresizingMaskKey = @"CPViewAutoresizingMask", _window = [aCoder decodeObjectForKey:CPViewWindowKey]; _superview = [aCoder decodeObjectForKey:CPViewSuperviewKey]; - // We have to manually add the subviews so that they will receive // viewWillMoveToSuperview: and viewDidMoveToSuperview: _subviews = []; diff --git a/AppKit/CPViewController.j b/AppKit/CPViewController.j index 254fdfb4c..2c8d4a0de 100644 --- a/AppKit/CPViewController.j +++ b/AppKit/CPViewController.j @@ -213,7 +213,7 @@ var CPViewControllerCachedCibs; if (anError == nil) { var data = [CPData dataWithRawString:aData], - aCib = [[CPCib alloc] _initWithData:data bundle:_cibBundle cibName:_cibName]; + aCib = [[CPCib alloc] _initWithData:data bundle:_cibBundle cibName:_cibName]; [CPViewControllerCachedCibs setObject:aCib forKey:_cibName]; [aCib instantiateCibWithExternalNameTable:_cibExternalNameTable]; @@ -286,13 +286,16 @@ var CPViewControllerCachedCibs; - (void)_viewDidLoad { - [self _viewDidLoadWithCompletionHandler:function() { - [self viewDidLoad]; - }]; + [self willChangeValueForKey:"isViewLoaded"]; + [self viewDidLoad]; + _isViewLoaded = YES; + [self didChangeValueForKey:"isViewLoaded"]; } - (void)_viewDidLoadWithCompletionHandler:(Function)aHandler { + [self _registerOrUnregister:YES notificationsForView:_view]; + [self willChangeValueForKey:"isViewLoaded"]; aHandler(_view, nil); _isViewLoaded = YES; @@ -325,6 +328,9 @@ var CPViewControllerCachedCibs; { var willChangeIsViewLoaded = (_isViewLoaded == NO && aView != nil) || (_isViewLoaded == YES && aView == nil); + [self _registerOrUnregister:NO notificationsForView:_view]; + [self _registerOrUnregister:YES notificationsForView:aView]; + if (willChangeIsViewLoaded) [self willChangeValueForKey:"isViewLoaded"]; @@ -340,6 +346,30 @@ var CPViewControllerCachedCibs; return NO; } +- (void)_registerOrUnregister:(BOOL)shouldRegister notificationsForView:(CPView)aView +{ + if (aView === nil) + return; + + var center = [CPNotificationCenter defaultCenter], + notifs_to_sel = @{_CPViewWillAppearNotification : @"viewWillAppear", + _CPViewDidAppearNotification : @"viewDidAppear", + _CPViewWillDisappearNotification : @"viewWillDisappear", + _CPViewDidDisappearNotification : @"viewDidDisappear"}; + + [notifs_to_sel enumerateKeysAndObjectsUsingBlock:function(notif, selString, stop) + { + var selector = CPSelectorFromString(selString); + if ([self implementsSelector:selector]) + { + if (shouldRegister) + [center addObserver:self selector:selector name:notif object:aView]; + else + [center removeObserver:self name:notif object:aView]; + } + }]; +} + @end diff --git a/Tests/AppKit/BundleTest/Info.plist b/Tests/AppKit/BundleTest/Info.plist new file mode 100644 index 000000000..bb4429725 --- /dev/null +++ b/Tests/AppKit/BundleTest/Info.plist @@ -0,0 +1,10 @@ + + + + + Main cib file base name + MainMenu.cib + CPBundleName + BundleTest + + diff --git a/Tests/AppKit/BundleTest/Resources/NSViewController.cib b/Tests/AppKit/BundleTest/Resources/NSViewController.cib new file mode 100644 index 000000000..45f5c8599 --- /dev/null +++ b/Tests/AppKit/BundleTest/Resources/NSViewController.cib @@ -0,0 +1 @@ +280NPLIST;1.0;D;K;4;$topD;K;18;CPCibObjectDataKeyD;K;6;CP$UIDd;1;2E;E;K;8;$objectsA;S;5;$nullD;K;10;$classnameS;16;_CPCibObjectDataK;8;$classesA;S;16;_CPCibObjectDataS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;1;1E;K;28;_CPCibObjectDataNamesKeysKeyD;K;6;CP$UIDd;1;0E;K;30;_CPCibObjectDataNamesValuesKeyD;K;6;CP$UIDd;1;0E;K;30;_CPCibObjectDataClassesKeysKeyD;K;6;CP$UIDd;1;0E;K;32;_CPCibObjectDataClassesValuesKeyD;K;6;CP$UIDd;1;0E;K;30;_CPCibObjectDataConnectionsKeyD;K;6;CP$UIDd;1;4E;K;28;_CPCibObjectDataFrameworkKeyD;K;6;CP$UIDd;1;0E;K;26;_CPCibObjectDataNextOidKeyD;K;6;CP$UIDd;1;5E;K;30;_CPCibObjectDataObjectsKeysKeyD;K;6;CP$UIDd;1;6E;K;32;_CPCibObjectDataObjectsValuesKeyD;K;6;CP$UIDd;1;7E;K;26;_CPCibObjectDataOidKeysKeyD;K;6;CP$UIDd;1;8E;K;28;_CPCibObjectDataOidValuesKeyD;K;6;CP$UIDd;1;9E;K;28;_CPCibObjectDataFileOwnerKeyD;K;6;CP$UIDd;2;11E;K;33;_CPCibObjectDataVisibleWindowsKeyD;K;6;CP$UIDd;2;13E;E;D;K;10;$classnameS;7;CPArrayK;8;$classesA;S;7;CPArrayS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;D;K;6;CP$UIDd;2;15E;E;E;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;E;E;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;D;K;6;CP$UIDd;2;16E;D;K;6;CP$UIDd;2;18E;E;E;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;D;K;6;CP$UIDd;2;11E;D;K;6;CP$UIDd;2;11E;E;E;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;E;E;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;E;E;D;K;10;$classnameS;18;_CPCibCustomObjectK;8;$classesA;S;18;_CPCibCustomObjectS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;2;10E;K;27;_CPCibCustomObjectClassNameD;K;6;CP$UIDd;2;19E;E;D;K;10;$classnameS;5;CPSetK;8;$classesA;S;5;CPSetS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;2;12E;K;15;CPSetObjectsKeyD;K;6;CP$UIDd;2;20E;E;D;K;10;$classnameS;20;CPCibOutletConnectorK;8;$classesA;S;20;CPCibOutletConnectorS;14;CPCibConnectorS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;2;14E;K;24;_CPCibConnectorSourceKeyD;K;6;CP$UIDd;2;11E;K;29;_CPCibConnectorDestinationKeyD;K;6;CP$UIDd;2;18E;K;23;_CPCibConnectorLabelKeyD;K;6;CP$UIDd;2;21E;E;D;K;6;$classD;K;6;CP$UIDd;2;10E;K;27;_CPCibCustomObjectClassNameD;K;6;CP$UIDd;2;22E;E;D;K;10;$classnameS;18;_CPCibClassSwapperK;8;$classesA;S;18;_CPCibClassSwapperS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;2;17E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;12;CPViewTagKeyD;K;6;CP$UIDd;2;23E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;2;24E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;2;24E;K;22;CPViewAutoresizingMaskD;K;6;CP$UIDd;2;25E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;1;0E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;2;26E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;2;27E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;2;27E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;2;28E;K;19;CPViewAppearanceKeyD;K;6;CP$UIDd;1;0E;K;22;CPViewTrackingAreasKeyD;K;6;CP$UIDd;2;29E;K;30;_CPCibClassSwapperClassNameKeyD;K;6;CP$UIDd;2;30E;K;38;_CPCibClassSwapperOriginalClassNameKeyD;K;6;CP$UIDd;2;31E;E;S;16;CPViewControllerD;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;E;E;S;4;viewS;13;CPApplicationd;1;0S;20;{{0, 0}, {480, 272}}d;2;12S;6;normalS;6;{1, 1}F;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;E;E;S;6;NSViewS;6;CPViewE;K;9;$archiverS;15;CPKeyedArchiverK;8;$versionS;6;100000E; \ No newline at end of file diff --git a/Tests/AppKit/BundleTest/Resources/NSViewController.xib b/Tests/AppKit/BundleTest/Resources/NSViewController.xib new file mode 100644 index 000000000..9a80f715c --- /dev/null +++ b/Tests/AppKit/BundleTest/Resources/NSViewController.xib @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/Tests/AppKit/CPViewControllerTest.j b/Tests/AppKit/CPViewControllerTest.j new file mode 100644 index 000000000..7ebed52fc --- /dev/null +++ b/Tests/AppKit/CPViewControllerTest.j @@ -0,0 +1,89 @@ +@import +@import + +var methodsCalled; + +@implementation CPViewControllerTest : OJTestCase +{ + CPBundle bundle; +} + +- (void)setUp +{ + bundle = [CPBundle bundleWithPath:@"Tests/AppKit/BundleTest"]; + [bundle loadWithDelegate:self]; +} + +- (void)bundleDidFinishLoading:(CPBundle)aBundle +{ + +} + +- (void)testViewControllerCallbacks +{ + methodsCalled = @[]; + + var expectedResult = @[@"viewDidLoad", + @"viewWillAppear", + @"viewDidAppear", + @"viewWillDisappear", + @"viewDidDisappear"]; + + [self assertTrue:[bundle isLoaded]]; + var viewController = [[ViewController alloc] initWithCibName:@"NSViewController.cib" bundle:bundle]; + [self assertNotNull:viewController]; + + var superview = [[CPView alloc] initWithFrame:CGRectMakeZero()]; + var view = [viewController view]; + + [superview addSubview:view]; + [view removeFromSuperview]; + [self assert:expectedResult equals:methodsCalled]; + + // Explicitely change the view. + methodsCalled = []; + expectedResult = @[@"viewWillAppear", + @"viewDidAppear", + @"viewWillDisappear", + @"viewDidDisappear"]; + + var newView = [[CPView alloc] initWithFrame:CGRectMake(0,0,100,100)]; + [viewController setView:newView]; + + [superview addSubview:newView]; + [newView removeFromSuperview]; + // Checks that with receive notifs from the new view and not from the old view. + [self assert:expectedResult equals:methodsCalled]; +} + +@end + +@implementation ViewController : CPViewController +{ +} + +- (void)viewDidLoad +{ + [methodsCalled addObject:_cmd]; +} + +- (void)viewDidAppear +{ + [methodsCalled addObject:_cmd]; +} + +- (void)viewWillAppear +{ + [methodsCalled addObject:_cmd]; +} + +- (void)viewDidDisappear +{ + [methodsCalled addObject:_cmd]; +} + +- (void)viewWillDisappear +{ + [methodsCalled addObject:_cmd]; +} +@end diff --git a/Tests/AppKit/CPViewTest.j b/Tests/AppKit/CPViewTest.j index 78812d2fc..93edeb549 100644 --- a/Tests/AppKit/CPViewTest.j +++ b/Tests/AppKit/CPViewTest.j @@ -905,6 +905,21 @@ var updateTrackingAreasCalls, [self assert:expectedResult equals:methodCalled]; } +- (void)testAddViewRemoveView +{ + var expectedResult = [@"viewWillMoveToSuperview_view2", + @"viewDidMoveToSuperview_view2", + @"viewWillMoveToSuperview_view2", + @"viewDidMoveToSuperview_view2", + @"viewWillMoveToWindow_view2", + @"viewDidMoveToWindow_view2"]; + + [view1 addSubview:view2]; + [view2 removeFromSuperview]; + + [self assert:expectedResult equals:methodCalled]; +} + - (void)testViewGainedHiddenAncestor { var expectedResult = [@"viewDidHide_view1", @@ -917,14 +932,16 @@ var updateTrackingAreasCalls, [view1 setHidden:YES]; [view2 addSubview:view3]; + CPLog.warn("will add view2"); [view1 addSubview:view2]; - [self assert:expectedResult equals:methodCalled]; - [self assertTrue: [view2 isHiddenOrHasHiddenAncestor] message:@"Expected isHiddenOrHasHiddenAncestor = YES"]; + [self assertTrue: [view2 isHiddenOrHasHiddenAncestor] message:@"Expected " + [view2 identifier] + "isHiddenOrHasHiddenAncestor = YES"]; [self assertTrue: [view3 isHiddenOrHasHiddenAncestor] message:@"Expected isHiddenOrHasHiddenAncestor = YES"]; [self assertFalse:[view2 isHidden]]; [self assertFalse:[view3 isHidden]]; + + [self assert:expectedResult equals:methodCalled]; } - (void)testRemoveViewsHiddenByAncestor