Files
cappuccino/Tests/AppKit/CPWindowTest.j
T
David Richardson af4d9dd9b9 AppKit: Implement window zoom and title bar double-click
Implement Cocoa zoom API on CPWindow and enable zoom button for resizable windows. Zoom window on double-click where drag moves it.

* CPWindow.j:
  - Add zoom:, performZoom: and isZoomed, toggling between standard frame and user frame; treat frame within 4 points of standard frame as zoomed.
  - Add windowWillUseStandardFrame:defaultFrame: and windowShouldZoom:toFrame: to CPWindowDelegate; consult window subclass when delegate lacks them.
  - Take default frame from platform window's usable content frame; keep standard frame within minimum and maximum sizes and usable area.
  - Enable performZoom: and zoom: menu items only when window can zoom.

* _CPStandardWindowView.j: Enable zoom button for resizable windows and target window's zoom:.

* _CPWindowView.j: Call performZoom: on double-click where drag moves window.

* CPWindowTest.j: Add tests for zoom toggling, default frame, delegate refusal, maximum size, missing user frame, window subclass standard frame, performZoom: on fixed-size window, menu validation and title bar double-click.

* DropInCompatibilityTest.j: Add test for zoom button enabled only for resizable windows.
2026-11-08 14:17:08 -06:00

367 lines
12 KiB
Plaintext

@import <AppKit/CPWindow.j>
@import <AppKit/CPMenuItem.j>
@import <AppKit/_CPBorderlessWindowView.j>
@import <Foundation/CPURL.j>
@implementation CPWindowTest : OJTestCase
{
CPWindow _window @accessors(property=window);
}
- (void)setUp
{
// This will init the global var CPApp which are used internally in the AppKit
[[CPApplication alloc] init];
_window = [[CPWindow alloc] initWithContentRect:CGRectMake(0.0, 0.0, 1024.0, 768.0)
styleMask:CPWindowNotSizable];
}
- (void)testCanAllocWindow
{
[self assertTrue:!![self window]];
}
- (void)testThatNilIsInitialFirstResponder
{
[self assert:nil
equals:[[self window] initialFirstResponder]
message:@"The initial first responder must be null"];
}
- (void)testKeyViewLoop
{
var contentView = [[self window] contentView],
textField = [CPTextField textFieldWithStringValue:@"" placeholder:@"" width:100.0];
[textField setFrame:CGRectMake(100.0, 100.0, 100.0, 26.0)];
[contentView addSubview:textField];
var nextTextField = [CPTextField textFieldWithStringValue:@"" placeholder:@"" width:100];
[nextTextField setFrame:CGRectMake(100.0, 200.0, 100.0, 26.0)];
[contentView addSubview:nextTextField];
[[self window] recalculateKeyViewLoop];
// Test nextKeyView
[self assert:textField
equals:[contentView nextKeyView]
message:@"textField should be the next key view of contentView"];
[self assert:nextTextField
equals:[textField nextKeyView]
message:@"nextTextField should be the next key view of textField"];
[self assert:contentView
equals:[nextTextField nextKeyView]
message:@"contentView should be the next key view of nextTextField"];
// Test previousKeyView
[self assert:contentView
equals:[textField previousKeyView]
message:@"contentView should be the previous key view of textField"];
[self assert:textField
equals:[nextTextField previousKeyView]
message:@"textField should be the previous key view of nextTextField"];
[self assert:nextTextField
equals:[contentView previousKeyView]
message:@"nextTextField should be the previous key view of contentView"];
}
- (void)testValiKeyViewLoop
{
var contentView = [[self window] contentView],
textField = [CPTextField textFieldWithStringValue:@"" placeholder:@"" width:100.0];
[textField setFrame:CGRectMake(100.0, 100.0, 100.0, 26.0)];
[contentView addSubview:textField];
[[self window] recalculateKeyViewLoop];
var nextTextField = [CPTextField textFieldWithStringValue:@"" placeholder:@"" width:100];
[nextTextField setFrame:CGRectMake(100.0, 200.0, 100.0, 26.0)];
[contentView addSubview:nextTextField];
[[self window] recalculateKeyViewLoop];
// Test nextValidKeyView
[self assert:textField
equals:[contentView nextValidKeyView]
message:@"textField should be the next valid key view of contentView"];
[self assert:nextTextField
equals:[textField nextValidKeyView]
message:@"nextTextField should be the next valid key view of textField"];
[self assert:textField
equals:[nextTextField nextValidKeyView]
message:@"textField should be the next key valid view of nextTextField"];
// Test previousValidKeyView
[self assert:nextTextField
equals:[textField previousValidKeyView]
message:@"nextTextField should be the previous valid key view of textField"];
[self assert:textField
equals:[nextTextField previousValidKeyView]
message:@"textField should be the previous key valid view of nextTextField"];
[self assert:nextTextField
equals:[contentView previousValidKeyView]
message:@"nextTextField should be the previous valid key view of contentView"];
}
- (void)testRepresentedFilename
{
/*
representedURL and representedFilename
are drawn from the _representedURL variable.
*/
var aURL = @"http://www.cappuccino-project.org";
[[self window] setRepresentedURL:[CPURL URLWithString:aURL]];
[self assertTrue:[[[self window] representedURL] class] === [CPURL class]];
/*
Test for Issue 1633
Make sure that changing it via setRepresentedFilename doesn't change the type
*/
[[self window] setRepresentedFilename:aURL];
[self assertTrue:[[[self window] representedURL] class] === [CPURL class]];
}
- (void)testOrderingMethod
{
[_window orderFront:self];
[_window orderBack:self];
[_window orderFront:self];
[_window orderOut:self];
}
/*
The zoom tests supply the standard frame through the delegate. Outside a browser,
each window has its own platform window, whose usable area follows the window.
*/
- (CPWindow)resizableWindow
{
return [[CPWindow alloc] initWithContentRect:CGRectMake(100.0, 100.0, 400.0, 300.0)
styleMask:CPTitledWindowMask | CPResizableWindowMask];
}
- (void)assertWindow:(CPWindow)aWindow hasFrame:(CGRect)aFrame
{
[self assertTrue:CGRectEqualToRect([aWindow frame], aFrame)
message:@"expected " + CPStringFromRect(aFrame) + " but was " + CPStringFromRect([aWindow frame])];
}
- (void)testZoomTogglesBetweenTheStandardFrameAndTheUserFrame
{
var window = [self resizableWindow],
standardFrame = CGRectMake(20.0, 30.0, 640.0, 480.0),
delegate = [[CPWindowTestZoomDelegate alloc] initWithStandardFrame:standardFrame],
userFrame = [window frame];
[window setDelegate:delegate];
[window zoom:self];
[self assertWindow:window hasFrame:standardFrame];
[self assertTrue:[window isZoomed]];
[window zoom:self];
[self assertWindow:window hasFrame:userFrame];
[self assertFalse:[window isZoomed]];
}
- (void)testZoomPassesTheUsableAreaAsTheDefaultFrame
{
var window = [self resizableWindow],
delegate = [[CPWindowTestZoomDelegate alloc] initWithStandardFrame:CGRectMake(20.0, 30.0, 640.0, 480.0)],
usableFrame = [[window platformWindow] usableContentFrame];
[window setDelegate:delegate];
[window zoom:self];
[self assertTrue:CGRectEqualToRect([delegate defaultFrame], usableFrame)
message:@"expected " + CPStringFromRect(usableFrame) + " but was " + CPStringFromRect([delegate defaultFrame])];
}
- (void)testWindowShouldZoomCanRefuseTheStandardFrame
{
var window = [self resizableWindow],
standardFrame = CGRectMake(20.0, 30.0, 640.0, 480.0),
delegate = [[CPWindowTestZoomDelegate alloc] initWithStandardFrame:standardFrame],
userFrame = [window frame];
[delegate setShouldZoom:NO];
[window setDelegate:delegate];
[window zoom:self];
[self assertWindow:window hasFrame:userFrame];
[self assertTrue:CGRectEqualToRect([delegate proposedFrame], standardFrame)];
}
- (void)testZoomKeepsTheStandardFrameWithinTheMaximumSize
{
var window = [self resizableWindow];
[window setMaxSize:CGSizeMake(500.0, 400.0)];
[window setDelegate:[[CPWindowTestZoomDelegate alloc] initWithStandardFrame:CGRectMake(20.0, 30.0, 640.0, 480.0)]];
[window zoom:self];
[self assertWindow:window hasFrame:CGRectMake(20.0, 30.0, 500.0, 400.0)];
}
- (void)testZoomWithoutAUserFrameLeavesTheStandardFrame
{
var window = [self resizableWindow],
standardFrame = [window frame];
[window setDelegate:[[CPWindowTestZoomDelegate alloc] initWithStandardFrame:standardFrame]];
[window zoom:self];
[self assertWindow:window hasFrame:standardFrame];
[self assertTrue:[window isZoomed]];
}
- (void)testAWindowSubclassCanSupplyTheStandardFrame
{
var window = [[CPWindowTestZoomingWindow alloc] initWithContentRect:CGRectMake(100.0, 100.0, 400.0, 300.0)
styleMask:CPTitledWindowMask | CPResizableWindowMask];
[window zoom:self];
[self assertWindow:window hasFrame:CPWindowTestZoomingWindowStandardFrame];
}
- (void)testPerformZoomZoomsOnlyAResizableWindow
{
var standardFrame = CGRectMake(20.0, 30.0, 640.0, 480.0),
resizableWindow = [self resizableWindow],
fixedWindow = [[CPWindow alloc] initWithContentRect:CGRectMake(100.0, 100.0, 400.0, 300.0) styleMask:CPTitledWindowMask],
fixedFrame = [fixedWindow frame];
[resizableWindow setDelegate:[[CPWindowTestZoomDelegate alloc] initWithStandardFrame:standardFrame]];
[fixedWindow setDelegate:[[CPWindowTestZoomDelegate alloc] initWithStandardFrame:standardFrame]];
[resizableWindow performZoom:self];
[fixedWindow performZoom:self];
[self assertWindow:resizableWindow hasFrame:standardFrame];
[self assertWindow:fixedWindow hasFrame:fixedFrame];
}
- (void)testZoomItemsAreEnabledOnlyForAResizableWindow
{
var resizableWindow = [self resizableWindow],
fixedWindow = [[CPWindow alloc] initWithContentRect:CGRectMake(100.0, 100.0, 400.0, 300.0) styleMask:CPTitledWindowMask];
[@selector(performZoom:), @selector(zoom:)].forEach(function(anAction)
{
var item = [[CPMenuItem alloc] initWithTitle:@"Zoom" action:anAction keyEquivalent:@""];
[self assertTrue:[resizableWindow validateUserInterfaceItem:item] message:CPStringFromSelector(anAction) + " in a resizable window"];
[self assertFalse:[fixedWindow validateUserInterfaceItem:item] message:CPStringFromSelector(anAction) + " in a window that cannot resize"];
});
}
/*
The title bar height comes from the default theme. This theme fixes it, so that the
test does not depend on the theme that another test leaves as the default.
*/
- (CPTheme)themeWithTitleBarHeight:(float)aHeight
{
var theme = [[CPTheme alloc] initWithName:@"CPWindowTestTitleBar"],
template = [[_CPStandardWindowView alloc] initWithFrame:CGRectMake(0.0, 0.0, 400.0, 300.0) styleMask:CPTitledWindowMask];
[template setValue:aHeight forThemeAttribute:@"title-bar-height"];
[theme takeThemeFromObject:template];
return theme;
}
- (void)testDoubleClickInTheTitleBarZoomsTheWindow
{
var defaultTheme = [CPTheme defaultTheme];
[CPTheme setDefaultTheme:[self themeWithTitleBarHeight:32.0]];
try
{
var window = [self resizableWindow],
standardFrame = CGRectMake(20.0, 30.0, 640.0, 480.0),
event = [CPEvent mouseEventWithType:CPLeftMouseDown
location:CGPointMake(200.0, 16.0)
modifierFlags:0
timestamp:0
windowNumber:[window windowNumber]
context:nil
eventNumber:0
clickCount:2
pressure:0];
[window setDelegate:[[CPWindowTestZoomDelegate alloc] initWithStandardFrame:standardFrame]];
[[window _windowView] mouseDown:event];
[self assertWindow:window hasFrame:standardFrame];
}
finally
{
[CPTheme setDefaultTheme:defaultTheme];
}
}
@end
/*
Supplies a fixed standard frame and records the frames the window passes to it.
*/
@implementation CPWindowTestZoomDelegate : CPObject
{
CGRect _standardFrame;
CGRect _defaultFrame @accessors(readonly, property=defaultFrame);
CGRect _proposedFrame @accessors(readonly, property=proposedFrame);
BOOL _shouldZoom @accessors(property=shouldZoom);
}
- (id)initWithStandardFrame:(CGRect)aFrame
{
self = [super init];
if (self)
{
_standardFrame = CGRectMakeCopy(aFrame);
_shouldZoom = YES;
}
return self;
}
- (CGRect)windowWillUseStandardFrame:(CPWindow)aWindow defaultFrame:(CGRect)aFrame
{
_defaultFrame = CGRectMakeCopy(aFrame);
return CGRectMakeCopy(_standardFrame);
}
- (BOOL)windowShouldZoom:(CPWindow)aWindow toFrame:(CGRect)aFrame
{
_proposedFrame = CGRectMakeCopy(aFrame);
return _shouldZoom;
}
@end
var CPWindowTestZoomingWindowStandardFrame = CGRectMake(40.0, 50.0, 600.0, 450.0);
/*
Supplies its own standard frame, as a window subclass may in Cocoa.
*/
@implementation CPWindowTestZoomingWindow : CPWindow
- (CGRect)windowWillUseStandardFrame:(CPWindow)aWindow defaultFrame:(CGRect)aFrame
{
return CGRectMakeCopy(CPWindowTestZoomingWindowStandardFrame);
}
@end