diff options
author | thomasvl <thomasvl@7dc7ac4e-7543-0410-b95c-c1676fc8e2a3> | 2008-01-28 20:19:42 +0000 |
---|---|---|
committer | thomasvl <thomasvl@7dc7ac4e-7543-0410-b95c-c1676fc8e2a3> | 2008-01-28 20:19:42 +0000 |
commit | 2a5219567634ab7ab74314ff3615132becadff4a (patch) | |
tree | 8e6f447544e5eaf460da741bf57771f929b4a70c /AppKit |
initial drop of a few sources to start things out
Diffstat (limited to 'AppKit')
31 files changed, 3317 insertions, 0 deletions
diff --git a/AppKit/GTMDelegatingTableColumn.h b/AppKit/GTMDelegatingTableColumn.h new file mode 100644 index 0000000..a90b298 --- /dev/null +++ b/AppKit/GTMDelegatingTableColumn.h @@ -0,0 +1,32 @@ +// +// GTMDelegatingTableColumn.h +// +// Copyright 2006-2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +// + +#import <Cocoa/Cocoa.h> + +@interface GTMDelegatingTableColumn : NSTableColumn { + @private + IBOutlet id delegate_; +} +- (void)setDelegate:(id)delegate; +- (id)delegate; +- (id)dataCellForRow:(int)row; +@end + +@interface NSObject (GTMDelegatingTableColumnDelegate) +- (id)tableColumn:(NSTableColumn*)column dataCellForRow:(int)row; +@end diff --git a/AppKit/GTMDelegatingTableColumn.m b/AppKit/GTMDelegatingTableColumn.m new file mode 100644 index 0000000..d84e2e7 --- /dev/null +++ b/AppKit/GTMDelegatingTableColumn.m @@ -0,0 +1,39 @@ +// +// GTMDelegatingTableColumn.m +// +// Copyright 2006-2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +// + +#import "GTMDelegatingTableColumn.h" + +@implementation GTMDelegatingTableColumn +- (void)setDelegate:(id)delegate { + delegate_ = delegate; +} + +- (id)delegate { + return delegate_; +} + +- (id)dataCellForRow:(int)row { + id dataCell = nil; + if (delegate_ && [delegate_ respondsToSelector:@selector(tableColumn:dataCellForRow:)]) { + dataCell = [delegate_ tableColumn:self dataCellForRow:row]; + } else { + dataCell = [super dataCellForRow:row]; + } + return dataCell; +} +@end diff --git a/AppKit/GTMGeometryUtils.h b/AppKit/GTMGeometryUtils.h new file mode 100644 index 0000000..1dd274f --- /dev/null +++ b/AppKit/GTMGeometryUtils.h @@ -0,0 +1,560 @@ +// +// GTMGeometryUtils.h +// +// Utilities for geometrical utilities such as conversions +// between different types. +// +// Copyright 2006-2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +// + +#include <Carbon/Carbon.h> +#include <Cocoa/Cocoa.h> + + +#pragma mark Miscellaneous + +/// Calculate the distance between two points. +// +// Args: +// pt1 first point +// pt2 second point +// +// Returns: +// Distance +CG_INLINE float GTMDistanceBetweenPoints(NSPoint pt1, NSPoint pt2) { + float dX = pt1.x - pt2.x; + float dY = pt1.y - pt2.y; + return sqrtf(dX * dX + dY * dY); +} + +/// Returns the height of the main display (the one with the menu bar). +// +/// The value updates itself automatically whenever the user +/// repositions their monitors, or changes resolutions etc. +// +// Returns: +// height of the main display area. +float GTMGetMainDisplayHeight(void); + +#pragma mark - +#pragma mark Point Conversion + +/// Quickly convert from a global HIPoint to a global NSPoint. +// +/// HIPoints are relative to 0,0 in upper left; +/// NSPoints are relative to 0,0 in lower left +// +// Args: +// inPoint: HIPoint to convert +// +// Returns: +// Converted NSPoint +CG_INLINE NSPoint GTMGlobalHIPointToNSPoint(HIPoint inPoint) { + return NSMakePoint(inPoint.x, GTMGetMainDisplayHeight() - inPoint.y); +} + +/// Quickly convert from a global NSPoint to a global HIPoint. +// +/// HIPoints are relative to 0,0 in upper left; +/// NSPoints are relative to 0,0 in lower left +// +// Args: +// inPoint: NSPoint to convert +// +// Returns: +// Converted HIPoint +CG_INLINE HIPoint GTMGlobalNSPointToHIPoint(NSPoint inPoint) { + return CGPointMake(inPoint.x, GTMGetMainDisplayHeight() - inPoint.y); +} + +/// Quickly convert from a CGPoint to a NSPoint. +// +/// CGPoints are relative to 0,0 in lower left; +/// NSPoints are relative to 0,0 in lower left +// +// Args: +// inPoint: CGPoint to convert +// +// Returns: +// Converted NSPoint +CG_INLINE NSPoint GTMCGPointToNSPoint(CGPoint inPoint) { + return NSMakePoint(inPoint.x, inPoint.y); +} + +/// Quickly convert from a NSPoint to a CGPoint. +// +/// CGPoints are relative to 0,0 in lower left; +/// NSPoints are relative to 0,0 in lower left +// +// Args: +// inPoint: NSPoint to convert +// +// Returns: +// Converted CGPoint +CG_INLINE CGPoint GTMNSPointToCGPoint(NSPoint inPoint) { + return CGPointMake(inPoint.x, inPoint.y); +} + +/// Quickly convert from a global HIPoint to a global CGPoint. +// +/// HIPoints are relative to 0,0 in upper left; +/// CGPoints are relative to 0,0 in lower left +// +// Args: +// inPoint: NSPoint to convert +// +// Returns: +// Converted CGPoint +CG_INLINE HIPoint GTMGlobalCGPointToHIPoint(CGPoint inPoint) { + return GTMGlobalNSPointToHIPoint(GTMCGPointToNSPoint(inPoint)); +} + +/// Quickly convert from a global CGPoint to a global HIPoint. +// +/// HIPoints are relative to 0,0 in upper left; +/// CGPoints are relative to 0,0 in lower left +// +// Args: +// inPoint: CGPoint to convert +// +// Returns: +// Converted NSPoint +CG_INLINE CGPoint GTMGlobalHIPointToCGPoint(HIPoint inPoint) { + return GTMNSPointToCGPoint(GTMGlobalHIPointToNSPoint(inPoint)); +} + +#pragma mark - +#pragma mark Rect Conversion + +/// Convert from a global NSRect to a global HIRect. +// +/// HIRect are relative to 0,0 in upper left; +/// NSRect are relative to 0,0 in lower left +// +// Args: +// inRect: NSRect to convert +// +// Returns: +// Converted HIRect +HIRect GTMGlobalNSRectToHIRect(NSRect inRect); + +/// Convert from a rect to a HIRect. +// +/// HIRect are relative to 0,0 in upper left; +/// Rect are relative to 0,0 in upper left +// +// Args: +// inRect: Rect to convert +// +// Returns: +// Converted HIRect +HIRect GTMRectToHIRect(Rect inRect); + +/// Convert from a global HIRect to a global NSRect. +// +/// NSRect are relative to 0,0 in lower left; +/// HIRect are relative to 0,0 in upper left +// +// Args: +// inRect: HIRect to convert +// +// Returns: +// Converted NSRect +NSRect GTMGlobalHIRectToNSRect(HIRect inRect); + + +/// Convert from a HIRect to a Rect. +// +/// Rect are relative to 0,0 in upper left; +/// HIRect are relative to 0,0 in upper left +// +// Args: +// inRect: HIRect to convert +// +// Returns: +// Converted Rect +Rect GTMHIRectToRect(HIRect inRect); + +/// Convert from a global Rect to a global NSRect. +// +/// NSRect are relative to 0,0 in lower left; +/// Rect are relative to 0,0 in upper left +// +// Args: +// inRect: Rect to convert +// +// Returns: +// Converted NSRect +CG_INLINE NSRect GTMGlobalRectToNSRect(Rect inRect) { + return GTMGlobalHIRectToNSRect(GTMRectToHIRect(inRect)); +} + +/// Convert from a CGRect to a NSRect. +// +/// NSRect are relative to 0,0 in lower left; +/// CGRect are relative to 0,0 in lower left +// +// Args: +// inRect: CGRect to convert +// +// Returns: +// Converted NSRect +CG_INLINE NSRect GTMCGRectToNSRect(CGRect inRect) { + return NSMakeRect(inRect.origin.x,inRect.origin.y,inRect.size.width,inRect.size.height); +} + +/// Convert from a NSRect to a CGRect. +// +/// NSRect are relative to 0,0 in lower left; +/// CGRect are relative to 0,0 in lower left +// +// Args: +// inRect: NSRect to convert +// +// Returns: +// Converted CGRect +CG_INLINE CGRect GTMNSRectToCGRect(NSRect inRect) { + return CGRectMake(inRect.origin.x,inRect.origin.y,inRect.size.width,inRect.size.height); +} + +/// Convert from a global HIRect to a global CGRect. +// +/// HIRect are relative to 0,0 in upper left; +/// CGRect are relative to 0,0 in lower left +// +// Args: +// inRect: HIRect to convert +// +// Returns: +// Converted CGRect +CG_INLINE CGRect GTMGlobalHIRectToCGRect(HIRect inRect) { + return GTMNSRectToCGRect(GTMGlobalHIRectToNSRect(inRect)); +} + +/// Convert from a global Rect to a global CGRect. +// +/// Rect are relative to 0,0 in upper left; +/// CGRect are relative to 0,0 in lower left +// +// Args: +// inRect: Rect to convert +// +// Returns: +// Converted CGRect +CG_INLINE CGRect GTMGlobalRectToCGRect(Rect inRect) { + return GTMNSRectToCGRect(GTMGlobalRectToNSRect(inRect)); +} + +/// Convert from a global NSRect to a global Rect. +// +/// Rect are relative to 0,0 in upper left; +/// NSRect are relative to 0,0 in lower left +// +// Args: +// inRect: NSRect to convert +// +// Returns: +// Converted Rect +CG_INLINE Rect GTMGlobalNSRectToRect(NSRect inRect) { + return GTMHIRectToRect(GTMGlobalNSRectToHIRect(inRect)); +} + +/// Convert from a global CGRect to a global HIRect. +// +/// HIRect are relative to 0,0 in upper left; +/// CGRect are relative to 0,0 in lower left +// +// Args: +// inRect: CGRect to convert +// +// Returns: +// Converted HIRect +CG_INLINE HIRect GTMGlobalCGRectToHIRect(CGRect inRect) { + return GTMGlobalNSRectToHIRect(GTMCGRectToNSRect(inRect)); +} + +/// Convert from a global CGRect to a global Rect. +// +/// Rect are relative to 0,0 in upper left; +/// CGRect are relative to 0,0 in lower left +// +// Args: +// inRect: CGRect to convert +// +// Returns: +// Converted Rect +CG_INLINE Rect GTMGlobalCGRectToRect(CGRect inRect) { + return GTMHIRectToRect(GTMGlobalCGRectToHIRect(inRect)); +} + +#pragma mark - +#pragma mark Size Conversion + +/// Convert from a CGSize to an NSSize. +// +// Args: +// inSize: CGSize to convert +// +// Returns: +// Converted NSSize +CG_INLINE NSSize GTMCGSizeToNSSize(CGSize inSize) { + return NSMakeSize(inSize.width, inSize.height); +} + +/// Convert from a NSSize to a CGSize. +// +// Args: +// inSize: NSSize to convert +// +// Returns: +// Converted CGSize +CG_INLINE CGSize GTMNSSizeToCGSize(NSSize inSize) { + return CGSizeMake(inSize.width, inSize.height); +} + +#pragma mark - +#pragma mark Point On Rect + +/// Return middle of left side of rectangle +// +// Args: +// rect - rectangle +// +// Returns: +// point located in the middle of left side of rect +CG_INLINE NSPoint GTMNSMidLeft(NSRect rect) { + return NSMakePoint(NSMinX(rect), NSMidY(rect)); +} + +/// Return middle of right side of rectangle +// +// Args: +// rect - rectangle +// +// Returns: +// point located in the middle of right side of rect +CG_INLINE NSPoint GTMNSMidRight(NSRect rect) { + return NSMakePoint(NSMaxX(rect), NSMidY(rect)); +} + +/// Return middle of top side of rectangle +// +// Args: +// rect - rectangle +// +// Returns: +// point located in the middle of top side of rect +CG_INLINE NSPoint GTMNSMidTop(NSRect rect) { + return NSMakePoint(NSMidX(rect), NSMaxY(rect)); +} + +/// Return middle of bottom side of rectangle +// +// Args: +// rect - rectangle +// +// Returns: +// point located in the middle of bottom side of rect +CG_INLINE NSPoint GTMNSMidBottom(NSRect rect) { + return NSMakePoint(NSMidX(rect), NSMinY(rect)); +} + +/// Return center of rectangle +// +// Args: +// rect - rectangle +// +// Returns: +// point located in the center of rect +CG_INLINE NSPoint GTMNSCenter(NSRect rect) { + return NSMakePoint(NSMidX(rect), NSMidY(rect)); +} + +/// Return middle of left side of rectangle +// +// Args: +// rect - rectangle +// +// Returns: +// point located in the middle of left side of rect +CG_INLINE CGPoint GTMCGMidLeft(CGRect rect) { + return CGPointMake(CGRectGetMinX(rect), CGRectGetMidY(rect)); +} + +/// Return middle of right side of rectangle +// +// Args: +// rect - rectangle +// +// Returns: +// point located in the middle of right side of rect +CG_INLINE CGPoint GTMCGMidRight(CGRect rect) { + return CGPointMake(CGRectGetMaxX(rect), CGRectGetMidY(rect)); +} + +/// Return middle of top side of rectangle +// +// Args: +// rect - rectangle +// +// Returns: +// point located in the middle of top side of rect +CG_INLINE CGPoint GTMCGMidTop(CGRect rect) { + return CGPointMake(CGRectGetMidX(rect), CGRectGetMaxY(rect)); +} + +/// Return middle of bottom side of rectangle +// +// Args: +// rect - rectangle +// +// Returns: +// point located in the middle of bottom side of rect +CG_INLINE CGPoint GTMCGMidBottom(CGRect rect) { + return CGPointMake(CGRectGetMidX(rect), CGRectGetMinY(rect)); +} + +/// Return center of rectangle +// +// Args: +// rect - rectangle +// +// Returns: +// point located in the center of rect +CG_INLINE CGPoint GTMCGCenter(CGRect rect) { + return CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect)); +} + +#pragma mark - +#pragma mark Rect-Size Conversion + +/// Return size of rectangle +// +// Args: +// rect - rectangle +// +// Returns: +// size of rectangle +CG_INLINE NSSize GTMNSRectSize(NSRect rect) { + return NSMakeSize(NSWidth(rect), NSHeight(rect)); +} + +/// Return size of rectangle +// +// Args: +// rect - rectangle +// +// Returns: +// size of rectangle +CG_INLINE CGSize GTMCGRectSize(CGRect rect) { + return CGSizeMake(CGRectGetWidth(rect), CGRectGetHeight(rect)); +} + +/// Return rectangle of size +// +// Args: +// size - size +// +// Returns: +// rectangle of size (origin 0,0) +CG_INLINE NSRect GTMNSRectOfSize(NSSize size) { + return NSMakeRect(0.0f, 0.0f, size.width, size.height); +} + +/// Return rectangle of size +// +// Args: +// size - size +// +// Returns: +// rectangle of size (origin 0,0) +CG_INLINE CGRect GTMCGRectOfSize(CGSize size) { + return CGRectMake(0.0f, 0.0f, size.width, size.height); +} + +#pragma mark - +#pragma mark Rect Scaling and Alignment + +/// Scales an NSRect +// +// Args: +// inRect: Rect to scale +// xScale: fraction to scale (1.0 is 100%) +// yScale: fraction to scale (1.0 is 100%) +// +// Returns: +// Converted Rect +CG_INLINE NSRect GTMNSRectScale(NSRect inRect, float xScale, float yScale) { + return NSMakeRect(inRect.origin.x, inRect.origin.y, + inRect.size.width * xScale, inRect.size.height * yScale); +} + +/// Scales an CGRect +// +// Args: +// inRect: Rect to scale +// xScale: fraction to scale (1.0 is 100%) +// yScale: fraction to scale (1.0 is 100%) +// +// Returns: +// Converted Rect +CG_INLINE CGRect GTMCGRectScale(CGRect inRect, float xScale, float yScale) { + return CGRectMake(inRect.origin.x, inRect.origin.y, + inRect.size.width * xScale, inRect.size.height * yScale); +} + +/// Align rectangles +// +// Args: +// alignee - rect to be aligned +// aligner - rect to be aligned from +NSRect GTMAlignRectangles(NSRect alignee, NSRect aligner, + NSImageAlignment alignment); + +/// Align rectangles +// +// Args: +// alignee - rect to be aligned +// aligner - rect to be aligned from +// alignment - way to align the rectangles +CG_INLINE CGRect GTMCGAlignRectangles(CGRect alignee, CGRect aligner, + NSImageAlignment alignment) { + return GTMNSRectToCGRect(GTMAlignRectangles(GTMCGRectToNSRect(alignee), + GTMCGRectToNSRect(aligner), + alignment)); +} + +/// Scale rectangle +// +// Args: +// scalee - rect to be scaled +// size - size to scale to +// scaling - way to scale the rectangle +NSRect GTMScaleRectangleToSize(NSRect scalee, NSSize size, + NSImageScaling scaling); + +/// Scale rectangle +// +// Args: +// scalee - rect to be scaled +// size - size to scale to +// scaling - way to scale the rectangle +CG_INLINE CGRect GTMCGScaleRectangleToSize(CGRect scalee, CGSize size, + NSImageScaling scaling) { + return GTMNSRectToCGRect(GTMScaleRectangleToSize(GTMCGRectToNSRect(scalee), + GTMCGSizeToNSSize(size), + scaling)); +} + diff --git a/AppKit/GTMGeometryUtils.m b/AppKit/GTMGeometryUtils.m new file mode 100644 index 0000000..efc543e --- /dev/null +++ b/AppKit/GTMGeometryUtils.m @@ -0,0 +1,152 @@ +// +// GTMGeometryUtils.m +// +// Copyright 2006-2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +// + +#import "GTMGeometryUtils.h" + +float GTMGetMainDisplayHeight(void) { + float height = 0; + NSArray *screens = [NSScreen screens]; + // We may have a headless machine without any screens. In this case we + // return 0. + if ([screens count] > 0) { + height = NSHeight([(NSScreen*)[screens objectAtIndex: 0] frame]); + } + return height; +} + + +// Rect conversion routines. +HIRect GTMGlobalNSRectToHIRect(NSRect inRect) { + HIRect theRect; + theRect.origin = GTMGlobalNSPointToHIPoint(inRect.origin); + theRect.origin.y -= inRect.size.height; + theRect.size = CGSizeMake(inRect.size.width, inRect.size.height); + return theRect; +} + + +HIRect GTMRectToHIRect(Rect inRect) { + HIRect theRect; + theRect.origin = CGPointMake(inRect.left,inRect.top); + theRect.size = CGSizeMake(inRect.right - inRect.left, inRect.bottom - inRect.top); + return theRect; +} + + +NSRect GTMGlobalHIRectToNSRect(HIRect inRect) { + NSRect theRect; + theRect.origin = GTMGlobalHIPointToNSPoint(inRect.origin); + theRect.origin.y -= inRect.size.height; + theRect.size = NSMakeSize(inRect.size.width, inRect.size.height); + return theRect; +} + + +Rect GTMHIRectToRect(HIRect inRect) { + Rect theRect; + theRect.left = inRect.origin.x; + theRect.right = ceilf(inRect.origin.x + inRect.size.width); + theRect.top = inRect.origin.y; + theRect.bottom = ceilf(inRect.origin.y + inRect.size.height); + return theRect; +} + +/// Align rectangles +// +// Args: +// alignee - rect to be aligned +// aligner - rect to be aligned to +// alignment - alignment to be applied to alignee based on aligner + +NSRect GTMAlignRectangles(NSRect alignee, NSRect aligner, NSImageAlignment alignment) { + switch (alignment) { + case NSImageAlignTop: + alignee.origin.x = aligner.origin.x + (NSWidth(aligner) * .5f - NSWidth(alignee) * .5f); + alignee.origin.y = aligner.origin.y + NSHeight(aligner) - NSHeight(alignee); + break; + + case NSImageAlignTopLeft: + alignee.origin.x = aligner.origin.x; + alignee.origin.y = aligner.origin.y + NSHeight(aligner) - NSHeight(alignee); + break; + + case NSImageAlignTopRight: + alignee.origin.x = aligner.origin.x + NSWidth(aligner) - NSWidth(alignee); + alignee.origin.y = aligner.origin.y + NSHeight(aligner) - NSHeight(alignee); + break; + + case NSImageAlignLeft: + alignee.origin.x = aligner.origin.x; + alignee.origin.y = aligner.origin.y + (NSHeight(aligner) * .5f - NSHeight(alignee) * .5f); + break; + + case NSImageAlignBottomLeft: + alignee.origin.x = aligner.origin.x; + alignee.origin.y = aligner.origin.y; + break; + + case NSImageAlignBottom: + alignee.origin.x = aligner.origin.x + (NSWidth(aligner) * .5f - NSWidth(alignee) * .5f); + alignee.origin.y = aligner.origin.y; + break; + + case NSImageAlignBottomRight: + alignee.origin.x = aligner.origin.x + NSWidth(aligner) - NSWidth(alignee); + alignee.origin.y = aligner.origin.y; + break; + + case NSImageAlignRight: + alignee.origin.x = aligner.origin.x + NSWidth(aligner) - NSWidth(alignee); + alignee.origin.y = aligner.origin.y + (NSHeight(aligner) * .5f - NSHeight(alignee) * .5f); + break; + + default: + case NSImageAlignCenter: + alignee.origin.x = aligner.origin.x + (NSWidth(aligner) * .5f - NSWidth(alignee) * .5f); + alignee.origin.y = aligner.origin.y + (NSHeight(aligner) * .5f - NSHeight(alignee) * .5f); + break; + } + return alignee; +} + +NSRect GTMScaleRectangleToSize(NSRect scalee, NSSize size, NSImageScaling scaling) { + switch (scaling) { + case NSScaleProportionally: { + float height = NSHeight(scalee); + float width = NSWidth(scalee); + if (isnormal(height) && isnormal(width) && + (height > size.height || width > size.width)) { + float horiz = size.width / width; + float vert = size.height / height; + float newScale = horiz < vert ? horiz : vert; + scalee = GTMNSRectScale(scalee, newScale, newScale); + } + break; + } + + case NSScaleToFit: + scalee.size = size; + break; + + case NSScaleNone: + default: + // Do nothing + break; + } + return scalee; +} diff --git a/AppKit/GTMGeometryUtilsTest.m b/AppKit/GTMGeometryUtilsTest.m new file mode 100644 index 0000000..0fad449 --- /dev/null +++ b/AppKit/GTMGeometryUtilsTest.m @@ -0,0 +1,323 @@ +// +// GTMGeometryUtilsTest.m +// +// Copyright 2006-2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +// + +#import <SenTestingKit/SenTestingKit.h> +#import "GTMGeometryUtils.h" + +@interface GTMGeometryUtilsTest : SenTestCase +@end + +@implementation GTMGeometryUtilsTest + +- (void)testGTMGetMainDisplayHeight { + STAssertTrue(CGRectGetHeight(CGDisplayBounds(CGMainDisplayID())) == GTMGetMainDisplayHeight(), nil); +} + + +- (void)testGTMGlobalHIPointToNSPoint { + HIPoint hiPoint = CGPointMake(12.5,14.5); + NSPoint nsPoint = GTMGlobalHIPointToNSPoint(hiPoint); + STAssertTrue(nsPoint.x == hiPoint.x && + nsPoint.y == GTMGetMainDisplayHeight() - hiPoint.y, nil); +} + + +- (void)testGTMGlobalNSPointToHIPoint { + NSPoint nsPoint = NSMakePoint(12.5,14.5); + HIPoint hiPoint = GTMGlobalNSPointToHIPoint(nsPoint); + STAssertTrue(nsPoint.x == hiPoint.x && + nsPoint.y == GTMGetMainDisplayHeight() - hiPoint.y, nil); +} + + +- (void)testGTMGlobalCGPointToNSPoint { + CGPoint cgPoint = CGPointMake(15.1,6.2); + NSPoint nsPoint = GTMCGPointToNSPoint(cgPoint); + STAssertTrue(CGPointEqualToPoint(*(CGPoint*)&nsPoint, cgPoint), nil); + +} + + +- (void)testGTMGlobalNSPointToCGPoint { + NSPoint nsPoint = NSMakePoint(10.2,1.5); + CGPoint cgPoint = GTMNSPointToCGPoint(nsPoint); + STAssertTrue(CGPointEqualToPoint(cgPoint, *(CGPoint*)&nsPoint), nil); +} + + +- (void)testGTMGlobalCGPointToHIPoint { + CGPoint cgPoint = CGPointMake(12.5,14.5); + HIPoint hiPoint = GTMGlobalCGPointToHIPoint(cgPoint); + STAssertTrue(cgPoint.x == hiPoint.x && + cgPoint.y == GTMGetMainDisplayHeight() - hiPoint.y, nil); +} + + +- (void)testGTMGlobalHIPointToCGPoint { + HIPoint hiPoint = CGPointMake(12.5,14.5); + CGPoint cgPoint = GTMGlobalHIPointToCGPoint(hiPoint); + STAssertTrue(cgPoint.x == hiPoint.x && + cgPoint.y == GTMGetMainDisplayHeight() - hiPoint.y, nil); +} + + +- (void)testGTMGlobalNSRectToHIRect { + NSRect nsRect = NSMakeRect(40,16,17,18); + HIRect hiRect = GTMGlobalNSRectToHIRect(nsRect); + STAssertTrue(nsRect.origin.x == hiRect.origin.x && + nsRect.origin.y == GTMGetMainDisplayHeight() - hiRect.origin.y - hiRect.size.height && + nsRect.size.height == hiRect.size.height && + nsRect.size.width == hiRect.size.width, nil); +} + + +- (void)testGTMGlobalCGRectToHIRect { + CGRect cgRect = CGRectMake(40,16,17,19.0); + HIRect hiRect = GTMGlobalCGRectToHIRect(cgRect); + STAssertTrue(cgRect.origin.x == hiRect.origin.x && + cgRect.origin.y == GTMGetMainDisplayHeight() - hiRect.origin.y - hiRect.size.height && + cgRect.size.height == hiRect.size.height && + cgRect.size.width == hiRect.size.width, nil); +} + + +- (void)testGTMGlobalHIRectToNSRect { + HIRect hiRect = CGRectMake(40.2,16.3,17.2,18.9); + NSRect nsRect = GTMGlobalHIRectToNSRect(hiRect); + STAssertTrue(nsRect.origin.x == hiRect.origin.x && + nsRect.origin.y == GTMGetMainDisplayHeight() - hiRect.origin.y - hiRect.size.height && + nsRect.size.height == hiRect.size.height && + nsRect.size.width == hiRect.size.width, nil); +} + + +- (void)testGTMGlobalCGRectToNSRect { + CGRect cgRect = CGRectMake(1.5,2.4,10.6,11.7); + NSRect nsRect = GTMCGRectToNSRect(cgRect); + STAssertTrue(CGRectEqualToRect(cgRect, *(CGRect*)&nsRect), nil); +} + + +- (void)testGTMGlobalNSRectToCGRect { + NSRect nsRect = NSMakeRect(4.6,3.2,22.1,45.0); + CGRect cgRect = GTMNSRectToCGRect(nsRect); + STAssertTrue(CGRectEqualToRect(cgRect, *(CGRect*)&nsRect), nil); +} + + +- (void)testGTMGlobalHIRectToCGRect { + HIRect hiRect = CGRectMake(40.2,16.3,17.2,18.9); + CGRect cgRect = GTMGlobalHIRectToCGRect(hiRect); + STAssertTrue(cgRect.origin.x == hiRect.origin.x && + cgRect.origin.y == GTMGetMainDisplayHeight() - hiRect.origin.y - hiRect.size.height && + cgRect.size.height == hiRect.size.height && + cgRect.size.width == hiRect.size.width, nil); +} + + +- (void)testGTMGlobalRectToNSRect { + Rect rect = { 10,50,40,60 }; + NSRect nsRect = GTMGlobalRectToNSRect(rect); + HIRect hiRect1 = GTMRectToHIRect(rect); + HIRect hiRect2 = GTMGlobalNSRectToHIRect(nsRect); + STAssertTrue(CGRectEqualToRect(hiRect1,hiRect2), nil); +} + + +- (void)testGTMGlobalNSRectToRect { + NSRect nsRect = NSMakeRect(1.5,2.4,10.6,11.7); + HIRect hiRect = GTMGlobalNSRectToHIRect(nsRect); + Rect rect1 = GTMGlobalNSRectToRect(nsRect); + Rect rect2 = GTMHIRectToRect(hiRect); + STAssertTrue(rect1.left == rect2.left && + rect1.right == rect2.right && + rect1.top == rect2.top && + rect1.bottom == rect2.bottom, nil); +} + + +- (void)testGTMGlobalRectToHIRect { + Rect rect = { 10,20,30,40 }; + HIRect hiRect = GTMRectToHIRect(rect); + STAssertTrue(CGRectEqualToRect(hiRect, CGRectMake(20,10,20,20)), nil); +} + + +- (void)testGTMGlobalHIRectToRect { + HIRect hiRect = CGRectMake(1.5,2.4,10.6,11.7); + Rect rect = GTMHIRectToRect(hiRect); + STAssertTrue(rect.left == 1 && + rect.right == 13 && + rect.top == 2 && + rect.bottom == 15, nil); +} + + +- (void)testGTMGlobalCGRectToRect { + CGRect cgRect = CGRectMake(1.5,2.4,10.6,11.7); + HIRect hiRect = GTMGlobalCGRectToHIRect(cgRect); + Rect rect1 = GTMGlobalCGRectToRect(cgRect); + Rect rect2 = GTMHIRectToRect(hiRect); + STAssertTrue(rect1.left == rect2.left && + rect1.right == rect2.right && + rect1.top == rect2.top && + rect1.bottom == rect2.bottom, nil); +} + + +- (void)testGTMGlobalRectToCGRect { + Rect rect = { 10,50,40,60 }; + CGRect nsRect = GTMGlobalRectToCGRect(rect); + HIRect hiRect1 = GTMRectToHIRect(rect); + HIRect hiRect2 = GTMGlobalCGRectToHIRect(nsRect); + STAssertTrue(CGRectEqualToRect(hiRect1,hiRect2), nil); +} + + +- (void)testGTMCGSizeToNSSize { + CGSize cgSize = {5,6}; + NSSize nsSize = GTMCGSizeToNSSize(cgSize); + STAssertTrue(CGSizeEqualToSize(cgSize, *(CGSize*)&nsSize), nil); +} + + +- (void)testGTMNSSizeToCGSize { + NSSize nsSize = {22,15}; + CGSize cgSize = GTMNSSizeToCGSize(nsSize); + STAssertTrue(CGSizeEqualToSize(cgSize, *(CGSize*)&nsSize), nil); +} + +- (void)testGTMDistanceBetweenPoints { + NSPoint pt1 = NSMakePoint(0, 0); + NSPoint pt2 = NSMakePoint(3, 4); + STAssertEquals(GTMDistanceBetweenPoints(pt1, pt2), 5.0f, nil); + STAssertEquals(GTMDistanceBetweenPoints(pt2, pt1), 5.0f, nil); + pt1 = NSMakePoint(1, 1); + pt2 = NSMakePoint(1, 1); + STAssertEquals(GTMDistanceBetweenPoints(pt1, pt2), 0.0f, nil); +} + +- (void)testGTMAlignRectangles { + typedef struct { + NSPoint expectedOrigin; + NSImageAlignment alignment; + } TestData; + + TestData data[] = { + { {1,2}, NSImageAlignTop }, + { {0,2}, NSImageAlignTopLeft }, + { {2,2}, NSImageAlignTopRight }, + { {0,1}, NSImageAlignLeft }, + { {1,0}, NSImageAlignBottom }, + { {0,0}, NSImageAlignBottomLeft }, + { {2,0}, NSImageAlignBottomRight }, + { {2,1}, NSImageAlignRight }, + { {1,1}, NSImageAlignCenter }, + }; + + NSRect rect1 = NSMakeRect(0, 0, 4, 4); + NSRect rect2 = NSMakeRect(0, 0, 2, 2); + + for (int i = 0; i < sizeof(data) / sizeof(TestData); i++) { + NSRect expectedRect; + expectedRect.origin = data[i].expectedOrigin; + expectedRect.size = NSMakeSize(2, 2); + NSRect outRect = GTMAlignRectangles(rect2, rect1, data[i].alignment); + STAssertEquals(outRect, expectedRect, nil); + } +} + +- (void)testGTMPointsOnRect { + NSRect rect = NSMakeRect(0, 0, 2, 2); + CGRect cgRect = GTMNSRectToCGRect(rect); + + NSPoint point = GTMNSMidLeft(rect); + CGPoint cgPoint = GTMCGMidLeft(cgRect); + STAssertEquals(point.x, cgPoint.x, nil); + STAssertEquals(point.y, cgPoint.y, nil); + STAssertEqualsWithAccuracy(point.y, 1.0f, 0.01f, nil); + STAssertEqualsWithAccuracy(point.x, 0.0f, 0.01f, nil); + + point = GTMNSMidRight(rect); + cgPoint = GTMCGMidRight(cgRect); + STAssertEquals(point.x, cgPoint.x, nil); + STAssertEquals(point.y, cgPoint.y, nil); + STAssertEqualsWithAccuracy(point.y, 1.0f, 0.01f, nil); + STAssertEqualsWithAccuracy(point.x, 2.0f, 0.01f, nil); + + point = GTMNSMidTop(rect); + cgPoint = GTMCGMidTop(cgRect); + STAssertEquals(point.x, cgPoint.x, nil); + STAssertEquals(point.y, cgPoint.y, nil); + STAssertEqualsWithAccuracy(point.y, 2.0f, 0.01f, nil); + STAssertEqualsWithAccuracy(point.x, 1.0f, 0.01f, nil); + + point = GTMNSMidBottom(rect); + cgPoint = GTMCGMidBottom(cgRect); + STAssertEquals(point.x, cgPoint.x, nil); + STAssertEquals(point.y, cgPoint.y, nil); + STAssertEqualsWithAccuracy(point.y, 0.0f, 0.01f, nil); + STAssertEqualsWithAccuracy(point.x, 1.0f, 0.01f, nil); +} + +- (void)testGTMRectScaling { + NSRect rect = NSMakeRect(1.0f, 2.0f, 5.0f, 10.0f); + NSRect rect2 = NSMakeRect(1.0f, 2.0f, 1.0f, 12.0f); + STAssertEquals(GTMNSRectScale(rect, 0.2f, 1.2f), + rect2, nil); + STAssertEquals(GTMCGRectScale(GTMNSRectToCGRect(rect), 0.2f, 1.2f), + GTMNSRectToCGRect(rect2), nil); +} + +- (void)testGTMScaleRectangleToSize { + NSRect rect = NSMakeRect(0.0f, 0.0f, 10.0f, 10.0f); + typedef struct { + NSSize size_; + NSSize newSize_; + } Test; + Test tests[] = { + { { 5.0, 10.0 }, { 5.0, 5.0 } }, + { { 10.0, 5.0 }, { 5.0, 5.0 } }, + { { 10.0, 10.0 }, { 10.0, 10.0 } }, + { { 11.0, 11.0, }, { 10.0, 10.0 } }, + { { 5.0, 2.0 }, { 2.0, 2.0 } }, + { { 2.0, 5.0 }, { 2.0, 2.0 } }, + { { 2.0, 2.0 }, { 2.0, 2.0 } }, + { { 0.0, 10.0 }, { 0.0, 0.0 } } + }; + + for (size_t i = 0; i < sizeof(tests) / sizeof(Test); ++i) { + NSRect result = GTMScaleRectangleToSize(rect, tests[i].size_, + NSScaleProportionally); + STAssertEquals(result, GTMNSRectOfSize(tests[i].newSize_), @"failed on test %z", i); + } + + NSRect result = GTMScaleRectangleToSize(NSZeroRect, tests[0].size_, + NSScaleProportionally); + STAssertEquals(result, NSZeroRect, nil); + + result = GTMScaleRectangleToSize(rect, tests[0].size_, + NSScaleToFit); + STAssertEquals(result, GTMNSRectOfSize(tests[0].size_), nil); + + result = GTMScaleRectangleToSize(rect, tests[0].size_, + NSScaleNone); + STAssertEquals(result, rect, nil); + +} +@end diff --git a/AppKit/GTMLinearRGBShading.h b/AppKit/GTMLinearRGBShading.h new file mode 100644 index 0000000..f6b6405 --- /dev/null +++ b/AppKit/GTMLinearRGBShading.h @@ -0,0 +1,76 @@ +// +// GTMLinearRGBShading.h +// +// Copyright 2006-2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +// + +#import <Cocoa/Cocoa.h> +#import "GTMShading.h" +#import "GTMCalculatedRange.h" + +/// A shading that does returns smooth linear values for RGB. +// +/// Thus if you create a shading from 0.0->red to 1.0->blue you will get +/// \verbatim +/// - 0.5->purple +/// - 0.75->eggplant +/// - 0.25->magenta +/// \endverbatim + +@interface GTMLinearRGBShading : GTMCalculatedRange <GTMShading> { +@private + CGFunctionRef function_; // function used to calculated shading (STRONG) + CGColorSpaceRef colorSpace_; // colorspace used for shading (STRONG) + BOOL isCalibrated_; // are we using calibrated or device RGB. + float colorValue[4]; // the RGBA color values +} + +/// Generate a shading with color |begin| at position 0.0 and color |end| at 1.0. +// +// Args: +// begin: color at beginning of range +// end: color at end of range +// colorSpaceName: name of colorspace to draw into must be either +// NSCalibratedRGBColorSpace or NSDeviceRGBColorSpace +// +// Returns: +// a GTMLinearRGBShading ++ (id)shadingFromColor:(NSColor *)begin toColor:(NSColor *)end + fromSpaceNamed:(NSString*)colorSpaceName; + +/// Generate a shading with a collection of colors at various positions. +// +// Args: +// colors: a C style array containg the colors we are adding +// colorSpaceName: name of colorspace to draw into must be either +// NSCalibratedRGBColorSpace or NSDeviceRGBColorSpace +// positions: a C style array containg the positions we want to +// add the colors at +// numberOfColors: how many colors/positions we are adding +// +// Returns: +// a GTMLinearRGBShading ++ (id)shadingWithColors:(NSColor **)colors + fromSpaceNamed:(NSString*)colorSpaceName + atPositions:(float *)positions + count:(unsigned int)numberOfColors; + +/// Designated initializer +// Args: +// colorSpaceName - name of the colorspace to use must be either +// NSCalibratedRGBColorSpace or NSDeviceRGBColorSpace +- (id)initWithColorSpaceName:(NSString*)colorSpaceName; + +@end diff --git a/AppKit/GTMLinearRGBShading.m b/AppKit/GTMLinearRGBShading.m new file mode 100644 index 0000000..ab5bda6 --- /dev/null +++ b/AppKit/GTMLinearRGBShading.m @@ -0,0 +1,193 @@ +// +// GTMLinearRGBShading.m +// +// Copyright 2006-2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +// + +#import "GTMLinearRGBShading.h" + +// Carbon callback function required for CoreGraphics +static void cShadeFunction(void *info, const float *inPos, float *outVals); + +@implementation GTMLinearRGBShading ++ (id)shadingFromColor:(NSColor *)begin toColor:(NSColor *)end + fromSpaceNamed:(NSString*)colorSpaceName { + NSColor *theColors[] = { begin, end }; + float thePositions[] = { 0.0f, 1.0f }; + return [[self class] shadingWithColors:theColors + fromSpaceNamed:colorSpaceName + atPositions:thePositions + count:(sizeof(thePositions)/sizeof(float))]; +} + ++ (id)shadingWithColors:(NSColor **)colors fromSpaceNamed:(NSString*)colorSpaceName + atPositions:(float *)positions count:(unsigned)count { + + GTMLinearRGBShading *theShading = [[[[self class] alloc] initWithColorSpaceName:colorSpaceName] autorelease]; + for (unsigned int i = 0; i < count; ++i) { + [theShading insertStop:colors[i] atPosition:positions[i]]; + } + return theShading; +} + +- (id)initWithColorSpaceName:(NSString*)colorSpaceName { + if ((self = [super init])) { + if ([colorSpaceName isEqualToString:NSDeviceRGBColorSpace]) { + isCalibrated_ = NO; + } else if ([colorSpaceName isEqualToString:NSCalibratedRGBColorSpace]) { + isCalibrated_ = YES; + } + else { + [self dealloc]; + self = nil; + } + } + return self; +} + +- (void) dealloc { + if (nil != function_) { + CGFunctionRelease(function_); + } + if (nil != colorSpace_) { + CGColorSpaceRelease(colorSpace_); + } + [super dealloc]; +} + + +- (void)insertStop:(id)item atPosition:(float)position { + NSString *colorSpaceName = isCalibrated_ ? NSCalibratedRGBColorSpace : NSDeviceRGBColorSpace; + NSColor *tempColor = [item colorUsingColorSpaceName: colorSpaceName]; + if (nil != tempColor) { + [super insertStop:tempColor atPosition:position]; + } +} + +// Calculate a linear value based on our stops +- (id)valueAtPosition:(float)position { + unsigned int index = 0; + unsigned int colorCount = [self stopCount]; + float stop1Position = 0.0f; + NSColor *stop1Color = [self stopAtIndex:index position:&stop1Position]; + index += 1; + float stop2Position = 0.0f; + NSColor *stop2Color = nil; + NSColor *theColor = nil; + if (colorCount > 1) { + stop2Color = [self stopAtIndex:index position:&stop2Position]; + index += 1; + } else { + // if we only have one value, that's what we return + stop2Position = stop1Position; + stop2Color = stop1Color; + } + + while (index < colorCount && stop2Position < position) { + stop1Color = stop2Color; + stop1Position = stop2Position; + stop2Color = [self stopAtIndex:index position:&stop2Position]; + index += 1; + } + + if (position <= stop1Position) { + // if we are less than our lowest position, return our first color + theColor = stop1Color; + [stop1Color getRed:&colorValue[0] green:&colorValue[1] + blue:&colorValue[2] alpha:&colorValue[3]]; + } else if (position >= stop2Position) { + // likewise if we are greater than our highest position, return the last color + [stop2Color getRed:&colorValue[0] green:&colorValue[1] + blue:&colorValue[2] alpha:&colorValue[3]]; + } else { + // otherwise interpolate between the two + position = (position - stop1Position) / (stop2Position - stop1Position); + float red1, red2, green1, green2, blue1, blue2, alpha1, alpha2; + [stop1Color getRed:&red1 green:&green1 blue:&blue1 alpha:&alpha1]; + [stop2Color getRed:&red2 green:&green2 blue:&blue2 alpha:&alpha2]; + + colorValue[0] = (red2 - red1) * position + red1; + colorValue[1] = (green2 - green1) * position + green1; + colorValue[2] = (blue2 - blue1) * position + blue1; + colorValue[3] = (alpha2 - alpha1) * position + alpha1; + } + + // Yes, I am casting a float[] to an id to pass it by the compiler. This + // significantly improves performance though as I avoid creating an NSColor + // for every scanline which later has to be cleaned up in an autorelease pool + // somewhere. Causes guardmalloc to run significantly faster. + return (id)colorValue; +} + +// +// switch from C to obj-C. The callback to a shader is a c function +// but we want to call our objective c object to do all the +// calculations for us. We have passed our function our +// GTMLinearRGBShading as an obj-c object in the |info| so +// we just turn around and ask it to calculate our value based +// on |inPos| and then stick the results back in |outVals| +// +// Args: +// info: is the GTMLinearRGBShading as an +// obj-C object. +// inPos: the position to calculate values for. This is a pointer to +// a single float value +// outVals: where we store our return values. Since we are calculating +// an RGBA color, this is a pointer to an array of four float values +// ranging from 0.0f to 1.0f +// +// +static void cShadeFunction(void *info, const float *inPos, float *outVals) { + id object = (id)info; + float *colorValue = (float*)[object valueAtPosition:*inPos]; + outVals[0] = colorValue[0]; + outVals[1] = colorValue[1]; + outVals[2] = colorValue[2]; + outVals[3] = colorValue[3]; +} + +- (CGFunctionRef) shadeFunction { + // lazily create the function as necessary + if (nil == function_) { + // We have to go to carbon here, and create the CGFunction. Note that this + // diposed if necessary in the dealloc call. + const CGFunctionCallbacks shadeFunctionCallbacks = { 0, &cShadeFunction, NULL }; + + // TODO: (dmaclach): this code assumes that we have a range from 0.0f to 1.0f + // which may not be true according to the stops that the user has given us. + // In general you have stops at 0.0 and 1.0, so this will do for right now + // but may be an issue in the future. + const float inRange[2] = { 0.0f, 1.0f }; + const float outRange[8] = { 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f }; + function_ = CGFunctionCreate(self, + sizeof(inRange) / (sizeof(float) * 2), inRange, + sizeof(outRange) / (sizeof(float) * 2), outRange, + &shadeFunctionCallbacks); + } + return function_; +} + +- (CGColorSpaceRef)colorSpace { + // lazily create the colorspace as necessary + if (nil == colorSpace_) { + if (isCalibrated_) { + colorSpace_ = CGColorSpaceCreateWithName(kCGColorSpaceUserRGB); + } else { + colorSpace_ = CGColorSpaceCreateDeviceRGB(); + } + } + return colorSpace_; +} +@end diff --git a/AppKit/GTMLinearRGBShadingTest.m b/AppKit/GTMLinearRGBShadingTest.m new file mode 100644 index 0000000..6407e71 --- /dev/null +++ b/AppKit/GTMLinearRGBShadingTest.m @@ -0,0 +1,94 @@ +// +// GTMLinearRGBShadingTest.m +// +// Copyright 2006-2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +// + +#import <SenTestingKit/SenTestingKit.h> +#import "GTMLinearRGBShading.h" +#import "GTMNSColor+Theme.h" + + +@interface GTMLinearRGBShadingTest : SenTestCase +@end + +@implementation GTMLinearRGBShadingTest +- (void)testShadingFrom { + // Create a shading from red to blue, and check if 50% is purple + NSColor *red = [NSColor gtm_deviceRedColor]; + NSColor *blue = [NSColor gtm_deviceBlueColor]; + NSColor *purple = [NSColor gtm_devicePurpleColor]; + GTMLinearRGBShading *theShading = + [GTMLinearRGBShading shadingFromColor:red + toColor:blue + fromSpaceNamed:NSDeviceRGBColorSpace]; + STAssertNotNil(theShading,nil); + STAssertTrue([theShading stopCount] == 2, nil); + float *theColor = (float*)[theShading valueAtPosition: 0.5]; + STAssertTrue(theColor[0] == [purple redComponent] && + theColor[1] == [purple greenComponent] && + theColor[2] == [purple blueComponent] && + theColor[3] == [purple alphaComponent], nil); +} + +- (void)testShadingWith { + // Create a shading with kColorCount colors and make sure all the values are there. + const unsigned int kColorCount = 100; + NSColor *theColors[kColorCount]; + float thePositions[kColorCount]; + const float kColorIncrement = 1.0f / kColorCount; + for (unsigned int i = 0; i < kColorCount; i++) { + thePositions[i] = kColorIncrement * i; + theColors[i] = [NSColor colorWithDeviceRed:kColorIncrement * i + green:kColorIncrement * i + blue:kColorIncrement * i + alpha:kColorIncrement * i]; + } + GTMLinearRGBShading *theShading = + [GTMLinearRGBShading shadingWithColors:theColors + fromSpaceNamed:NSDeviceRGBColorSpace + atPositions:thePositions + count:kColorCount]; + for (unsigned int i = 0; i < kColorCount; i++) { + float *theColor = (float*)[theShading valueAtPosition: kColorIncrement * i]; + STAssertTrue(theColor[0] == kColorIncrement * i && + theColor[1] == kColorIncrement * i && + theColor[2] == kColorIncrement * i && + theColor[3] == kColorIncrement * i, nil); + } +} + +- (void)testShadeFunction { + GTMLinearRGBShading *theShading = + [GTMLinearRGBShading shadingWithColors:nil + fromSpaceNamed:NSDeviceRGBColorSpace + atPositions:nil + count:0]; + CGFunctionRef theFunction = [theShading shadeFunction]; + STAssertTrue(nil != theFunction, nil); + STAssertTrue(CFGetTypeID(theFunction) == CGFunctionGetTypeID(), nil); +} + +- (void)testColorSpace { + GTMLinearRGBShading *theShading = + [GTMLinearRGBShading shadingWithColors:nil + fromSpaceNamed:NSDeviceRGBColorSpace + atPositions:nil + count:0]; + CGColorSpaceRef theColorSpace = [theShading colorSpace]; + STAssertTrue(nil != theColorSpace, nil); + STAssertTrue(CFGetTypeID(theColorSpace) == CGColorSpaceGetTypeID(), nil); +} +@end diff --git a/AppKit/GTMLoginItems.h b/AppKit/GTMLoginItems.h new file mode 100644 index 0000000..1531bd0 --- /dev/null +++ b/AppKit/GTMLoginItems.h @@ -0,0 +1,87 @@ +// +// GTMLoginItems.h +// +// Copyright 2007-2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +// + +#import <Foundation/Foundation.h> + +/// Login items key constants, used as keys in |+loginItems| +// +// Item name +extern NSString * const kGTMLoginItemsNameKey; +// Item path +extern NSString * const kGTMLoginItemsPathKey; +// Hidden (NSNumber bool) +extern NSString * const kGTMLoginItemsHiddenKey; + +/// GTMLoginItems +// +/// A helper class to manipulate the user's Login Items. +@interface GTMLoginItems : NSObject + +/// Obtain a complete list of all login items. +// +// Returns: +// Autoreleased array of dictionaries keyed with kGTMLoginItemsPathKey, etc. +// ++ (NSArray *)loginItems:(NSError **)errorInfo; + +/// Check if the given path is in the current user's Login Items +// +// Args: +// path: path to the application +// +// Returns: +// YES if the path is in the Login Items +// ++ (BOOL)pathInLoginItems:(NSString *)path; + +/// Check if the given name is in the current user's Login Items +// +// Args: +// name: name to the application +// +// Returns: +// YES if the name is in the Login Items +// ++ (BOOL)itemWithNameInLoginItems:(NSString *)name; + +/// Add the given path to the current user's Login Items. Does nothing if the +/// path is already there. +// +// Args: +// path: path to add +// hide: Set to YES to have the item launch hidden +// ++ (void)addPathToLoginItems:(NSString *)path hide:(BOOL)hide; + +/// Remove the given path from the current user's Login Items. Does nothing if +/// the path is not there. +// +// Args: +// path: the path to remove +// ++ (void)removePathFromLoginItems:(NSString *)path; + +/// Remove the given item name from the current user's Login Items. Does nothing +/// if no item with that name is present. +// +// Args: +// name: name of the item to remove +// ++ (void)removeItemWithNameFromLoginItems:(NSString *)name; + +@end diff --git a/AppKit/GTMLoginItems.m b/AppKit/GTMLoginItems.m new file mode 100644 index 0000000..d3197c0 --- /dev/null +++ b/AppKit/GTMLoginItems.m @@ -0,0 +1,183 @@ +// +// GTMLoginItems.m +// Based on AELoginItems from DTS. +// +// Copyright 2007-2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +// + +#import "GTMLoginItems.h" + +#include <Carbon/Carbon.h> + +// Exposed constants +NSString * const kGTMLoginItemsNameKey = @"Name"; +NSString * const kGTMLoginItemsPathKey = @"Path"; +NSString * const kGTMLoginItemsHiddenKey = @"Hide"; + +@interface GTMLoginItems (PrivateMethods) ++ (int)indexOfLoginItemWithValue:(id)value + forKey:(NSString *)key + loginItems:(NSArray *)items; ++ (BOOL)compileAndRunScript:(NSString *)script + withError:(NSError **)errorInfo; +@end + +@implementation GTMLoginItems (PrivateMethods) + ++ (int)indexOfLoginItemWithValue:(id)value + forKey:(NSString *)key + loginItems:(NSArray *)items { + if (!value || !key || !items) return NSNotFound; + NSDictionary *item = nil; + NSEnumerator *itemsEnum = [items objectEnumerator]; + int found = -1; + while ((item = [itemsEnum nextObject])) { + ++found; + id itemValue = [item objectForKey:key]; + if (itemValue && [itemValue isEqual:value]) { + return found; + } + } + return NSNotFound; +} + ++ (BOOL)compileAndRunScript:(NSString *)script + withError:(NSError **)errorInfo { + if ([script length] == 0) { + if (errorInfo) + *errorInfo = [NSError errorWithDomain:@"GTMLoginItems" code:-90 userInfo:nil]; + return NO; + } + NSAppleScript *query = [[[NSAppleScript alloc] initWithSource:script] autorelease]; + NSDictionary *errDict = nil; + if ( ![query compileAndReturnError:&errDict]) { + if (errorInfo) + *errorInfo = [NSError errorWithDomain:@"GTMLoginItems" code:-91 userInfo:errDict]; + return NO; + } + NSAppleEventDescriptor *scriptResult = [query executeAndReturnError:&errDict]; + if (!scriptResult) { + if (*errorInfo) + *errorInfo = [NSError errorWithDomain:@"GTMLoginItems" code:-92 userInfo:errDict]; + return NO; + } + // we don't process the result + return YES; +} + +@end + +@implementation GTMLoginItems + ++ (NSArray*)loginItems:(NSError **)errorInfo { + NSDictionary *errDict = nil; + // get the script compiled and saved off + static NSAppleScript *query = nil; + if (!query) { + NSString *querySource = @"tell application \"System Events\" to get properties of login items"; + query = [[NSAppleScript alloc] initWithSource:querySource]; + if ( ![query compileAndReturnError:&errDict]) { + if (errorInfo) + *errorInfo = [NSError errorWithDomain:@"GTMLoginItems" code:-1 userInfo:errDict]; + [query release]; + query = nil; + return nil; + } + } + // run the script + NSAppleEventDescriptor *scriptResult = [query executeAndReturnError:&errDict]; + if (!scriptResult) { + if (*errorInfo) + *errorInfo = [NSError errorWithDomain:@"GTMLoginItems" code:-2 userInfo:errDict]; + return nil; + } + // build our results + NSMutableArray *result = [NSMutableArray array]; + int count = [scriptResult numberOfItems]; + for (int i = 0; i < count; ++i) { + NSAppleEventDescriptor *aeItem = [scriptResult descriptorAtIndex:i+1]; + NSAppleEventDescriptor *hidn = [aeItem descriptorForKeyword:kAEHidden]; + NSAppleEventDescriptor *nam = [aeItem descriptorForKeyword:pName]; + NSAppleEventDescriptor *ppth = [aeItem descriptorForKeyword:'ppth']; + NSMutableDictionary *item = [NSMutableDictionary dictionary]; + if (hidn && [hidn booleanValue]) { + [item setObject:[NSNumber numberWithBool:YES] forKey:kGTMLoginItemsHiddenKey]; + } + if (nam) { + NSString *name = [nam stringValue]; + if (name) { + [item setObject:name forKey:kGTMLoginItemsNameKey]; + } + } + if (ppth) { + NSString *path = [ppth stringValue]; + if (path) { + [item setObject:path forKey:kGTMLoginItemsPathKey]; + } + } + [result addObject:item]; + } + + return result; +} + ++ (BOOL)pathInLoginItems:(NSString *)path { + NSArray *loginItems = [self loginItems:nil]; + int index = [self indexOfLoginItemWithValue:path + forKey:kGTMLoginItemsPathKey + loginItems:loginItems]; + return (index != NSNotFound) ? YES : NO; +} + ++ (BOOL)itemWithNameInLoginItems:(NSString *)name { + NSArray *loginItems = [self loginItems:nil]; + int index = [self indexOfLoginItemWithValue:name + forKey:kGTMLoginItemsNameKey + loginItems:loginItems]; + return (index != NSNotFound) ? YES : NO; +} + ++ (void)addPathToLoginItems:(NSString*)path hide:(BOOL)hide { + if (!path) return; + // make sure it isn't already there + if ([self pathInLoginItems:path]) return; + // now append it + NSString *scriptSource = + [NSString stringWithFormat: + @"tell application \"System Events\" to make new login item with properties { path:\"%s\", hidden:%s } at end", + [path UTF8String], + (hide ? "yes" : "no")]; + [self compileAndRunScript:scriptSource withError:nil]; +} + ++ (void)removePathFromLoginItems:(NSString*)path { + if ([path length] == 0) return; + NSString *scriptSource = + [NSString stringWithFormat: + @"tell application \"System Events\" to delete (login items whose path is \"%s\")", + [path UTF8String]]; + [self compileAndRunScript:scriptSource withError:nil]; +} + ++ (void)removeItemWithNameFromLoginItems:(NSString *)name { + if ([name length] == 0) return; + NSString *scriptSource = + [NSString stringWithFormat: + @"tell application \"System Events\" to delete (login items whose name is \"%s\")", + [name UTF8String]]; + [self compileAndRunScript:scriptSource withError:nil]; +} + +@end diff --git a/AppKit/GTMLoginItemsTest.m b/AppKit/GTMLoginItemsTest.m new file mode 100644 index 0000000..05a93a1 --- /dev/null +++ b/AppKit/GTMLoginItemsTest.m @@ -0,0 +1,112 @@ +// +// GTMLoginItemsTest.m +// +// Copyright 2006-2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +// + +#import <SenTestingKit/SenTestingKit.h> +#import "GTMSenTestCase.h" +#import "GTMLoginItems.h" + + // we don't really run this test because if someone had it in some automated + // tests, then if something did fail, it could leave things in the login items + // on the computer which could be a nasty surprise. +#define TESTS_ENABLED 0 + + +@interface GTMLoginItemsTest : SenTestCase +@end + +#if TESTS_ENABLED + +static BOOL ItemsListHasPath(NSArray *items, NSString *path) { + NSDictionary *item = nil; + NSEnumerator *itemsEnum = [items objectEnumerator]; + while ((item = [itemsEnum nextObject])) { + NSString *itemPath = [item objectForKey:kGTMLoginItemsPathKey]; + if (itemPath && [itemPath isEqual:path]) { + return YES; + } + } + return NO; +} + +#endif // TESTS_ENABLED + +@implementation GTMLoginItemsTest + +- (void)testGTMLoginItems { + +#if TESTS_ENABLED + + NSError *error = nil; + NSString *textEditPath = @"/Applications/TextEdit.app"; + NSString *textEditName = @"TextEdit"; + + // fetch the starting values + NSArray *initialItems = [GTMLoginItems loginItems:&error]; + STAssertNotNil(initialItems, @"shouldn't be nil (%@)", error); + STAssertFalse(ItemsListHasPath(initialItems, textEditPath), + @"textedit shouldn't be in list to start for test (%@)", initialItems); + + // add textedit + [GTMLoginItems addPathToLoginItems:textEditPath hide:NO]; + NSArray *curItems = [GTMLoginItems loginItems:nil]; + STAssertNotEqualObjects(initialItems, curItems, nil); + + // check by path + STAssertTrue([GTMLoginItems pathInLoginItems:textEditPath], nil); + + // check by name + STAssertTrue([GTMLoginItems itemWithNameInLoginItems:textEditName], nil); + + // remove it by path + [GTMLoginItems removePathFromLoginItems:textEditPath]; + curItems = [GTMLoginItems loginItems:nil]; + STAssertEqualObjects(initialItems, curItems, nil); + + // check by path + STAssertFalse([GTMLoginItems pathInLoginItems:textEditPath], nil); + + // check by name + STAssertFalse([GTMLoginItems itemWithNameInLoginItems:textEditName], nil); + + // add textedit + [GTMLoginItems addPathToLoginItems:textEditPath hide:NO]; + curItems = [GTMLoginItems loginItems:nil]; + STAssertNotEqualObjects(initialItems, curItems, nil); + + // check by path + STAssertTrue([GTMLoginItems pathInLoginItems:textEditPath], nil); + + // check by name + STAssertTrue([GTMLoginItems itemWithNameInLoginItems:textEditName], nil); + + // remove it by name + [GTMLoginItems removeItemWithNameFromLoginItems:textEditName]; + curItems = [GTMLoginItems loginItems:nil]; + STAssertEqualObjects(initialItems, curItems, nil); + + // check by path + STAssertFalse([GTMLoginItems pathInLoginItems:textEditPath], nil); + + // check by name + STAssertFalse([GTMLoginItems itemWithNameInLoginItems:textEditName], nil); + +#endif // TESTS_ENABLED + +} + +@end diff --git a/AppKit/GTMNSBezierPath+CGPath.h b/AppKit/GTMNSBezierPath+CGPath.h new file mode 100644 index 0000000..3349142 --- /dev/null +++ b/AppKit/GTMNSBezierPath+CGPath.h @@ -0,0 +1,33 @@ +// +// GTMNSBezierPath+CGPath.h +// +// Copyright 2006-2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +// + +#import <Cocoa/Cocoa.h> + +/// Category for extracting a CGPathRef from a NSBezierPath +@interface NSBezierPath (GTMBezierPathCGPathAdditions) + +/// Extract a CGPathRef from a NSBezierPath. +// +// Args: +// +// Returns: +// Converted CGPathRef. Must be released by client (CGPathRelease). +// nil if failure. +- (CGPathRef)gtm_createCGPath; + +@end diff --git a/AppKit/GTMNSBezierPath+CGPath.m b/AppKit/GTMNSBezierPath+CGPath.m new file mode 100644 index 0000000..97464fc --- /dev/null +++ b/AppKit/GTMNSBezierPath+CGPath.m @@ -0,0 +1,68 @@ +// +// GTMNSBezierPath+CGPath.h +// +// Category for extracting a CGPathRef from a NSBezierPath +// +// Copyright 2006-2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +// +#import "GTMNSBezierPath+CGPath.h" + +@implementation NSBezierPath (GTMBezierPathCGPathAdditions) + +// Extract a CGPathRef from a NSBezierPath. +// +// Args: +// +// Returns: +// Converted CGPathRef. Must be released by client (CGPathRelease). +// nil if failure. +- (CGPathRef)gtm_createCGPath { + CGMutablePathRef thePath = CGPathCreateMutable(); + if (!thePath) return nil; + + unsigned int elementCount = [self elementCount]; + + // The maximum number of points is 3 for a NSCurveToBezierPathElement. + // (controlPoint1, controlPoint2, and endPoint) + NSPoint controlPoints[3]; + + for (unsigned int i = 0; i < elementCount; i++) { + switch ([self elementAtIndex:i associatedPoints:controlPoints]) { + case NSMoveToBezierPathElement: + CGPathMoveToPoint(thePath, &CGAffineTransformIdentity, + controlPoints[0].x, controlPoints[0].y); + break; + case NSLineToBezierPathElement: + CGPathAddLineToPoint(thePath, &CGAffineTransformIdentity, + controlPoints[0].x, controlPoints[0].y); + break; + case NSCurveToBezierPathElement: + CGPathAddCurveToPoint(thePath, &CGAffineTransformIdentity, + controlPoints[0].x, controlPoints[0].y, + controlPoints[1].x, controlPoints[1].y, + controlPoints[2].x, controlPoints[2].y); + break; + case NSClosePathBezierPathElement: + CGPathCloseSubpath(thePath); + break; + default: + NSLog(@"Unknown element at [NSBezierPath (GTMBezierPathCGPathAdditions) cgPath]"); + break; + }; + } + return thePath; +} + +@end diff --git a/AppKit/GTMNSBezierPath+CGPathTest.m b/AppKit/GTMNSBezierPath+CGPathTest.m new file mode 100644 index 0000000..c8506d7 --- /dev/null +++ b/AppKit/GTMNSBezierPath+CGPathTest.m @@ -0,0 +1,78 @@ +// +// GTMNSBezierPath+CGPathTest.m +// +// Copyright 2006-2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +// + +#import <Cocoa/Cocoa.h> + +#import <SenTestingKit/SenTestingKit.h> +#import "GTMNSBezierPath+CGPath.h" +#import "GTMNSView+UnitTesting.h" + +@interface GTMNSBezierPath_CGPathTest : SenTestCase<GTMUnitTestViewDrawer> +@end + +@implementation GTMNSBezierPath_CGPathTest + +- (void)testCreateCGPath { + GTMAssertDrawingEqualToFile(self, NSMakeSize(100, 100), @"GTMNSBezierPath+CGPathTest", nil, nil); +} + + +// Draws all of our tests so that we can compare this to our stored TIFF file. +- (void)unitTestViewDrawRect:(NSRect)rect contextInfo:(void*)contextInfo{ + NSBezierPath *thePath = [NSBezierPath bezierPath]; + NSPoint theStart = NSMakePoint(20.0f, 20.0f); + + // Test moveto/lineto + [thePath moveToPoint: theStart]; + for (unsigned int i = 0; i < 10; ++i) { + NSPoint theNewPoint = NSMakePoint(i * 5, i * 10); + [thePath lineToPoint: theNewPoint]; + theNewPoint = NSMakePoint(i * 2, i * 6); + [thePath moveToPoint: theNewPoint]; + } + + // Test moveto/curveto + for (unsigned int i = 0; i < 10; ++i) { + NSPoint startPoint = NSMakePoint(5.0f, 50.0f); + NSPoint endPoint = NSMakePoint(55.0f, 50.0f); + NSPoint controlPoint1 = NSMakePoint(17.5f, 50.0f + 5.0f * i); + NSPoint controlPoint2 = NSMakePoint(42.5f, 50.0f - 5.0f * i); + [thePath moveToPoint:startPoint]; + [thePath curveToPoint:endPoint controlPoint1:controlPoint1 controlPoint2:controlPoint2]; + } + // test close + [thePath closePath]; + + CGPathRef cgPath = [thePath gtm_createCGPath]; + if (nil == cgPath) { + @throw [NSException failureInFile:[NSString stringWithCString:__FILE__] + atLine:__LINE__ + withDescription:@"Nil CGPath"]; + } + CGContextRef cgContext = [[NSGraphicsContext currentContext] graphicsPort]; + if (nil == cgContext) { + @throw [NSException failureInFile:[NSString stringWithCString:__FILE__] + atLine:__LINE__ + withDescription:@"Nil CGContext"]; + } + CGContextAddPath(cgContext, cgPath); + CGContextStrokePath(cgContext); + CGPathRelease(cgPath); +} + +@end diff --git a/AppKit/GTMNSBezierPath+CGPathTest.tif b/AppKit/GTMNSBezierPath+CGPathTest.tif Binary files differnew file mode 100644 index 0000000..a7fee9b --- /dev/null +++ b/AppKit/GTMNSBezierPath+CGPathTest.tif diff --git a/AppKit/GTMNSBezierPath+RoundRect.h b/AppKit/GTMNSBezierPath+RoundRect.h new file mode 100644 index 0000000..8e916fe --- /dev/null +++ b/AppKit/GTMNSBezierPath+RoundRect.h @@ -0,0 +1,45 @@ +// +// GTMNSBezierPath+RoundRect.h +// +// Category for adding utility functions for creating +// round rectangles. +// +// Copyright 2006-2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +// + +#import <Cocoa/Cocoa.h> + +/// Category for adding utility functions for creating round rectangles. +@interface NSBezierPath (GMBezierPathRoundRectAdditions) + +/// Inscribe a round rectangle inside of rectangle |rect| with a corner radius of |radius| +// +// Args: +// rect: outer rectangle to inscribe into +// radius: radius of the corners. |radius| is clamped internally +// to be no larger than the smaller of half |rect|'s width or height +// +// Returns: +// Auto released NSBezierPath ++ (NSBezierPath *)gtm_bezierPathWithRoundRect:(NSRect)rect cornerRadius:(float)radius; + +/// Adds a path which is a round rectangle inscribed inside of rectangle |rect| with a corner radius of |radius| +// +// Args: +// rect: outer rectangle to inscribe into +// radius: radius of the corners. |radius| is clamped internally +// to be no larger than the smaller of half |rect|'s width or height +- (void)gtm_appendBezierPathWithRoundRect:(NSRect)rect cornerRadius:(float)radius; +@end diff --git a/AppKit/GTMNSBezierPath+RoundRect.m b/AppKit/GTMNSBezierPath+RoundRect.m new file mode 100644 index 0000000..4b98dc9 --- /dev/null +++ b/AppKit/GTMNSBezierPath+RoundRect.m @@ -0,0 +1,58 @@ +// +// GTMNSBezierPath+RoundRect.h +// +// Category for adding utility functions for creating +// round rectangles. +// +// Copyright 2006-2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +// + +#import "GTMNSBezierPath+RoundRect.h" + +@implementation NSBezierPath (GTMBezierPathRoundRectAdditions) + + ++ (NSBezierPath *)gtm_bezierPathWithRoundRect:(NSRect)rect cornerRadius:(float)radius { + NSBezierPath *bezier = [NSBezierPath bezierPath]; + [bezier gtm_appendBezierPathWithRoundRect:rect cornerRadius:radius]; + return bezier; +} + + +- (void)gtm_appendBezierPathWithRoundRect:(NSRect)rect cornerRadius:(float)radius { + if (!NSIsEmptyRect(rect)) { + if (radius > 0.0) { + // Clamp radius to be no larger than half the rect's width or height. + radius = MIN(radius, 0.5 * MIN(rect.size.width, rect.size.height)); + + NSPoint topLeft = NSMakePoint(NSMinX(rect), NSMaxY(rect)); + NSPoint topRight = NSMakePoint(NSMaxX(rect), NSMaxY(rect)); + NSPoint bottomRight = NSMakePoint(NSMaxX(rect), NSMinY(rect)); + + [self moveToPoint:NSMakePoint(NSMidX(rect), NSMaxY(rect))]; + [self appendBezierPathWithArcFromPoint:topLeft toPoint:rect.origin radius:radius]; + [self appendBezierPathWithArcFromPoint:rect.origin toPoint:bottomRight radius:radius]; + [self appendBezierPathWithArcFromPoint:bottomRight toPoint:topRight radius:radius]; + [self appendBezierPathWithArcFromPoint:topRight toPoint:topLeft radius:radius]; + [self closePath]; + } else { + // When radius <= 0.0, use plain rectangle. + [self appendBezierPathWithRect:rect]; + } + } +} + + +@end diff --git a/AppKit/GTMNSBezierPath+RoundRectTest.m b/AppKit/GTMNSBezierPath+RoundRectTest.m new file mode 100644 index 0000000..61bd2dd --- /dev/null +++ b/AppKit/GTMNSBezierPath+RoundRectTest.m @@ -0,0 +1,93 @@ +// +// NSBezierPath+RoundRectTest.m +// +// Copyright 2006-2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +// + +#import <Cocoa/Cocoa.h> + +#import <SenTestingKit/SenTestingKit.h> +#import "GTMNSBezierPath+RoundRect.h" +#import "GTMNSView+UnitTesting.h" + +@interface GTMNSBezierPath_RoundRectTest : SenTestCase<GTMUnitTestViewDrawer> +@end + +@implementation GTMNSBezierPath_RoundRectTest + +- (void)testRoundRects { + GTMAssertDrawingEqualToFile(self, NSMakeSize(330, 430), @"GTMNSBezierPath+RoundRectTest", nil, nil); +} + + +// Draws all of our tests so that we can compare this to our stored TIFF file. +- (void)unitTestViewDrawRect:(NSRect)rect contextInfo:(void*)contextInfo{ + NSRect theRects[] = { + NSMakeRect(0.0f, 10.0f, 0.0f, 0.0f), //Empty Rect test + NSMakeRect(50.0f, 10.0f, 30.0f, 30.0f), //Square Test + NSMakeRect(100.0f, 10.0f, 1.0f, 2.0f), //Small Test + NSMakeRect(120.0f, 10.0f, 15.0f, 20.0f), //Medium Test + NSMakeRect(140.0f, 10.0f, 150.0f, 30.0f) //Large Test + }; + const unsigned int theRectCount = sizeof(theRects) / sizeof(NSRect); + + // Line Width Tests + float theLineWidths[] = { 0.5f, 50.0f, 2.0f }; + const unsigned int theLineWidthCount = sizeof(theLineWidths) / sizeof(float); + unsigned int i,j; + + for (i = 0; i < theLineWidthCount; ++i) { + for (j = 0; j < theRectCount; ++j) { + NSBezierPath *roundRect = [NSBezierPath gtm_bezierPathWithRoundRect:theRects[j] cornerRadius:20.0f]; + [roundRect setLineWidth: theLineWidths[i]]; + [roundRect stroke]; + float newWidth = 35.0f; + if (i < theLineWidthCount - 1) newWidth += theLineWidths[i + 1] + theLineWidths[i]; + theRects[j].origin.y += newWidth; + } + } + + // Fill test + NSColor *theColors[] = { + [NSColor colorWithDeviceRed:1.0f green:0.0f blue:0.0f alpha:1.0f], + [NSColor colorWithDeviceRed:0.2f green:0.4f blue:0.6f alpha:0.4f] + }; + const unsigned int theColorCount = sizeof(theColors)/sizeof(NSColor); + + for (i = 0; i < theColorCount; ++i) { + for (j = 0; j < theRectCount; ++j) { + NSBezierPath *roundRect = [NSBezierPath gtm_bezierPathWithRoundRect:theRects[j] cornerRadius:10.0f]; + [theColors[i] setFill]; + [roundRect fill]; + theRects[j].origin.y += 35.0f; + } + } + + // Flatness test + float theFlatness[] = {0.0f, 0.1f, 1.0f, 10.0f}; + const unsigned int theFlatnessCount = sizeof(theFlatness)/sizeof(float); + + for (i = 0; i < theFlatnessCount; i++) { + for (j = 0; j < theRectCount; ++j) { + NSBezierPath *roundRect = [NSBezierPath gtm_bezierPathWithRoundRect:theRects[j] cornerRadius:6.0f]; + [roundRect setFlatness:theFlatness[i]]; + [roundRect stroke]; + theRects[j].origin.y += 35.0f; + } + } +} + + +@end diff --git a/AppKit/GTMNSBezierPath+RoundRectTest.tif b/AppKit/GTMNSBezierPath+RoundRectTest.tif Binary files differnew file mode 100644 index 0000000..7ce0d45 --- /dev/null +++ b/AppKit/GTMNSBezierPath+RoundRectTest.tif diff --git a/AppKit/GTMNSBezierPath+Shading.h b/AppKit/GTMNSBezierPath+Shading.h new file mode 100644 index 0000000..b67043c --- /dev/null +++ b/AppKit/GTMNSBezierPath+Shading.h @@ -0,0 +1,120 @@ +// +// GTMNSBezierPath+Shading.h +// +// Category for radial and axial stroke and fill functions for NSBezierPaths +// +// Copyright 2006-2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +// + +#import <Cocoa/Cocoa.h> + +@protocol GTMShading; + +// +/// Category for radial and axial stroke and fill functions for NSBezierPaths +// +@interface NSBezierPath (GTMBezierPathShadingAdditions) + +/// Stroke the path axially with a color blend defined by |shading|. +// +/// The fill will extend from |fromPoint| to |toPoint| and will extend +/// indefinitely perpendicular to the axis of the line defined by the +/// two points. You can extend beyond the |fromPoint|/|toPoint by setting +/// |extendingStart|/|extendingEnd| respectively. +// +// Args: +// fromPoint: point to start the shading at +// toPoint: point to end the shading at +// extendingStart: should we extend the shading before |fromPoint| using +// the first color in our shading? +// extendingEnd: should we extend the shading after |toPoint| using the +// last color in our shading? +// shading: the shading to use to take our colors from. +// +- (void)gtm_strokeAxiallyFrom:(NSPoint)fromPoint to:(NSPoint)toPoint + extendingStart:(BOOL)extendingStart extendingEnd:(BOOL)extendingEnd + shading:(id<GTMShading>)shading; + +/// Stroke the path radially with a color blend defined by |shading|. +// +/// The fill will extend from the circle with center |fromPoint| +/// and radius |fromRadius| to the circle with center |toPoint| +/// with radius |toRadius|. +/// You can extend beyond the |fromPoint|/|toPoint| by setting +/// |extendingStart|/|extendingEnd| respectively. +// +// Args: +// fromPoint: center of the circle to start the shading at +// fromRadius: radius of the circle to start the shading at +// toPoint: center of the circle to to end the shading at +// toRadius: raidus of the circle to end the shading at +// extendingStart: should we extend the shading before |fromPoint| using +// the first color in our shading? +// extendingEnd: should we extend the shading after |toPoint| using the +// last color in our shading? +// shading: the shading to use to take our colors from. +// +- (void)gtm_strokeRadiallyFrom:(NSPoint)fromPoint fromRadius:(float)fromRadius + to:(NSPoint)toPoint toRadius:(float)toRadius + extendingStart:(BOOL)extendingStart extendingEnd:(BOOL)extendingEnd + shading:(id<GTMShading>)shading; + +/// Fill the path radially with a color blend defined by |shading|. +// +/// The fill will extend from the circle with center |fromPoint| +/// and radius |fromRadius| to the circle with center |toPoint| +/// with radius |toRadius|. +/// You can extend beyond the |fromPoint|/|toPoint by setting +/// |extendingStart|/|extendingEnd| respectively. +// +// Args: +// fromPoint: center of the circle to start the shading at +// fromRadius: radius of the circle to start the shading at +// toPoint: center of the circle to to end the shading at +// toRadius: radius of the circle to end the shading at +// extendingStart: should we extend the shading before |fromPoint| using +// the first color in our shading? +// extendingEnd: should we extend the shading after |toPoint| using the +// last color in our shading? +// shading: the shading to use to take our colors from. +// +- (void)gtm_fillAxiallyFrom:(NSPoint)fromPoint to:(NSPoint)toPoint + extendingStart:(BOOL)extendingStart extendingEnd:(BOOL)extendingEnd + shading:(id<GTMShading>)shading; + +/// Fill the path radially with a color blend defined by |shading|. +// +/// The fill will extend from the circle with center |fromPoint| +/// and radius |fromRadius| to the circle with center |toPoint| +/// with radius |toRadius|. +/// You can extend beyond the |fromPoint|/|toPoint by setting +/// |extendingStart|/|extendingEnd| respectively. +// +// Args: +// fromPoint: center of the circle to start the shading at +// fromRadius: radius of the circle to start the shading at +// toPoint: center of the circle to to end the shading at +// toRadius: radius of the circle to end the shading at +// extendingStart: should we extend the shading before |fromPoint| using +// the first color in our shading? +// extendingEnd: should we extend the shading after |toPoint| using the +// last color in our shading? +// shading: the shading to use to take our colors from. +// +- (void)gtm_fillRadiallyFrom:(NSPoint)fromPoint fromRadius:(float)fromRadius + to:(NSPoint)toPoint toRadius:(float)toRadius + extendingStart:(BOOL)extendingStart extendingEnd:(BOOL)extendingEnd + shading:(id<GTMShading>)shading; +@end diff --git a/AppKit/GTMNSBezierPath+Shading.m b/AppKit/GTMNSBezierPath+Shading.m new file mode 100644 index 0000000..f5935ab --- /dev/null +++ b/AppKit/GTMNSBezierPath+Shading.m @@ -0,0 +1,214 @@ +// +// GTMNSBezierPath+Shading.m +// +// Category for radial and axial stroke and fill functions for NSBezierPaths +// +// Copyright 2006-2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +// + +#import "GTMNSBezierPath+Shading.h" +#import "GTMNSBezierPath+CGPath.h" +#import "GTMShading.h" +#import "GTMGeometryUtils.h" + +@interface NSBezierPath (GTMBezierPathShadingAdditionsPrivate) +// Fills a CGPathRef either axially or radially with the given shading. +// +// Args: +// path: path to fill +// axially: if YES fill axially, otherwise fill radially +// asStroke: if YES, clip to the stroke of the path, otherwise +// clip to the fill +// from: where to shade from +// fromRadius: in a radial fill, the radius of the from circle +// to: where to shade to +// toRadius: in a radial fill, the radius of the to circle +// extendingStart: if true, extend the fill with the first color of the shade +// beyond |from| away from |to| +// extendingEnd: if true, extend the fill with the last color of the shade +// beyond |to| away from |from| +// shading: the shading to use for the fill +// +- (void)gtm_fillCGPath:(CGPathRef)path + axially:(BOOL)axially + asStroke:(BOOL)asStroke + from:(NSPoint)fromPoint fromRadius:(float)fromRadius + to:(NSPoint)toPoint toRadius:(float)toRadius + extendingStart:(BOOL)extendingStart extendingEnd:(BOOL)extendingEnd + shading:(id<GTMShading>)shading; + +// Returns the point which is the projection of a line from point |pointA| +// to |pointB| by length +// +// Args: +// pointA: first point +// pointB: second point +// length: distance to project beyond |pointB| which is in line with +// |pointA| and |pointB| +// +// Returns: +// the projected point +- (NSPoint)gtm_projectLineFrom:(NSPoint)pointA + to:(NSPoint)pointB + by:(float)length; +@end + + +@implementation NSBezierPath (GTMBezierPathAdditionsPrivate) + +- (void)gtm_fillCGPath:(CGPathRef)path + axially:(BOOL)axially asStroke:(BOOL)asStroke + from:(NSPoint)fromPoint fromRadius:(float)fromRadius + to:(NSPoint)toPoint toRadius:(float)toRadius + extendingStart:(BOOL)extendingStart extendingEnd:(BOOL)extendingEnd + shading:(id<GTMShading>)shading { + CGFunctionRef shadingFunction = [shading shadeFunction]; + if (nil != shadingFunction) { + CGContextRef currentContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort]; + if (nil != currentContext) { + CGContextSaveGState(currentContext); + float lineWidth = [self lineWidth]; + CGContextSetLineWidth(currentContext,lineWidth); + if (asStroke) { + // if we are using the stroke, we offset the from and to points + // by half the stroke width away from the center of the stroke. + // Otherwise we tend to end up with fills that only cover half of the + // because users set the start and end points based on the center + // of the stroke. + float halfWidth = lineWidth * 0.5f; + fromPoint = [self gtm_projectLineFrom:toPoint to:fromPoint by:halfWidth]; + toPoint = [self gtm_projectLineFrom:fromPoint to:toPoint by:-halfWidth]; + } + CGColorSpaceRef colorspace = [shading colorSpace]; + if (nil != colorspace) { + CGPoint toCGPoint = GTMNSPointToCGPoint(toPoint); + CGPoint fromCGPoint = GTMNSPointToCGPoint(fromPoint); + CGShadingRef myCGShading; + if(axially) { + myCGShading = CGShadingCreateAxial(colorspace, fromCGPoint, + toCGPoint, shadingFunction, + extendingStart == YES, + extendingEnd == YES); + } + else { + myCGShading = CGShadingCreateRadial(colorspace, fromCGPoint, fromRadius, + toCGPoint, toRadius, shadingFunction, + extendingStart == YES, + extendingEnd == YES); + } + + if (nil != myCGShading) { + CGContextAddPath(currentContext,path); + if(asStroke) { + CGContextReplacePathWithStrokedPath(currentContext); + } + CGContextClip(currentContext); + CGContextDrawShading(currentContext, myCGShading); + CGShadingRelease(myCGShading); + } + } + CGContextRestoreGState(currentContext); + } + } +} + + +- (NSPoint)gtm_projectLineFrom:(NSPoint)pointA + to:(NSPoint)pointB + by:(float)length { + NSPoint newPoint = pointB; + float x = (pointB.x - pointA.x); + float y = (pointB.y - pointA.y); + if (x == 0.0f) { + newPoint.y += length; + } else if (y == 0.0f) { + newPoint.x += length; + } else { + float angle = atanf(y / x); + newPoint.x += sinf(angle) * length; + newPoint.y += cosf(angle) * length; + } + return newPoint; +} + +@end + + +@implementation NSBezierPath (GTMBezierPathShadingAdditions) + +//METHOD_CHECK(NSBezierPath, gtm_createCGPath); + +- (void)gtm_strokeAxiallyFrom:(NSPoint)fromPoint to:(NSPoint)toPoint + extendingStart:(BOOL)extendingStart extendingEnd:(BOOL)extendingEnd + shading:(id<GTMShading>)shading { + CGPathRef thePath = [self gtm_createCGPath]; + if (nil != thePath) { + [self gtm_fillCGPath:thePath axially:YES asStroke:YES + from:fromPoint fromRadius:0.0f + to:toPoint toRadius:0.0f + extendingStart:extendingStart extendingEnd:extendingEnd + shading:shading]; + CGPathRelease(thePath); + } +} + + +- (void)gtm_strokeRadiallyFrom:(NSPoint)fromPoint fromRadius:(float)fromRadius + to:(NSPoint)toPoint toRadius:(float)toRadius + extendingStart:(BOOL)extendingStart extendingEnd:(BOOL)extendingEnd + shading:(id<GTMShading>)shading { + CGPathRef thePath = [self gtm_createCGPath]; + if (nil != thePath) { + [self gtm_fillCGPath:thePath axially:NO asStroke:YES + from:fromPoint fromRadius:fromRadius + to:toPoint toRadius:toRadius + extendingStart:extendingStart extendingEnd:extendingEnd + shading:shading]; + CGPathRelease(thePath); + } +} + + +- (void)gtm_fillAxiallyFrom:(NSPoint)fromPoint to:(NSPoint)toPoint + extendingStart:(BOOL)extendingStart extendingEnd:(BOOL)extendingEnd + shading:(id<GTMShading>)shading { + CGPathRef thePath = [self gtm_createCGPath]; + if (nil != thePath) { + [self gtm_fillCGPath:thePath axially:YES asStroke:NO + from:fromPoint fromRadius:0.0f + to:toPoint toRadius:0.0f + extendingStart:extendingStart extendingEnd:extendingEnd + shading:shading]; + CGPathRelease(thePath); + } +} + + +- (void)gtm_fillRadiallyFrom:(NSPoint)fromPoint fromRadius:(float)fromRadius + to:(NSPoint)toPoint toRadius:(float)toRadius + extendingStart:(BOOL)extendingStart extendingEnd:(BOOL)extendingEnd + shading:(id<GTMShading>)shading { + CGPathRef thePath = [self gtm_createCGPath]; + if (nil != thePath) { + [self gtm_fillCGPath:thePath axially:NO asStroke:NO + from:fromPoint fromRadius:fromRadius + to:toPoint toRadius:toRadius + extendingStart:extendingStart extendingEnd:extendingEnd + shading:shading]; + CGPathRelease(thePath); + } +} + +@end diff --git a/AppKit/GTMNSBezierPath+ShadingTest.10.4.tif b/AppKit/GTMNSBezierPath+ShadingTest.10.4.tif Binary files differnew file mode 100644 index 0000000..44a09b5 --- /dev/null +++ b/AppKit/GTMNSBezierPath+ShadingTest.10.4.tif diff --git a/AppKit/GTMNSBezierPath+ShadingTest.10.5.tif b/AppKit/GTMNSBezierPath+ShadingTest.10.5.tif Binary files differnew file mode 100644 index 0000000..6d4570a --- /dev/null +++ b/AppKit/GTMNSBezierPath+ShadingTest.10.5.tif diff --git a/AppKit/GTMNSBezierPath+ShadingTest.m b/AppKit/GTMNSBezierPath+ShadingTest.m new file mode 100644 index 0000000..3883356 --- /dev/null +++ b/AppKit/GTMNSBezierPath+ShadingTest.m @@ -0,0 +1,95 @@ +// +// GTMNSBezierPath+ShadingTest.m +// +// Copyright 2006-2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +// + +#import <Cocoa/Cocoa.h> + +#import <SenTestingKit/SenTestingKit.h> + +#import "GTMLinearRGBShading.h" +#import "GTMNSView+UnitTesting.h" +#import "GTMNSBezierPath+Shading.h" +#import "GTMNSColor+Theme.h" + +@interface GTMNSBezierPath_ShadingTest : SenTestCase<GTMUnitTestViewDrawer> +@end + +@implementation GTMNSBezierPath_ShadingTest + +- (void)testShadings { + GTMAssertDrawingEqualToFile(self, NSMakeSize(200, 200), @"GTMNSBezierPath+ShadingTest", nil, nil); +} + + +- (void)unitTestViewDrawRect:(NSRect)rect contextInfo:(void*)contextInfo{ + + NSColor *theColorArray[] = { [NSColor gtm_deviceBlueColor], + [NSColor gtm_deviceRedColor], [NSColor gtm_deviceYellowColor], + [NSColor gtm_deviceBlueColor], [NSColor gtm_deviceGreenColor], + [NSColor gtm_deviceRedColor] }; + float theFloatArray[] = { 0.0f, 0.2f, 0.4f, 0.6f, 0.8f, 1.00f }; + + GTMLinearRGBShading *shading = + [GTMLinearRGBShading shadingWithColors:theColorArray + fromSpaceNamed:NSDeviceRGBColorSpace + atPositions:theFloatArray + count:sizeof(theFloatArray)/sizeof(float)]; + NSBezierPath *shadedPath; + + // axialStrokeRect test + NSRect axialStrokeRect = NSMakeRect(10.0f, 10.0f, 90.0f, 90.0f); + shadedPath = [NSBezierPath bezierPathWithRect:axialStrokeRect]; + [shadedPath setLineWidth: 10.0f]; + NSPoint startPoint = NSMakePoint(axialStrokeRect.origin.x + 20.0f, + axialStrokeRect.origin.y + 20.0f); + NSPoint endPoint = NSMakePoint(axialStrokeRect.origin.x + axialStrokeRect.size.width - 20.0f, + axialStrokeRect.origin.y + axialStrokeRect.size.height - 20.0f); + [shadedPath gtm_strokeAxiallyFrom:startPoint to:endPoint extendingStart:YES extendingEnd:YES shading:shading]; + + // axial fill + NSRect axialFillRect = NSMakeRect(10.0f, 110.0f, 90.0f, 90.0f); + shadedPath = [NSBezierPath bezierPathWithRect:axialFillRect]; + startPoint = NSMakePoint(axialFillRect.origin.x + 20.0f, + axialFillRect.origin.y + 20.0f); + endPoint = NSMakePoint(axialFillRect.origin.x + axialFillRect.size.width - 20.0f, + axialFillRect.origin.y + axialFillRect.size.height - 20.0f); + [shadedPath gtm_fillAxiallyFrom:startPoint to:endPoint extendingStart:YES extendingEnd:YES shading:shading]; + + // radial stroke + NSRect radialStrokeRect = NSMakeRect(110.0f, 110.0f, 90.0f, 90.0f); + shadedPath = [NSBezierPath bezierPathWithRect:radialStrokeRect]; + startPoint = NSMakePoint(radialStrokeRect.origin.x + 20.0f, + radialStrokeRect.origin.y + 20.0f); + endPoint = NSMakePoint(radialStrokeRect.origin.x + radialStrokeRect.size.width - 20.0f, + radialStrokeRect.origin.y + radialStrokeRect.size.height - 20.0f); + [shadedPath gtm_strokeRadiallyFrom:startPoint fromRadius:60.0f + to:endPoint toRadius:20.0f + extendingStart:YES extendingEnd:YES shading:shading]; + + // radial fill + NSRect radialFillRect = NSMakeRect(110.0f, 10.0f, 90.0f, 90.0f); + shadedPath = [NSBezierPath bezierPathWithRect:radialFillRect]; + startPoint = NSMakePoint(radialFillRect.origin.x + 20.0f, + radialFillRect.origin.y + 20.0f); + endPoint = NSMakePoint(radialFillRect.origin.x + radialFillRect.size.width - 20.0f, + radialFillRect.origin.y + radialFillRect.size.height - 20.0f); + [shadedPath gtm_fillRadiallyFrom:startPoint fromRadius:10.0f + to:endPoint toRadius:20.0f + extendingStart:YES extendingEnd:YES shading:shading]; +} + +@end diff --git a/AppKit/GTMNSColor+Theme.h b/AppKit/GTMNSColor+Theme.h new file mode 100644 index 0000000..ef121eb --- /dev/null +++ b/AppKit/GTMNSColor+Theme.h @@ -0,0 +1,62 @@ +// +// GTMNSColor+Theme.m +// +// Category for working with Themes and NSColor +// +// Copyright 2006-2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +// + +#import <Cocoa/Cocoa.h> +#import <Carbon/Carbon.h> + +/// Category for working with Themes and NSColor +@interface NSColor (GTMColorThemeAdditions) + +/// Create up an NSColor based on a Theme Text Color. +/// Colors will be in the DeviceRGB color space ++ (id)gtm_colorWithThemeTextColor:(ThemeTextColor)textColor; + +/// Create up an NSColor based on a Theme Brush +/// Colors will be in the DeviceRGB color space ++ (id)gtm_colorWithThemeBrush:(ThemeBrush)brush; + +/// Device colors for drawing UI elements and such ++ (NSColor *)gtm_deviceBlackColor; /* 0.0 white */ ++ (NSColor *)gtm_deviceDarkGrayColor; /* 0.333 white */ ++ (NSColor *)gtm_deviceLightGrayColor; /* 0.667 white */ ++ (NSColor *)gtm_deviceWhiteColor; /* 1.0 white */ ++ (NSColor *)gtm_deviceGrayColor; /* 0.5 white */ ++ (NSColor *)gtm_deviceRedColor; /* 1.0, 0.0, 0.0 RGB */ ++ (NSColor *)gtm_deviceGreenColor; /* 0.0, 1.0, 0.0 RGB */ ++ (NSColor *)gtm_deviceBlueColor; /* 0.0, 0.0, 1.0 RGB */ ++ (NSColor *)gtm_deviceCyanColor; /* 0.0, 1.0, 1.0 RGB */ ++ (NSColor *)gtm_deviceYellowColor; /* 1.0, 1.0, 0.0 RGB */ ++ (NSColor *)gtm_deviceMagentaColor; /* 1.0, 0.0, 1.0 RGB */ ++ (NSColor *)gtm_deviceOrangeColor; /* 1.0, 0.5, 0.0 RGB */ ++ (NSColor *)gtm_devicePurpleColor; /* 0.5, 0.0, 0.5 RGB */ ++ (NSColor *)gtm_deviceBrownColor; /* 0.6, 0.4, 0.2 RGB */ ++ (NSColor *)gtm_deviceClearColor; /* 0.0 white, 0.0 alpha */ + +/// default colorWithAlphaComponent has a bug where it doesn't maintain colorspace +// Radar 5047862 [NSColor colorWithAlphaComponent] does NOT maintain colorspace. +- (NSColor *)gtm_safeColorWithAlphaComponent:(float)alpha; + +// gives us a color for any color space. +// Should never return nil, but will if there is an error (out of memory?) +// converting a color to a different colorspace. +// Radar 5608378 Would like theme brush value for the menu item highlight color +// Radar 5608444 colorUsingColorSpaceName doesn't work for NSDeviceRGBColorSpace and SystemColors +- (NSColor *)gtm_safeColorUsingColorSpaceName:(NSString *)colorSpaceName; +@end diff --git a/AppKit/GTMNSColor+Theme.m b/AppKit/GTMNSColor+Theme.m new file mode 100644 index 0000000..b3437e7 --- /dev/null +++ b/AppKit/GTMNSColor+Theme.m @@ -0,0 +1,192 @@ +// +// GTMNSColor+Theme.m +// +// Category for working with Themes and NSColor +// +// Copyright 2006-2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +// + +#import "GTMNSColor+Theme.h" + +@implementation NSColor (GTMColorThemeAdditions) + +/// Create up an NSColor based on a Theme Text Color +/// Colors will be in the device color space ++ (id)gtm_colorWithThemeTextColor:(ThemeTextColor)textColor { + NSColor *nsTextColor = nil; + RGBColor rgbTextColor; + OSStatus status = GetThemeTextColor(textColor, 32, true, &rgbTextColor); + if (status == noErr) { + float red = rgbTextColor.red / 65535.0f; + float green = rgbTextColor.green / 65535.0f; + float blue = rgbTextColor.blue / 65535.0f; + nsTextColor = [NSColor colorWithDeviceRed:red + green:green + blue:blue + alpha:1.0f]; + } else { +#ifdef DEBUG + NSLog(@"Unable to create color for textcolor %d", textColor); +#endif + } + return nsTextColor; +} + +/// Create up an NSColor based on a Theme Brush +/// Colors will be in the DeviceRGB color space ++ (id)gtm_colorWithThemeBrush:(ThemeBrush)brush { + NSColor *nsBrushColor = nil; + RGBColor rgbBrushColor; + OSStatus status = GetThemeBrushAsColor(brush, 32, true, &rgbBrushColor); + if (status == noErr) { + nsBrushColor = [NSColor colorWithDeviceRed:rgbBrushColor.red / 65535.0f + green:rgbBrushColor.green / 65535.0f + blue:rgbBrushColor.blue / 65535.0f + alpha:1.0f]; + } else { +#ifdef DEBUG + NSLog(@"Unable to create color for brushcolor %d", brush); +#endif + } + return nsBrushColor; +} + ++ (NSColor *)gtm_deviceBlackColor { /* 0.0f white */ + return [NSColor colorWithDeviceWhite:0.0f alpha:1.0f]; +} + ++ (NSColor *)gtm_deviceDarkGrayColor { /* 0.333 white */ + return [NSColor colorWithDeviceWhite:1.0f/3.0f alpha:1.0f]; +} + ++ (NSColor *)gtm_deviceLightGrayColor { /* 0.667 white */ + return [NSColor colorWithDeviceWhite:2.0f/3.0f alpha:1.0f]; +} + ++ (NSColor *)gtm_deviceWhiteColor { /* 1.0f white */ + return [NSColor colorWithDeviceWhite:1.0f alpha:1.0f]; +} + ++ (NSColor *)gtm_deviceGrayColor { /* 0.5 white */ + return [NSColor colorWithDeviceWhite:0.5f alpha:1.0f]; +} + ++ (NSColor *)gtm_deviceRedColor { /* 1.0f, 0.0f, 0.0f RGB */ + return [NSColor colorWithDeviceRed:1.0f green:0.0f blue: 0.0f alpha:1.0f]; +} + ++ (NSColor *)gtm_deviceGreenColor { /* 0.0f, 1.0f, 0.0f RGB */ +return [NSColor colorWithDeviceRed:0.0f green:1.0f blue: 0.0f alpha:1.0f]; +} + ++ (NSColor *)gtm_deviceBlueColor { /* 0.0f, 0.0f, 1.0f RGB */ +return [NSColor colorWithDeviceRed:0.0f green:0.0f blue: 1.0f alpha:1.0f]; +} + ++ (NSColor *)gtm_deviceCyanColor { /* 0.0f, 1.0f, 1.0f RGB */ +return [NSColor colorWithDeviceRed:0.0f green:1.0f blue: 1.0f alpha:1.0f]; +} + ++ (NSColor *)gtm_deviceYellowColor { /* 1.0f, 1.0f, 0.0f RGB */ +return [NSColor colorWithDeviceRed:1.0f green:1.0f blue: 0.0f alpha:1.0f]; +} + ++ (NSColor *)gtm_deviceMagentaColor { /* 1.0f, 0.0f, 1.0f RGB */ +return [NSColor colorWithDeviceRed:1.0f green:0.0f blue: 1.0f alpha:1.0f]; +} + ++ (NSColor *)gtm_deviceOrangeColor { /* 1.0f, 0.5, 0.0f RGB */ +return [NSColor colorWithDeviceRed:1.0f green:0.5f blue: 0.0f alpha:1.0f]; +} + ++ (NSColor *)gtm_devicePurpleColor { /* 0.5, 0.0f, 0.5 RGB */ +return [NSColor colorWithDeviceRed:0.5f green:0.0f blue: 0.5f alpha:1.0f]; +} + ++ (NSColor *)gtm_deviceBrownColor { /* 0.6, 0.4, 0.2 RGB */ +return [NSColor colorWithDeviceRed:0.6f green:0.4f blue: 0.2f alpha:1.0f]; +} + ++ (NSColor *)gtm_deviceClearColor { /* 0.0f white, 0.0f alpha */ + return [NSColor colorWithDeviceWhite:0.0f alpha:0.0f]; +} + +- (NSColor *)gtm_safeColorWithAlphaComponent:(float)alpha { + // This mess is here because of + // Radar 5047862 [NSColor colorWithAlphaComponent] does NOT maintain colorspace. + // As of 10.4.8, colorWithAlphaComponent will return an NSCalibratedRGBColor + // instead of a NSDeviceRGBColor when you call colorWithAlphaComponent on + // a NSDeviceRGBColor even though the docs say "Creates and returns an NSColor + // object that has the same color space and component values as the receiver" + // We must use exceptions in case somebody attempts to put a pattern through + // here. The assumption being made is that alpha is the last color component + // which it is for all current cases. + NSColor *newColor = nil; + +#if (MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4) + @try { + int componentCount = [self numberOfComponents]; + float *components = (float*)calloc(componentCount, sizeof(float)); + if (!components) { + NSException *exception = [NSException exceptionWithName:NSMallocException + reason:@"Unable to malloc components" + userInfo:nil]; + @throw exception; + } + [self getComponents:components]; + components[componentCount - 1] = alpha; + newColor = [NSColor colorWithColorSpace:[self colorSpace] + components:components count:componentCount]; + free(components); + } + @catch (NSException *ex) { + // Probably passed us a pattern. I'm not even sure how Apple deals with + // changing the alpha of a pattern color. + newColor = [self colorWithAlphaComponent:alpha]; + } +#else + // Radar 5047862 is fixed in Leopard. + newColor = [self colorWithAlphaComponent:alpha]; +#endif + return newColor; +} + +- (NSColor *)gtm_safeColorUsingColorSpaceName:(NSString *)colorSpaceName { + NSColor *outColor = [self colorUsingColorSpaceName:colorSpaceName]; + if (!outColor) { + NSBitmapImageRep *rep = [[[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL + pixelsWide:1 + pixelsHigh:1 + bitsPerSample:8 + samplesPerPixel:4 + hasAlpha:YES + isPlanar:NO + colorSpaceName:colorSpaceName + bytesPerRow:0 + bitsPerPixel:0] autorelease]; + NSGraphicsContext *context = [NSGraphicsContext graphicsContextWithBitmapImageRep:rep]; + if (context) { + NSRect rect = NSMakeRect(0, 0, 1, 1); + [NSGraphicsContext saveGraphicsState]; + [NSGraphicsContext setCurrentContext:context]; + [self setFill]; + NSRectFill(rect); + [NSGraphicsContext restoreGraphicsState]; + outColor = [rep colorAtX:0 y:0]; + } + } + return outColor; +} +@end diff --git a/AppKit/GTMNSColor+ThemeTest.m b/AppKit/GTMNSColor+ThemeTest.m new file mode 100644 index 0000000..d3c9db1 --- /dev/null +++ b/AppKit/GTMNSColor+ThemeTest.m @@ -0,0 +1,249 @@ +// +// NSColor+ThemeTest.m +// +// Copyright 2006-2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +// + +#import <SenTestingKit/SenTestingKit.h> +#import "GTMNSColor+Theme.h" +#import "GTMNSWorkspace+Theme.h" +#import "GTMSystemVersion.h" + + +@interface GTMNSColor_ThemeTest : SenTestCase +@end + +@implementation GTMNSColor_ThemeTest + +//METHOD_CHECK(NSWorkspace, themeAppearance); + +// utility function for giving random floats between 0.0f and 1.0f +static float Randomf() { + float val = random(); + return val / INT32_MAX; +} + +- (void)testColorWithThemeTextColor { + float colorValues[][4] = { + { 1.000000, 1.000000, 1.000000, 1.000000 }, + { 0.000000, 0.000000, 0.000000, 1.000000 }, + { 0.000000, 0.000000, 0.000000, 0.000000 }, + { 0.000000, 0.000000, 0.000000, 1.000000 }, + { 0.499992, 0.499992, 0.499992, 1.000000 }, + { 0.000000, 0.000000, 0.000000, 1.000000 }, + { 0.499992, 0.499992, 0.499992, 1.000000 }, + { 0.000000, 0.000000, 0.000000, 1.000000 }, + { 0.499992, 0.499992, 0.499992, 1.000000 }, + { 0.000000, 0.000000, 0.000000, 1.000000 }, + { 0.499992, 0.499992, 0.499992, 1.000000 }, + { 0.000000, 0.000000, 0.000000, 1.000000 }, + { 0.499992, 0.499992, 0.499992, 1.000000 }, + { 0.000000, 0.000000, 0.000000, 1.000000 }, + { 0.000000, 0.000000, 0.000000, 1.000000 }, + { 0.499992, 0.499992, 0.499992, 1.000000 }, + { 0.000000, 0.000000, 0.000000, 1.000000 }, + { 0.000000, 0.000000, 0.000000, 1.000000 }, + { 0.499992, 0.499992, 0.499992, 1.000000 }, + { 0.000000, 0.000000, 0.000000, 1.000000 }, + { 0.000000, 0.000000, 0.000000, 1.000000 }, + { 0.499992, 0.499992, 0.499992, 1.000000 }, + { 0.000000, 0.000000, 0.000000, 1.000000 }, + { 0.000000, 0.000000, 0.000000, 1.000000 }, + { 0.000000, 0.000000, 0.000000, 1.000000 }, + { 0.000000, 0.000000, 0.000000, 1.000000 }, + { 0.449989, 0.449989, 0.449989, 1.000000 }, + { 0.000000, 0.000000, 0.000000, 1.000000 }, + { 0.449989, 0.449989, 0.449989, 1.000000 }, + { 0.000000, 0.000000, 0.000000, 1.000000 }, + { 0.499992, 0.499992, 0.499992, 1.000000 }, + { 0.000000, 0.000000, 0.000000, 1.000000 }, + { 0.499992, 0.499992, 0.499992, 1.000000 }, + { 0.000000, 0.000000, 0.000000, 1.000000 }, + { 1.000000, 1.000000, 1.000000, 1.000000 }, + { 0.599985, 0.599985, 0.599985, 1.000000 }, + { 0.000000, 0.000000, 0.000000, 1.000000 }, + { 1.000000, 1.000000, 1.000000, 1.000000 }, + { 0.599985, 0.599985, 0.599985, 1.000000 }, + { 0.000000, 0.000000, 0.000000, 1.000000 }, + { 0.499992, 0.499992, 0.499992, 1.000000 }, + { 0.000000, 0.000000, 0.000000, 1.000000 }, + { 0.000000, 0.000000, 0.000000, 1.000000 }, + { 0.000000, 0.000000, 0.000000, 1.000000 }, + { 0.499992, 0.499992, 0.499992, 1.000000 }, + { 0.499992, 0.499992, 0.499992, 1.000000 }, + { 0.000000, 0.000000, 0.000000, 1.000000 }, + { 0.000000, 0.000000, 0.000000, 1.000000 }, + { 0.499992, 0.499992, 0.499992, 1.000000 }, + { 0.000000, 0.000000, 0.000000, 1.000000 } + }; + + if ([GTMSystemVersion isLeopardOrGreater]) { + // kThemeTextColorRootMenuDisabled changed to white in Leopard. + colorValues[35][0] = 1.0; + colorValues[35][1] = 1.0; + colorValues[35][2] = 1.0; + } + for(int i = kThemeTextColorWhite; i < kThemeTextColorSystemDetail; i++) { + if (i == 0) continue; + NSColor *textColor = [NSColor gtm_colorWithThemeTextColor:i]; + float nsComponents[5]; + [textColor getComponents: nsComponents]; + for(int j = 0; j < 4; j++) { + STAssertEqualsWithAccuracy(nsComponents[j], colorValues[i + 2][j], 0.000001, + @"Theme Text Color %d is wrong", i); + STAssertEqualObjects([textColor colorSpaceName], NSDeviceRGBColorSpace, + @"Color space must be DeviceRGB"); + } + } +} + +- (void)testColorWithThemeBrushColor { + float colorValues[][4] = { + { 1.000000, 1.000000, 1.000000, 1.000000 }, + { 0.000000, 0.000000, 0.000000, 1.000000 }, + { 0.000000, 0.000000, 0.000000, 0.000000 }, + { 1.000000, 1.000000, 1.000000, 1.000000 }, + { 1.000000, 1.000000, 1.000000, 1.000000 }, + { 1.000000, 1.000000, 1.000000, 1.000000 }, + { 1.000000, 1.000000, 1.000000, 1.000000 }, + { 1.000000, 1.000000, 1.000000, 1.000000 }, + { 1.000000, 1.000000, 1.000000, 1.000000 }, + { 1.000000, 1.000000, 1.000000, 1.000000 }, + { 1.000000, 1.000000, 1.000000, 1.000000 }, + { 1.000000, 1.000000, 1.000000, 1.000000 }, + { 1.000000, 1.000000, 1.000000, 1.000000 }, + { 0.980011, 0.990005, 0.990005, 1.000000 }, + { 1.000000, 1.000000, 1.000000, 1.000000 }, + { 0.000000, 0.000000, 0.000000, 1.000000 }, + { 0.709789, 0.835294, 1.000000, 1.000000 }, + { 1.000000, 1.000000, 1.000000, 1.000000 }, + { 1.000000, 1.000000, 1.000000, 1.000000 }, + { 1.000000, 1.000000, 1.000000, 1.000000 }, + { 1.000000, 1.000000, 1.000000, 1.000000 }, + { 0.492195, 0.675792, 0.847669, 1.000000 }, + { 0.000000, 0.000000, 0.000000, 1.000000 }, + { 0.000000, 0.000000, 0.000000, 1.000000 }, + { 0.500008, 0.500008, 0.500008, 1.000000 }, + { 1.000000, 1.000000, 1.000000, 1.000000 }, + { 0.600000, 0.600000, 0.600000, 1.000000 }, + { 1.000000, 1.000000, 1.000000, 1.000000 }, + { 1.000000, 1.000000, 1.000000, 1.000000 }, + { 0.510002, 0.510002, 0.510002, 1.000000 }, + { 0.760006, 0.760006, 0.760006, 1.000000 }, + { 0.940002, 0.940002, 0.940002, 1.000000 }, + { 0.980011, 0.980011, 0.980011, 1.000000 }, + { 0.670008, 0.670008, 0.670008, 1.000000 }, + { 0.860014, 0.860014, 0.860014, 1.000000 }, + { 0.880003, 0.880003, 0.880003, 1.000000 }, + { 0.880003, 0.880003, 0.880003, 1.000000 }, + { 0.990005, 0.990005, 0.990005, 1.000000 }, + { 0.900008, 0.900008, 0.900008, 1.000000 }, + { 0.930007, 0.930007, 0.930007, 1.000000 }, + { 0.930007, 0.930007, 0.930007, 1.000000 }, + { 0.990005, 0.990005, 0.990005, 1.000000 }, + { 0.540002, 0.540002, 0.540002, 1.000000 }, + { 0.590005, 0.590005, 0.590005, 1.000000 }, + { 0.590005, 0.590005, 0.590005, 1.000000 }, + { 0.730007, 0.730007, 0.730007, 1.000000 }, + { 0.640009, 0.640009, 0.640009, 1.000000 }, + { 0.640009, 0.640009, 0.640009, 1.000000 }, + { 0.820005, 0.820005, 0.820005, 1.000000 }, + { 0.820005, 0.820005, 0.820005, 1.000000 }, + { 1.000000, 1.000000, 1.000000, 1.000000 }, + { 1.000000, 1.000000, 1.000000, 1.000000 }, + { 1.000000, 1.000000, 1.000000, 1.000000 }, + { 1.000000, 1.000000, 1.000000, 1.000000 }, + { 1.000000, 1.000000, 1.000000, 1.000000 }, + { 1.000000, 1.000000, 1.000000, 1.000000 }, + { 1.000000, 1.000000, 1.000000, 1.000000 }, + { 1.000000, 1.000000, 1.000000, 1.000000 }, + { 1.000000, 1.000000, 1.000000, 1.000000 }, + { 0.925490, 0.952895, 0.996094, 1.000000 } + }; + + NSString *theme = [[NSWorkspace sharedWorkspace] gtm_themeAppearance]; + if ([theme isEqualToString:(NSString*)kThemeAppearanceAquaGraphite]) { + // These are the only two brushes that change with an appearance change + colorValues[21][0] = 0.605478; + colorValues[21][1] = 0.667979; + colorValues[21][2] = 0.738293; + colorValues[59][0] = 0.941192; + colorValues[59][1] = 0.941192; + colorValues[59][2] = 0.941192; + } + for(int i = kThemeBrushWhite; i < kThemeBrushListViewColumnDivider; i++) { + // Brush "14" is the selection, so it will change depending on the system + // There is no brush 0. + if (i == 0 || i == 14) continue; + NSColor *brushColor = [NSColor gtm_colorWithThemeBrush:i]; + float nsComponents[5]; + [brushColor getComponents: nsComponents]; + for(int j = 0; j < 4; j++) { + STAssertEqualsWithAccuracy(nsComponents[j], colorValues[i + 2][j], 0.000001, + @"Theme Text Brush %d is wrong", i + 2); + STAssertEqualObjects([brushColor colorSpaceName], NSDeviceRGBColorSpace, + @"Color space must be DeviceRGB"); + } + } +} + +- (void)testSafeColorWithAlphaComponent { + NSColorSpace *testSpace[6]; + testSpace[0] = [NSColorSpace genericRGBColorSpace]; + testSpace[1] = [NSColorSpace genericGrayColorSpace]; + testSpace[2] = [NSColorSpace genericCMYKColorSpace]; + testSpace[3] = [NSColorSpace deviceRGBColorSpace]; + testSpace[4] = [NSColorSpace deviceGrayColorSpace]; + testSpace[5] = [NSColorSpace deviceCMYKColorSpace]; + + float comp[5]; + for (int i = 0; i < sizeof(comp) / sizeof(float); ++i) { + comp[i] = Randomf(); + } + + float alpha = Randomf(); + for (int i = 0; i < sizeof(testSpace) / sizeof(NSColorSpace*); ++i) { + int componentCount = [testSpace[i] numberOfColorComponents]; + NSColor *color = [NSColor colorWithColorSpace:testSpace[i] + components:comp + count:componentCount + 1]; + NSColor *alphaColor = [color gtm_safeColorWithAlphaComponent:alpha]; + + float alphaSwap = comp[componentCount]; + comp[componentCount] = alpha; + NSColor *testColor = [NSColor colorWithColorSpace:testSpace[i] + components:comp + count:componentCount + 1]; + comp[componentCount] = alphaSwap; + STAssertEqualObjects(alphaColor, + testColor, + @"Compare failed with components: %f %f %f %f %f %f alpha: %f", + testSpace[0], testSpace[1], testSpace[2], + testSpace[3], testSpace[4], testSpace[5], + alpha); + } +} + +- (void)testSafeColorUsingColorSpaceName { + + NSColor *brushColor = [[NSColor selectedMenuItemColor] colorUsingColorSpaceName:NSDeviceRGBColorSpace]; + STAssertNil(brushColor, @"This doesn't work on Tiger or Leopard"); + + brushColor = [[NSColor selectedMenuItemColor] gtm_safeColorUsingColorSpaceName:NSDeviceRGBColorSpace]; + STAssertNotNil(brushColor, nil); +} + + +@end diff --git a/AppKit/GTMNSWorkspace+Theme.h b/AppKit/GTMNSWorkspace+Theme.h new file mode 100644 index 0000000..364d623 --- /dev/null +++ b/AppKit/GTMNSWorkspace+Theme.h @@ -0,0 +1,39 @@ +// +// GTMNSWorkspace+ScreenSaver.h +// +// Copyright 2007-2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +// + +#import <Cocoa/Cocoa.h> +#import <Carbon/Carbon.h> + +enum { + // This means that we had a failure getting the style + kThemeScrollBarArrowsInvalid = -1, + // This is the "missing" scroll bar arrow style that some people use + kThemeScrollBarArrowsDouble = 2 +}; + +/// Category for interacting with the screen saver +@interface NSWorkspace (GTMWorkspaceThemeAddition) + +// returns one of the kThemeAppearance... constants as an autoreleased NSString +// tells you whether we are running under blue or graphite +- (NSString*)gtm_themeAppearance; + +// Returns how the user has their scroll bars configured. +- (ThemeScrollBarArrowStyle)gtm_themeScrollBarArrowStyle; +@end + diff --git a/AppKit/GTMNSWorkspace+Theme.m b/AppKit/GTMNSWorkspace+Theme.m new file mode 100644 index 0000000..86b9935 --- /dev/null +++ b/AppKit/GTMNSWorkspace+Theme.m @@ -0,0 +1,42 @@ +// +// GTMNSWorkspace+Theme.m +// +// Copyright 2007-2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +// + +#import "GTMNSWorkspace+Theme.h" +#import "GTMGarbageCollection.h" + +@implementation NSWorkspace (GTMWorkspaceThemeAddition) + +- (NSString*)gtm_themeAppearance { + CFStringRef cfTheme; + NSString *nsTheme = nil; + OSStatus theStatus = CopyThemeIdentifier(&cfTheme); + if (theStatus == noErr) { + nsTheme = [GTMNSMakeCollectable(cfTheme) autorelease]; + } + return nsTheme; +} + +- (ThemeScrollBarArrowStyle)gtm_themeScrollBarArrowStyle { + ThemeScrollBarArrowStyle style = kThemeScrollBarArrowsInvalid; + OSStatus theStatus = GetThemeScrollBarArrowStyle(&style); + if (theStatus != noErr) { + style = kThemeScrollBarArrowsInvalid; + } + return style; +} +@end diff --git a/AppKit/GTMNSWorkspace+ThemeTest.m b/AppKit/GTMNSWorkspace+ThemeTest.m new file mode 100644 index 0000000..213de22 --- /dev/null +++ b/AppKit/GTMNSWorkspace+ThemeTest.m @@ -0,0 +1,37 @@ +// +// NSWorkspace+ThemeTest.m +// +// Copyright 2007-2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +// + +#import <SenTestingKit/SenTestingKit.h> +#import "GTMNSWorkspace+Theme.h" + +@interface GTMNSWorkspace_ThemeTest : SenTestCase +@end + +@implementation GTMNSWorkspace_ThemeTest + +- (void)testThemeAppearance { + NSString *theme = [[NSWorkspace sharedWorkspace] gtm_themeAppearance]; + STAssertNotNil(theme, nil); + STAssertTrue([theme hasPrefix:(NSString*)kThemeAppearanceAqua], nil); +} + +- (void)testThemeScrollBarArrowStyle { + ThemeScrollBarArrowStyle style = [[NSWorkspace sharedWorkspace] gtm_themeScrollBarArrowStyle]; + STAssertTrue(style <= kThemeScrollBarArrowsDouble, nil); +} +@end diff --git a/AppKit/GTMShading.h b/AppKit/GTMShading.h new file mode 100644 index 0000000..27e163e --- /dev/null +++ b/AppKit/GTMShading.h @@ -0,0 +1,41 @@ +// +// GTMShading.h +// +// A protocol for an object that can be used as a shader. +// +// Copyright 2006-2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +// + +/// \cond Protocols + +@protocol GTMShading +// Returns the shadefunction for using in a shader. +// This shadefunction shoud never be released. It is owned by the implementor +// of the GTMShading protocol. +// +// Returns: +// a shading function. +- (CGFunctionRef)shadeFunction; + +// Returns the colorSpace for using in a shader. +// This colorSpace shoud never be released. It is owned by the implementor +// of the GTMShading protocol. +// +// Returns: +// a color space. +- (CGColorSpaceRef)colorSpace; +@end + +/// \endcond |