From 2a5219567634ab7ab74314ff3615132becadff4a Mon Sep 17 00:00:00 2001 From: thomasvl Date: Mon, 28 Jan 2008 20:19:42 +0000 Subject: initial drop of a few sources to start things out --- AppKit/GTMDelegatingTableColumn.h | 32 + AppKit/GTMDelegatingTableColumn.m | 39 ++ AppKit/GTMGeometryUtils.h | 560 +++++++++++++++ AppKit/GTMGeometryUtils.m | 152 +++++ AppKit/GTMGeometryUtilsTest.m | 323 +++++++++ AppKit/GTMLinearRGBShading.h | 76 +++ AppKit/GTMLinearRGBShading.m | 193 ++++++ AppKit/GTMLinearRGBShadingTest.m | 94 +++ AppKit/GTMLoginItems.h | 87 +++ AppKit/GTMLoginItems.m | 183 +++++ AppKit/GTMLoginItemsTest.m | 112 +++ AppKit/GTMNSBezierPath+CGPath.h | 33 + AppKit/GTMNSBezierPath+CGPath.m | 68 ++ AppKit/GTMNSBezierPath+CGPathTest.m | 78 +++ AppKit/GTMNSBezierPath+CGPathTest.tif | Bin 0 -> 1090 bytes AppKit/GTMNSBezierPath+RoundRect.h | 45 ++ AppKit/GTMNSBezierPath+RoundRect.m | 58 ++ AppKit/GTMNSBezierPath+RoundRectTest.m | 93 +++ AppKit/GTMNSBezierPath+RoundRectTest.tif | Bin 0 -> 6982 bytes AppKit/GTMNSBezierPath+Shading.h | 120 ++++ AppKit/GTMNSBezierPath+Shading.m | 214 ++++++ AppKit/GTMNSBezierPath+ShadingTest.10.4.tif | Bin 0 -> 12964 bytes AppKit/GTMNSBezierPath+ShadingTest.10.5.tif | Bin 0 -> 17982 bytes AppKit/GTMNSBezierPath+ShadingTest.m | 95 +++ AppKit/GTMNSColor+Theme.h | 62 ++ AppKit/GTMNSColor+Theme.m | 192 ++++++ AppKit/GTMNSColor+ThemeTest.m | 249 +++++++ AppKit/GTMNSWorkspace+Theme.h | 39 ++ AppKit/GTMNSWorkspace+Theme.m | 42 ++ AppKit/GTMNSWorkspace+ThemeTest.m | 37 + AppKit/GTMShading.h | 41 ++ COPYING | 202 ++++++ Foundation/GTMCalculatedRange.h | 101 +++ Foundation/GTMCalculatedRange.m | 146 ++++ Foundation/GTMCalculatedRangeTest.m | 88 +++ Foundation/GTMGarbageCollection.h | 43 ++ Foundation/GTMNSData+zlib.h | 83 +++ Foundation/GTMNSData+zlib.m | 246 +++++++ Foundation/GTMNSData+zlibTest.m | 183 +++++ Foundation/GTMNSEnumerator+Filter.h | 53 ++ Foundation/GTMNSEnumerator+Filter.m | 167 +++++ Foundation/GTMNSEnumerator+FilterTest.m | 68 ++ Foundation/GTMNSString+HTML.h | 66 ++ Foundation/GTMNSString+HTML.m | 514 ++++++++++++++ Foundation/GTMNSString+HTMLTest.m | 240 +++++++ Foundation/GTMNSString+XML.h | 39 ++ Foundation/GTMNSString+XML.m | 162 +++++ Foundation/GTMNSString+XMLTest.m | 44 ++ Foundation/GTMObjectSingleton.h | 69 ++ Foundation/GTMSystemVersion.h | 45 ++ Foundation/GTMSystemVersion.m | 104 +++ Foundation/GTMSystemVersionTest.m | 47 ++ GTM-Info.plist | 20 + GTM.xcodeproj/project.pbxproj | 787 ++++++++++++++++++++++ GTM_Prefix.pch | 19 + ReleaseNotes.txt | 5 + UnitTest-Info.plist | 20 + UnitTesting/GTMNSObject+UnitTesting.h | 534 +++++++++++++++ UnitTesting/GTMNSObject+UnitTesting.m | 730 ++++++++++++++++++++ UnitTesting/GTMNSView+UnitTesting.h | 138 ++++ UnitTesting/GTMNSView+UnitTesting.m | 135 ++++ UnitTesting/GTMSenTestCase.h | 429 ++++++++++++ XcodeConfig/DebugTigerOrLater.xcconfig | 34 + XcodeConfig/DebugUnittest.xcconfig | 29 + XcodeConfig/LoadableBundle.xcconfig | 30 + XcodeConfig/ReleaseTigerOrLater.xcconfig | 33 + XcodeConfig/ReleaseUnittest.xcconfig | 37 + XcodeConfig/SharedLibrary.xcconfig | 29 + XcodeConfig/StaticLibrary.xcconfig | 30 + XcodeConfig/subconfig/CodeCoverage.xcconfig | 25 + XcodeConfig/subconfig/CodeCoverageStatic.xcconfig | 24 + XcodeConfig/subconfig/Debug.xcconfig | 40 ++ XcodeConfig/subconfig/General.xcconfig | 48 ++ XcodeConfig/subconfig/Release.xcconfig | 38 ++ XcodeConfig/subconfig/TigerOrLater.xcconfig | 29 + XcodeConfig/subconfig/Unittest.xcconfig | 37 + 76 files changed, 9307 insertions(+) create mode 100644 AppKit/GTMDelegatingTableColumn.h create mode 100644 AppKit/GTMDelegatingTableColumn.m create mode 100644 AppKit/GTMGeometryUtils.h create mode 100644 AppKit/GTMGeometryUtils.m create mode 100644 AppKit/GTMGeometryUtilsTest.m create mode 100644 AppKit/GTMLinearRGBShading.h create mode 100644 AppKit/GTMLinearRGBShading.m create mode 100644 AppKit/GTMLinearRGBShadingTest.m create mode 100644 AppKit/GTMLoginItems.h create mode 100644 AppKit/GTMLoginItems.m create mode 100644 AppKit/GTMLoginItemsTest.m create mode 100644 AppKit/GTMNSBezierPath+CGPath.h create mode 100644 AppKit/GTMNSBezierPath+CGPath.m create mode 100644 AppKit/GTMNSBezierPath+CGPathTest.m create mode 100644 AppKit/GTMNSBezierPath+CGPathTest.tif create mode 100644 AppKit/GTMNSBezierPath+RoundRect.h create mode 100644 AppKit/GTMNSBezierPath+RoundRect.m create mode 100644 AppKit/GTMNSBezierPath+RoundRectTest.m create mode 100644 AppKit/GTMNSBezierPath+RoundRectTest.tif create mode 100644 AppKit/GTMNSBezierPath+Shading.h create mode 100644 AppKit/GTMNSBezierPath+Shading.m create mode 100644 AppKit/GTMNSBezierPath+ShadingTest.10.4.tif create mode 100644 AppKit/GTMNSBezierPath+ShadingTest.10.5.tif create mode 100644 AppKit/GTMNSBezierPath+ShadingTest.m create mode 100644 AppKit/GTMNSColor+Theme.h create mode 100644 AppKit/GTMNSColor+Theme.m create mode 100644 AppKit/GTMNSColor+ThemeTest.m create mode 100644 AppKit/GTMNSWorkspace+Theme.h create mode 100644 AppKit/GTMNSWorkspace+Theme.m create mode 100644 AppKit/GTMNSWorkspace+ThemeTest.m create mode 100644 AppKit/GTMShading.h create mode 100644 COPYING create mode 100644 Foundation/GTMCalculatedRange.h create mode 100644 Foundation/GTMCalculatedRange.m create mode 100644 Foundation/GTMCalculatedRangeTest.m create mode 100644 Foundation/GTMGarbageCollection.h create mode 100644 Foundation/GTMNSData+zlib.h create mode 100644 Foundation/GTMNSData+zlib.m create mode 100644 Foundation/GTMNSData+zlibTest.m create mode 100644 Foundation/GTMNSEnumerator+Filter.h create mode 100644 Foundation/GTMNSEnumerator+Filter.m create mode 100644 Foundation/GTMNSEnumerator+FilterTest.m create mode 100644 Foundation/GTMNSString+HTML.h create mode 100644 Foundation/GTMNSString+HTML.m create mode 100644 Foundation/GTMNSString+HTMLTest.m create mode 100644 Foundation/GTMNSString+XML.h create mode 100644 Foundation/GTMNSString+XML.m create mode 100644 Foundation/GTMNSString+XMLTest.m create mode 100644 Foundation/GTMObjectSingleton.h create mode 100644 Foundation/GTMSystemVersion.h create mode 100644 Foundation/GTMSystemVersion.m create mode 100644 Foundation/GTMSystemVersionTest.m create mode 100644 GTM-Info.plist create mode 100644 GTM.xcodeproj/project.pbxproj create mode 100644 GTM_Prefix.pch create mode 100644 ReleaseNotes.txt create mode 100644 UnitTest-Info.plist create mode 100644 UnitTesting/GTMNSObject+UnitTesting.h create mode 100644 UnitTesting/GTMNSObject+UnitTesting.m create mode 100644 UnitTesting/GTMNSView+UnitTesting.h create mode 100644 UnitTesting/GTMNSView+UnitTesting.m create mode 100644 UnitTesting/GTMSenTestCase.h create mode 100644 XcodeConfig/DebugTigerOrLater.xcconfig create mode 100644 XcodeConfig/DebugUnittest.xcconfig create mode 100644 XcodeConfig/LoadableBundle.xcconfig create mode 100644 XcodeConfig/ReleaseTigerOrLater.xcconfig create mode 100644 XcodeConfig/ReleaseUnittest.xcconfig create mode 100644 XcodeConfig/SharedLibrary.xcconfig create mode 100644 XcodeConfig/StaticLibrary.xcconfig create mode 100644 XcodeConfig/subconfig/CodeCoverage.xcconfig create mode 100644 XcodeConfig/subconfig/CodeCoverageStatic.xcconfig create mode 100644 XcodeConfig/subconfig/Debug.xcconfig create mode 100644 XcodeConfig/subconfig/General.xcconfig create mode 100644 XcodeConfig/subconfig/Release.xcconfig create mode 100644 XcodeConfig/subconfig/TigerOrLater.xcconfig create mode 100644 XcodeConfig/subconfig/Unittest.xcconfig 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 + +@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 +#include + + +#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 +#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 +#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 { +@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 +#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 + +/// 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 + +// 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 +#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 + +/// 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 + +#import +#import "GTMNSBezierPath+CGPath.h" +#import "GTMNSView+UnitTesting.h" + +@interface GTMNSBezierPath_CGPathTest : SenTestCase +@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 new file mode 100644 index 0000000..a7fee9b Binary files /dev/null and b/AppKit/GTMNSBezierPath+CGPathTest.tif differ 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 + +/// 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 + +#import +#import "GTMNSBezierPath+RoundRect.h" +#import "GTMNSView+UnitTesting.h" + +@interface GTMNSBezierPath_RoundRectTest : SenTestCase +@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 new file mode 100644 index 0000000..7ce0d45 Binary files /dev/null and b/AppKit/GTMNSBezierPath+RoundRectTest.tif differ 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 + +@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)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)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)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)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)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)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)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)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)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)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 new file mode 100644 index 0000000..44a09b5 Binary files /dev/null and b/AppKit/GTMNSBezierPath+ShadingTest.10.4.tif differ diff --git a/AppKit/GTMNSBezierPath+ShadingTest.10.5.tif b/AppKit/GTMNSBezierPath+ShadingTest.10.5.tif new file mode 100644 index 0000000..6d4570a Binary files /dev/null and b/AppKit/GTMNSBezierPath+ShadingTest.10.5.tif differ 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 + +#import + +#import "GTMLinearRGBShading.h" +#import "GTMNSView+UnitTesting.h" +#import "GTMNSBezierPath+Shading.h" +#import "GTMNSColor+Theme.h" + +@interface GTMNSBezierPath_ShadingTest : SenTestCase +@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 +#import + +/// 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 +#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 +#import + +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 +#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 diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..a95e82a --- /dev/null +++ b/COPYING @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2007 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. \ No newline at end of file diff --git a/Foundation/GTMCalculatedRange.h b/Foundation/GTMCalculatedRange.h new file mode 100644 index 0000000..5823ba6 --- /dev/null +++ b/Foundation/GTMCalculatedRange.h @@ -0,0 +1,101 @@ +// +// GTMCalculatedRange.h +// +// This is a collection that allows you to calculate a value based on +// defined stops in a range. +// +// 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 + +/// Allows you to calculate a value based on defined stops in a range. +// +/// For example if you have a range from 0.0 to 1.0 where the stop +/// located at 0.0 is red and the stop located at 1.0 is blue, +/// the value based on the position 0.5 would come out as purple assuming +/// that the valueAtPosition function calculates a purely linear mapping between +/// the stops at 0.0 and 1.0. Stops have indices and are sorted from lowest to +/// highest. The example above would have 2 stops. Stop 0 would be red and stop +/// 1 would be blue. +/// +/// Subclasses of GTMCalculatedRange are expected to override the valueAtPosition: +/// method to return a value based on the position passed in, and the stops +/// that are currently set in the range. Stops do not necessarily have to +/// be the same type as the values that are calculated, but normally they are. +@interface GTMCalculatedRange : NSObject { + NSMutableArray *storage_; +} + +// Adds a stop to the range at |position|. If there is already a stop +// at position |position| it is replaced. +// +// Args: +// item: the object to place at |position|. +// position: the position in the range to put |item|. +// +- (void)insertStop:(id)item atPosition:(float)position; + +// Removes a stop from the range at |position|. +// +// Args: +// position: the position in the range to remove |item|. +// +// Returns: +// YES if there is a stop at |position| that has been removed +// NO if there is not a stop at the |position| +- (BOOL)removeStopAtPosition:(float)position; + +// Removes stop |index| from the range. Stops are ordered +// based on position where index of x < index of y if position +// of x < position of y. +// +// Args: +// item: the object to place at |position|. +// position: the position in the range to put |item|. +// +- (void)removeStopAtIndex:(unsigned int)index; + +// Returns the number of stops in the range. +// +// Returns: +// number of stops +- (unsigned int)stopCount; + +// Returns the value at position |position|. +// This function should be overridden by subclasses to calculate a +// value for any given range. +// The default implementation returns a value if there happens to be +// a stop for the given position. Otherwise it returns nil. +// +// Args: +// position: the position to calculate a value for. +// +// Returns: +// value for position +- (id)valueAtPosition:(float)position; + +// Returns the |index|'th stop and position in the set. +// Throws an exception if out of range. +// +// Args: +// index: the index of the stop +// outPosition: a pointer to a value to be filled in with a position. +// this can be NULL, in which case no position is returned. +// +// Returns: +// the stop at the index. +- (id)stopAtIndex:(unsigned int)index position:(float*)outPosition; +@end diff --git a/Foundation/GTMCalculatedRange.m b/Foundation/GTMCalculatedRange.m new file mode 100644 index 0000000..8562d8a --- /dev/null +++ b/Foundation/GTMCalculatedRange.m @@ -0,0 +1,146 @@ +// +// GTMCalculatedRange.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 "GTMCalculatedRange.h" + +// Our internal storage type. It keeps track of an item and it's +// position. +@interface GTMCalculatedRangeStopPrivate : NSObject { + id item_; // the item (STRONG) + float position_; // +} ++ (id)stopWithObject:(id)item position:(float)inPosition; +- (id)initWithObject:(id)item position:(float)inPosition; +- (id)item; +- (float)position; +@end + + +@implementation GTMCalculatedRangeStopPrivate ++ (id)stopWithObject:(id)item position:(float)inPosition { + return [[[[self class] alloc] initWithObject:item position:inPosition] autorelease]; +} + +- (id)initWithObject:(id)item position:(float)inPosition { + self = [super init]; + if (self != nil) { + item_ = [item retain]; + position_ = inPosition; + } + return self; +} + +- (void)dealloc { + [item_ release]; + [super dealloc]; +} + +- (id)item { + return item_; +} + +- (float)position { + return position_; +} + +- (NSString *)description { + return [NSString stringWithFormat: @"%f %@", position_, item_]; +} +@end + +@implementation GTMCalculatedRange +- (id)init { + self = [super init]; + if (self != nil) { + storage_ = [[NSMutableArray arrayWithCapacity:0] retain]; + } + return self; +} +- (void)dealloc { + [storage_ release]; + [super dealloc]; +} + +- (void)insertStop:(id)item atPosition:(float)position { + unsigned int index = 0; + NSEnumerator *theEnumerator = [storage_ objectEnumerator]; + GTMCalculatedRangeStopPrivate *theStop; + while (nil != (theStop = [theEnumerator nextObject])) { + if ([theStop position] < position) { + index += 1; + } + else if ([theStop position] == position) { + [storage_ removeObjectAtIndex:index]; + } + } + [storage_ insertObject:[GTMCalculatedRangeStopPrivate stopWithObject:item position:position] + atIndex:index]; +} + +- (BOOL)removeStopAtPosition:(float)position { + unsigned int index = 0; + BOOL foundStop = NO; + NSEnumerator *theEnumerator = [storage_ objectEnumerator]; + GTMCalculatedRangeStopPrivate *theStop; + while (nil != (theStop = [theEnumerator nextObject])) { + if ([theStop position] == position) { + break; + } else { + index += 1; + } + } + if (nil != theStop) { + [self removeStopAtIndex:index]; + foundStop = YES; + } + return foundStop; +} + +- (void)removeStopAtIndex:(unsigned int)index { + [storage_ removeObjectAtIndex:index]; +} + +- (unsigned int)stopCount { + return [storage_ count]; +} + +- (id)stopAtIndex:(unsigned int)index position:(float*)outPosition { + GTMCalculatedRangeStopPrivate *theStop = [storage_ objectAtIndex:index]; + if (nil != outPosition) { + *outPosition = [theStop position]; + } + return [theStop item]; +} + +- (id)valueAtPosition:(float)position { + id theValue = nil; + GTMCalculatedRangeStopPrivate *theStop; + NSEnumerator *theEnumerator = [storage_ objectEnumerator]; + while (nil != (theStop = [theEnumerator nextObject])) { + if ([theStop position] == position) { + theValue = [theStop item]; + break; + } + } + return theValue; +} + +- (NSString *)description { + return [storage_ description]; +} +@end diff --git a/Foundation/GTMCalculatedRangeTest.m b/Foundation/GTMCalculatedRangeTest.m new file mode 100644 index 0000000..cd336f0 --- /dev/null +++ b/Foundation/GTMCalculatedRangeTest.m @@ -0,0 +1,88 @@ +// +// GTMCalculatedRangeTest.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 +#import "GTMCalculatedRange.h" +#import "GTMSenTestCase.h" + +@interface GTMCalculatedRangeTest : SenTestCase { + GTMCalculatedRange *range_; +} +@end + +@implementation GTMCalculatedRangeTest +NSString *kStrings[] = { @"Fee", @"Fi", @"Fo", @"Fum" }; +const unsigned int kStringCount = sizeof(kStrings) / sizeof(NSString*); +const float kOddPosition = 0.14159265f; +const float kExistingPosition = 0.5f; +const unsigned int kExisitingIndex = 2; + +- (void)setUp { + range_ = [[GTMCalculatedRange alloc] init]; + for(unsigned int i = kStringCount; i > 0; --i) { + [range_ insertStop:kStrings[kStringCount - i] atPosition: 1.0f / i]; + } +} + +- (void)tearDown { + [range_ release]; +} + +- (void)testInsertStop { + NSString *theString = @"I smell the blood of an Englishman!"; + [range_ insertStop:theString atPosition: kOddPosition]; + STAssertEquals([range_ stopCount], kStringCount + 1, @"Stop count was bad"); + NSString *getString = [range_ valueAtPosition:kOddPosition]; + STAssertNotNil(getString, @"String was bad"); + STAssertEquals(theString, getString, @"Stops weren't equal"); +} + +- (void)testRemoveStopAtPosition { + STAssertFalse([range_ removeStopAtPosition: kOddPosition], @"Was able to remove non-existant stop"); + STAssertTrue([range_ removeStopAtPosition: kExistingPosition], @"Was unable to remove good stop"); + STAssertEquals([range_ stopCount], kStringCount - 1, @"Removing stop should adjust stop count"); +} + +- (void)testRemoveStopAtIndex { + STAssertThrows([range_ removeStopAtIndex: kStringCount], @"Was able to remove non-existant stop"); + STAssertNoThrow([range_ removeStopAtIndex: kStringCount - 1], @"Was unable to remove good stop"); + STAssertEquals([range_ stopCount], kStringCount - 1, @"Removing stop should adjust stop count"); +} + +- (void)testStopCount { + STAssertEquals([range_ stopCount], kStringCount, @"Bad stop count"); +} + +- (void)testValueAtPosition { + STAssertEqualObjects([range_ valueAtPosition: kExistingPosition], kStrings[kExisitingIndex], nil); + STAssertNotEqualObjects([range_ valueAtPosition: kExistingPosition], kStrings[kStringCount - 1], nil); + STAssertNil([range_ valueAtPosition: kOddPosition], nil); +} + +- (void)testStopAtIndex { + float thePosition; + + STAssertEqualObjects([range_ stopAtIndex:kStringCount - 1 position:nil], kStrings[kStringCount - 1], nil); + STAssertEqualObjects([range_ stopAtIndex:kExisitingIndex position:&thePosition], kStrings[kExisitingIndex], nil); + STAssertEquals(thePosition, kExistingPosition, nil); + STAssertNotEqualObjects([range_ stopAtIndex:kStringCount - 1 position:nil], kStrings[2], nil); + STAssertThrows([range_ stopAtIndex:kStringCount position:nil], nil); +} + + +@end diff --git a/Foundation/GTMGarbageCollection.h b/Foundation/GTMGarbageCollection.h new file mode 100644 index 0000000..4a7ac56 --- /dev/null +++ b/Foundation/GTMGarbageCollection.h @@ -0,0 +1,43 @@ +// +// GTMGarbageCollection.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 + +// This allows us to easily move our code from GC to non GC. +// They are no-ops unless we are require Leopard or above. +// See +// http://developer.apple.com/documentation/Cocoa/Conceptual/GarbageCollection/index.html +// for details. +// General use would be +// CFTypeRef type = ... +// return [GTMNSMakeCollectable(type) autorelease]; + +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1050 + +FOUNDATION_STATIC_INLINE id GTMNSMakeCollectable(CFTypeRef cf) { + return NSMakeCollectable(cf); +} + +#else + +FOUNDATION_STATIC_INLINE id GTMNSMakeCollectable(CFTypeRef cf) { + // NSMakeCollectable handles NULLs just fine and returns nil as expected. + return (id)cf; +} + +#endif diff --git a/Foundation/GTMNSData+zlib.h b/Foundation/GTMNSData+zlib.h new file mode 100644 index 0000000..2c937c9 --- /dev/null +++ b/Foundation/GTMNSData+zlib.h @@ -0,0 +1,83 @@ +// +// GTMNSData+zlib.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 + +/// Helpers for dealing w/ zlib inflate/deflate calls. +@interface NSData (GTMZLibAdditions) + +/// Return an autoreleased NSData w/ the result of gzipping the bytes. +// +// Uses the default compression level. ++ (NSData *)gtm_dataByGzippingBytes:(const void *)bytes + length:(unsigned)length; + +/// Return an autoreleased NSData w/ the result of gzipping the payload of |data|. +// +// Uses the default compression level. ++ (NSData *)gtm_dataByGzippingData:(NSData *)data; + +/// Return an autoreleased NSData w/ the result of gzipping the bytes using |level| compression level. +// +// |level| can be 1-9, any other values will be clipped to that range. ++ (NSData *)gtm_dataByGzippingBytes:(const void *)bytes + length:(unsigned)length + compressionLevel:(int)level; + +/// Return an autoreleased NSData w/ the result of gzipping the payload of |data| using |level| compression level. ++ (NSData *)gtm_dataByGzippingData:(NSData *)data + compressionLevel:(int)level; + +// NOTE: deflate is *NOT* gzip. deflate is a "zlib" stream. pick which one +// you really want to create. (the inflate api will handle either) + +/// Return an autoreleased NSData w/ the result of deflating the bytes. +// +// Uses the default compression level. ++ (NSData *)gtm_dataByDeflatingBytes:(const void *)bytes + length:(unsigned)length; + +/// Return an autoreleased NSData w/ the result of deflating the payload of |data|. +// +// Uses the default compression level. ++ (NSData *)gtm_dataByDeflatingData:(NSData *)data; + +/// Return an autoreleased NSData w/ the result of deflating the bytes using |level| compression level. +// +// |level| can be 1-9, any other values will be clipped to that range. ++ (NSData *)gtm_dataByDeflatingBytes:(const void *)bytes + length:(unsigned)length + compressionLevel:(int)level; + +/// Return an autoreleased NSData w/ the result of deflating the payload of |data| using |level| compression level. ++ (NSData *)gtm_dataByDeflatingData:(NSData *)data + compressionLevel:(int)level; + + +/// Return an autoreleased NSData w/ the result of decompressing the bytes. +// +// The bytes to decompress can be zlib or gzip payloads. ++ (NSData *)gtm_dataByInflatingBytes:(const void *)bytes + length:(unsigned)length; + +/// Return an autoreleased NSData w/ the result of decompressing the payload of |data|. +// +// The data to decompress can be zlib or gzip payloads. ++ (NSData *)gtm_dataByInflatingData:(NSData *)data; + +@end diff --git a/Foundation/GTMNSData+zlib.m b/Foundation/GTMNSData+zlib.m new file mode 100644 index 0000000..89a906a --- /dev/null +++ b/Foundation/GTMNSData+zlib.m @@ -0,0 +1,246 @@ +// +// GTMNSData+zlib.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 "GTMNSData+zlib.h" +#import + +#define kChunkSize 1024 + +@interface NSData (GTMZlibAdditionsPrivate) ++ (NSData *)gtm_dataByCompressingBytes:(const void *)bytes + length:(unsigned)length + compressionLevel:(int)level + useGzip:(BOOL)useGzip; +@end + +@implementation NSData (GTMZlibAdditionsPrivate) ++ (NSData *)gtm_dataByCompressingBytes:(const void *)bytes + length:(unsigned)length + compressionLevel:(int)level + useGzip:(BOOL)useGzip { + if (!bytes || !length) return nil; + + if (level < Z_BEST_SPEED) + level = Z_BEST_SPEED; + else if (level > Z_BEST_COMPRESSION) + level = Z_BEST_COMPRESSION; + + z_stream strm; + bzero(&strm, sizeof(z_stream)); + + int windowBits = 15; // the default + int memLevel = 8; // the default + if (useGzip) + windowBits += 16; // enable gzip header instead of zlib header + int retCode; + if ((retCode = deflateInit2(&strm, level, Z_DEFLATED, windowBits, + memLevel, Z_DEFAULT_STRATEGY)) != Z_OK) { +#ifdef DEBUG + NSLog(@"Failed to init for deflate w/ level %d, error %d", + level, retCode); +#endif + return nil; + } + + // hint the size at 1/4 the input size + NSMutableData *result = [NSMutableData dataWithCapacity:(length/4)]; + unsigned char output[kChunkSize]; + + // setup the input + strm.avail_in = length; + strm.next_in = (unsigned char*)bytes; + + // loop to collect the data + do { + // update what we're passing in + strm.avail_out = kChunkSize; + strm.next_out = output; + retCode = deflate(&strm, Z_FINISH); + if ((retCode != Z_OK) && (retCode != Z_STREAM_END)) { +#ifdef DEBUG + NSLog(@"Error trying to deflate some of the payload, error %d", + retCode); +#endif + deflateEnd(&strm); + return nil; + } + // collect what we got + unsigned gotBack = kChunkSize - strm.avail_out; + if (gotBack > 0) { + [result appendBytes:output length:gotBack]; + } + + } while (retCode == Z_OK); + +#ifdef DEBUG + if (strm.avail_in != 0) { + NSLog(@"thought we finished deflate w/o using all input, %u bytes left", + strm.avail_in); + } + if (retCode != Z_STREAM_END) { + NSLog(@"thought we finished deflate w/o getting a result of stream end, code %d", + retCode); + } +#endif + + // clean up + deflateEnd(&strm); + + return result; +} // gtm_dataByCompressingBytes:length:compressionLevel:useGzip: + + +@end + + +@implementation NSData (GTMZLibAdditions) + ++ (NSData *)gtm_dataByGzippingBytes:(const void *)bytes + length:(unsigned)length { + return [self gtm_dataByCompressingBytes:bytes + length:length + compressionLevel:Z_DEFAULT_COMPRESSION + useGzip:YES]; +} // gtm_dataByGzippingBytes:length: + ++ (NSData *)gtm_dataByGzippingData:(NSData *)data { + return [self gtm_dataByCompressingBytes:[data bytes] + length:[data length] + compressionLevel:Z_DEFAULT_COMPRESSION + useGzip:YES]; +} // gtm_dataByGzippingData: + ++ (NSData *)gtm_dataByGzippingBytes:(const void *)bytes + length:(unsigned)length + compressionLevel:(int)level { + return [self gtm_dataByCompressingBytes:bytes + length:length + compressionLevel:level + useGzip:YES]; +} // gtm_dataByGzippingBytes:length:level: + ++ (NSData *)gtm_dataByGzippingData:(NSData *)data + compressionLevel:(int)level { + return [self gtm_dataByCompressingBytes:[data bytes] + length:[data length] + compressionLevel:level + useGzip:YES]; +} // gtm_dataByGzippingData:level: + ++ (NSData *)gtm_dataByDeflatingBytes:(const void *)bytes + length:(unsigned)length { + return [self gtm_dataByCompressingBytes:bytes + length:length + compressionLevel:Z_DEFAULT_COMPRESSION + useGzip:NO]; +} // gtm_dataByDeflatingBytes:length: + ++ (NSData *)gtm_dataByDeflatingData:(NSData *)data { + return [self gtm_dataByCompressingBytes:[data bytes] + length:[data length] + compressionLevel:Z_DEFAULT_COMPRESSION + useGzip:NO]; +} // gtm_dataByDeflatingData: + ++ (NSData *)gtm_dataByDeflatingBytes:(const void *)bytes + length:(unsigned)length + compressionLevel:(int)level { + return [self gtm_dataByCompressingBytes:bytes + length:length + compressionLevel:level + useGzip:NO]; +} // gtm_dataByDeflatingBytes:length:level: + ++ (NSData *)gtm_dataByDeflatingData:(NSData *)data + compressionLevel:(int)level { + return [self gtm_dataByCompressingBytes:[data bytes] + length:[data length] + compressionLevel:level + useGzip:NO]; +} // gtm_dataByDeflatingData:level: + ++ (NSData *)gtm_dataByInflatingBytes:(const void *)bytes + length:(unsigned)length { + if (!bytes || !length) return nil; + + z_stream strm; + bzero(&strm, sizeof(z_stream)); + + // setup the input + strm.avail_in = length; + strm.next_in = (unsigned char*)bytes; + + int windowBits = 15; // 15 to enable any window size + windowBits += 32; // and +32 to enable zlib or gzip header detection. + int retCode; + if ((retCode = inflateInit2(&strm, windowBits)) != Z_OK) { +#ifdef DEBUG + NSLog(@"Failed to init for inflate, error %d", retCode); +#endif + return nil; + } + + // hint the size at 4x the input size + NSMutableData *result = [NSMutableData dataWithCapacity:(length*4)]; + unsigned char output[kChunkSize]; + + // loop to collect the data + do { + // update what we're passing in + strm.avail_out = kChunkSize; + strm.next_out = output; + retCode = inflate(&strm, Z_NO_FLUSH); + if ((retCode != Z_OK) && (retCode != Z_STREAM_END)) { +#ifdef DEBUG + NSLog(@"Error trying to inflate some of the payload, error %d", + retCode); +#endif + inflateEnd(&strm); + return nil; + } + // collect what we got + unsigned gotBack = kChunkSize - strm.avail_out; + if (gotBack > 0) { + [result appendBytes:output length:gotBack]; + } + + } while (retCode == Z_OK); + +#ifdef DEBUG + if (strm.avail_in != 0) { + NSLog(@"thought we finished inflate w/o using all input, %u bytes left", + strm.avail_in); + } + if (retCode != Z_STREAM_END) { + NSLog(@"thought we finished inflate w/o getting a result of stream end, code %d", + retCode); + } +#endif + + // clean up + inflateEnd(&strm); + + return result; +} // gtm_dataByInflatingBytes:length: + ++ (NSData *)gtm_dataByInflatingData:(NSData *)data { + return [self gtm_dataByInflatingBytes:[data bytes] + length:[data length]]; +} // gtm_dataByInflatingData: + +@end diff --git a/Foundation/GTMNSData+zlibTest.m b/Foundation/GTMNSData+zlibTest.m new file mode 100644 index 0000000..f18735d --- /dev/null +++ b/Foundation/GTMNSData+zlibTest.m @@ -0,0 +1,183 @@ +// +// GTMNSData+zlibTest.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 "GTMNSData+zlib.h" +#import // for randiom/srandomdev +#import + +#import + +@interface GTMNSData_zlibTest : SenTestCase +@end + + +static void FillWithRandom(char *data, unsigned long len) { + char *max = data + len; + for ( ; data < max ; ++data) { + *data = random() & 0xFF; + } +} + +static BOOL HasGzipHeader(NSData *data) { + // very simple check + if ([data length] > 2) { + const unsigned char *bytes = [data bytes]; + return (bytes[0] == 0x1f) && (bytes[1] == 0x8b); + } + return NO; +} + + +@implementation GTMNSData_zlibTest + +- (void)setUp { + // seed random from /dev/random + srandomdev(); +} + +- (void)testInflateDeflate { + // generate a range of sizes w/ random content + for (int n = 0 ; n < 2 ; ++n) { + for (int x = 1 ; x < 128 ; ++x) { + NSAutoreleasePool *localPool = [[NSAutoreleasePool alloc] init]; + STAssertNotNil(localPool, @"failed to alloc local pool"); + + NSMutableData *data = [NSMutableData data]; + STAssertNotNil(data, @"failed to alloc data block"); + + // first pass small blocks, second pass, larger ones, but second pass + // avoid making them multimples of 128. + [data setLength:((n*x*128) + x)]; + FillWithRandom([data mutableBytes], [data length]); + + // w/ *Bytes apis, default level + NSData *deflated = [NSData gtm_dataByDeflatingBytes:[data bytes] length:[data length]]; + STAssertNotNil(deflated, @"failed to deflate data block"); + STAssertTrue([deflated length] > 0, @"failed to deflate data block"); + STAssertFalse(HasGzipHeader(deflated), @"has gzip header on zlib data"); + NSData *dataPrime = [NSData gtm_dataByInflatingBytes:[deflated bytes] length:[deflated length]]; + STAssertNotNil(dataPrime, @"failed to inflate data block"); + STAssertTrue([dataPrime length] > 0, @"failed to inflate data block"); + STAssertEqualObjects(data, dataPrime, @"failed to round trip via *Bytes apis"); + + // w/ *Data apis, default level + deflated = [NSData gtm_dataByDeflatingData:data]; + STAssertNotNil(deflated, @"failed to deflate data block"); + STAssertTrue([deflated length] > 0, @"failed to deflate data block"); + STAssertFalse(HasGzipHeader(deflated), @"has gzip header on zlib data"); + dataPrime = [NSData gtm_dataByInflatingData:deflated]; + STAssertNotNil(dataPrime, @"failed to inflate data block"); + STAssertTrue([dataPrime length] > 0, @"failed to inflate data block"); + STAssertEqualObjects(data, dataPrime, @"failed to round trip via *Data apis"); + + // loop over the compression levels + for (int level = 1 ; level < 9 ; ++level) { + // w/ *Bytes apis, using our level + deflated = [NSData gtm_dataByDeflatingBytes:[data bytes] + length:[data length] + compressionLevel:level]; + STAssertNotNil(deflated, @"failed to deflate data block"); + STAssertTrue([deflated length] > 0, @"failed to deflate data block"); + STAssertFalse(HasGzipHeader(deflated), @"has gzip header on zlib data"); + dataPrime = [NSData gtm_dataByInflatingBytes:[deflated bytes] length:[deflated length]]; + STAssertNotNil(dataPrime, @"failed to inflate data block"); + STAssertTrue([dataPrime length] > 0, @"failed to inflate data block"); + STAssertEqualObjects(data, dataPrime, @"failed to round trip via *Bytes apis"); + + // w/ *Data apis, using our level + deflated = [NSData gtm_dataByDeflatingData:data compressionLevel:level]; + STAssertNotNil(deflated, @"failed to deflate data block"); + STAssertTrue([deflated length] > 0, @"failed to deflate data block"); + STAssertFalse(HasGzipHeader(deflated), @"has gzip header on zlib data"); + dataPrime = [NSData gtm_dataByInflatingData:deflated]; + STAssertNotNil(dataPrime, @"failed to inflate data block"); + STAssertTrue([dataPrime length] > 0, @"failed to inflate data block"); + STAssertEqualObjects(data, dataPrime, @"failed to round trip via *Data apis"); + } + + [localPool release]; + } + } +} + +- (void)testInflateGzip { + // generate a range of sizes w/ random content + for (int n = 0 ; n < 2 ; ++n) { + for (int x = 1 ; x < 128 ; ++x) { + NSAutoreleasePool *localPool = [[NSAutoreleasePool alloc] init]; + STAssertNotNil(localPool, @"failed to alloc local pool"); + + NSMutableData *data = [NSMutableData data]; + STAssertNotNil(data, @"failed to alloc data block"); + + // first pass small blocks, second pass, larger ones, but second pass + // avoid making them multimples of 128. + [data setLength:((n*x*128) + x)]; + FillWithRandom([data mutableBytes], [data length]); + + // w/ *Bytes apis, default level + NSData *gzipped = [NSData gtm_dataByGzippingBytes:[data bytes] length:[data length]]; + STAssertNotNil(gzipped, @"failed to gzip data block"); + STAssertTrue([gzipped length] > 0, @"failed to gzip data block"); + STAssertTrue(HasGzipHeader(gzipped), @"doesn't have gzip header on gzipped data"); + NSData *dataPrime = [NSData gtm_dataByInflatingBytes:[gzipped bytes] length:[gzipped length]]; + STAssertNotNil(dataPrime, @"failed to inflate data block"); + STAssertTrue([dataPrime length] > 0, @"failed to inflate data block"); + STAssertEqualObjects(data, dataPrime, @"failed to round trip via *Bytes apis"); + + // w/ *Data apis, default level + gzipped = [NSData gtm_dataByGzippingData:data]; + STAssertNotNil(gzipped, @"failed to gzip data block"); + STAssertTrue([gzipped length] > 0, @"failed to gzip data block"); + STAssertTrue(HasGzipHeader(gzipped), @"doesn't have gzip header on gzipped data"); + dataPrime = [NSData gtm_dataByInflatingData:gzipped]; + STAssertNotNil(dataPrime, @"failed to inflate data block"); + STAssertTrue([dataPrime length] > 0, @"failed to inflate data block"); + STAssertEqualObjects(data, dataPrime, @"failed to round trip via *Data apis"); + + // loop over the compression levels + for (int level = 1 ; level < 9 ; ++level) { + // w/ *Bytes apis, using our level + gzipped = [NSData gtm_dataByGzippingBytes:[data bytes] + length:[data length] + compressionLevel:level]; + STAssertNotNil(gzipped, @"failed to gzip data block"); + STAssertTrue([gzipped length] > 0, @"failed to gzip data block"); + STAssertTrue(HasGzipHeader(gzipped), @"doesn't have gzip header on gzipped data"); + dataPrime = [NSData gtm_dataByInflatingBytes:[gzipped bytes] length:[gzipped length]]; + STAssertNotNil(dataPrime, @"failed to inflate data block"); + STAssertTrue([dataPrime length] > 0, @"failed to inflate data block"); + STAssertEqualObjects(data, dataPrime, @"failed to round trip via *Bytes apis"); + + // w/ *Data apis, using our level + gzipped = [NSData gtm_dataByGzippingData:data compressionLevel:level]; + STAssertNotNil(gzipped, @"failed to gzip data block"); + STAssertTrue([gzipped length] > 0, @"failed to gzip data block"); + STAssertTrue(HasGzipHeader(gzipped), @"doesn't have gzip header on gzipped data"); + dataPrime = [NSData gtm_dataByInflatingData:gzipped]; + STAssertNotNil(dataPrime, @"failed to inflate data block"); + STAssertTrue([dataPrime length] > 0, @"failed to inflate data block"); + STAssertEqualObjects(data, dataPrime, @"failed to round trip via *Data apis"); + } + + [localPool release]; + } + } +} + +@end diff --git a/Foundation/GTMNSEnumerator+Filter.h b/Foundation/GTMNSEnumerator+Filter.h new file mode 100644 index 0000000..8e10d93 --- /dev/null +++ b/Foundation/GTMNSEnumerator+Filter.h @@ -0,0 +1,53 @@ +// +// GTMNSEnumerator+Filter.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 + +/// A generic category for methods that allow us to filter enumeratable +/// containers, inspired by C++ Standard Library's use of iterators. +/// Like in C++, these assume the underlying container is not modified during +/// the lifetime of the iterator. +/// +@interface NSEnumerator (GTMEnumeratorFilterAdditions) + +/// @argument predicate - the function return BOOL. will be applied to each element +/// @argument argument - optional argument to pass to predicate +/// @returns an enumerator that contains only elements where [element sel:argument] is true +- (NSEnumerator *)gtm_filteredEnumeratorByMakingEachObjectPerformSelector:(SEL)predicate + withObject:(id)argument; + +/// @argument selector - the function return a transformed object. will be applied to each element +/// @argument argument - optional argument to pass to transformer +/// @returns an enumerator that contains the transformed elements +- (NSEnumerator *)gtm_enumeratorByMakingEachObjectPerformSelector:(SEL)selector + withObject:(id)argument; + +/// @argument target - receiver for each method +/// @argument predicate - as in, [target predicate: [self nextObject]], return a BOOL +/// @returns an enumerator that contains only elements where [element sel:argument] is true +- (NSEnumerator *)gtm_filteredEnumeratorByTarget:(id)target + performOnEachSelector:(SEL)predicate; + +/// @argument target - receiver for each method +/// @argument sel - as in, [target selector: [self nextObject]], return a transformed object +/// @returns an enumerator that contains the transformed elements +- (NSEnumerator *)gtm_enumeratorByTarget:(id)target + performOnEachSelector:(SEL)selector; + +@end + diff --git a/Foundation/GTMNSEnumerator+Filter.m b/Foundation/GTMNSEnumerator+Filter.m new file mode 100644 index 0000000..60c85f3 --- /dev/null +++ b/Foundation/GTMNSEnumerator+Filter.m @@ -0,0 +1,167 @@ +// +// GTMNSEnumerator+Filter.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 "GTMNSEnumerator+Filter.h" + +// a private subclass of NSEnumerator that does all the work. +// public interface just returns one of these. +// This top level class contains all the additional boilerplate. Specific +// behavior is in the subclasses. +@interface GTMEnumerator : NSEnumerator { + @protected + NSEnumerator *base_; // STRONG + SEL operation_; // either a predicate or a transform depending on context. + id other_; // STRONG, may be nil +} +- (id)nextObject; +- (BOOL)filterObject:(id)obj returning:(id *)resultp; +@end +@implementation GTMEnumerator +- (id)initWithBase:(NSEnumerator *)base + sel:(SEL)filter + object:(id)optionalOther { + self = [super init]; + if (self) { + + // specializing a nil enumerator returns nil. + if (nil == base) { + [self release]; + return nil; + } + + base_ = [base retain]; + operation_ = filter; + other_ = [optionalOther retain]; + } + return self; +} + +// it is an error to call this initializer. +- (id)init { + [self doesNotRecognizeSelector:_cmd]; + return nil; +} + +- (void)dealloc { + [base_ release]; + [other_ release]; + [super dealloc]; +} + +- (id)nextObject { + for (id obj = [base_ nextObject]; obj; obj = [base_ nextObject]) { + id result = nil; + if ([self filterObject:obj returning:&result]) { + return result; + } + } + return nil; +} + +// subclasses must override +- (BOOL)filterObject:(id)obj returning:(id *)resultp { + [self doesNotRecognizeSelector:_cmd]; + return NO; +} +@end + +// a transformer, for each item in the enumerator, returns a f(item). +@interface GTMEnumeratorTransformer : GTMEnumerator +- (BOOL)filterObject:(id)obj returning:(id *)resultp; +@end +@implementation GTMEnumeratorTransformer +- (BOOL)filterObject:(id)obj returning:(id *)resultp { + *resultp = [obj performSelector:operation_ withObject:other_]; + return nil != *resultp; +} +@end + +// a transformer, for each item in the enumerator, returns a f(item). +// a target transformer swaps the target and the argument. +@interface GTMEnumeratorTargetTransformer : GTMEnumerator +- (BOOL)filterObject:(id)obj returning:(id *)resultp; +@end +@implementation GTMEnumeratorTargetTransformer +- (BOOL)filterObject:(id)obj returning:(id *)resultp { + *resultp = [other_ performSelector:operation_ withObject:obj]; + return nil != *resultp; +} +@end + +// a filter, for each item in the enumerator, if(f(item)) { returns item. } +@interface GTMEnumeratorFilter : GTMEnumerator +- (BOOL)filterObject:(id)obj returning:(id *)resultp; +@end +@implementation GTMEnumeratorFilter +// We must take care here, since Intel leaves junk in high bytes of return register +// for predicates that return BOOL. +- (BOOL)filterObject:(id)obj returning:(id *)resultp { + *resultp = obj; + // intptr_t is an integer the same size as a pointer. + return (BOOL) (intptr_t) [obj performSelector:operation_ withObject:other_]; +} +@end + +// a target filter, for each item in the enumerator, if(f(item)) { returns item. } +// a target transformer swaps the target and the argument. +@interface GTMEnumeratorTargetFilter : GTMEnumerator +- (BOOL)filterObject:(id)obj returning:(id *)resultp; +@end +@implementation GTMEnumeratorTargetFilter +// We must take care here, since Intel leaves junk in high bytes of return register +// for predicates that return BOOL. +- (BOOL)filterObject:(id)obj returning:(id *)resultp { + *resultp = obj; + // intptr_t is an integer the same size as a pointer. + return (BOOL) (intptr_t) [other_ performSelector:operation_ withObject:obj]; +} +@end + +@implementation NSEnumerator (GTMEnumeratorFilterAdditions) + +- (NSEnumerator *)gtm_filteredEnumeratorByMakingEachObjectPerformSelector:(SEL)selector + withObject:(id)argument { + return [[[GTMEnumeratorFilter alloc] initWithBase:self + sel:selector + object:argument] autorelease]; +} + +- (NSEnumerator *)gtm_enumeratorByMakingEachObjectPerformSelector:(SEL)selector + withObject:(id)argument { + return [[[GTMEnumeratorTransformer alloc] initWithBase:self + sel:selector + object:argument] autorelease]; +} + + +- (NSEnumerator *)gtm_filteredEnumeratorByTarget:(id)target + performOnEachSelector:(SEL)selector { + return [[[GTMEnumeratorTargetFilter alloc] initWithBase:self + sel:selector + object:target] autorelease]; +} + +- (NSEnumerator *)gtm_enumeratorByTarget:(id)target + performOnEachSelector:(SEL)selector { + return [[[GTMEnumeratorTargetTransformer alloc] initWithBase:self + sel:selector + object:target] autorelease]; +} + +@end + diff --git a/Foundation/GTMNSEnumerator+FilterTest.m b/Foundation/GTMNSEnumerator+FilterTest.m new file mode 100644 index 0000000..99bde01 --- /dev/null +++ b/Foundation/GTMNSEnumerator+FilterTest.m @@ -0,0 +1,68 @@ +// +// GTMNSEnumerator+FilterTest.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 +#import "GTMNSEnumerator+Filter.h" + +@interface GTMNSEnumerator_FilterTest : SenTestCase +@end + +@implementation GTMNSEnumerator_FilterTest + +// test using an NSSet enumerator. +- (void)testEnumeratorByMakingEachObjectPerformSelector { + NSSet *numbers = [NSSet setWithObjects: @"1", @"2", @"3", nil]; + NSEnumerator *e = [[numbers objectEnumerator] + gtm_enumeratorByMakingEachObjectPerformSelector:@selector(stringByAppendingString:) + withObject:@" "]; + NSMutableSet *trailingSpaces = [NSMutableSet set]; + id obj; + while (nil != (obj = [e nextObject])) { + [trailingSpaces addObject:obj]; + } + NSSet *trailingSpacesGood = [NSSet setWithObjects: @"1 ", @"2 ", @"3 ", nil]; + STAssertEqualObjects(trailingSpaces, trailingSpacesGood, @""); + NSSet *empty = [NSSet set]; + NSEnumerator *ee = [[empty objectEnumerator] + gtm_enumeratorByMakingEachObjectPerformSelector:@selector(stringByAppendingString:) + withObject:@" "]; + + NSMutableSet *emptySpaces = [NSMutableSet set]; + while (nil != (obj = [ee nextObject])) { + [emptySpaces addObject:obj]; + } + STAssertEqualObjects(empty, emptySpaces, @""); +} + +// test using an NSDictionary enumerator. +- (void)testFilteredEnumeratorByMakingEachObjectPerformSelector { + NSDictionary *numbers = [NSDictionary dictionaryWithObjectsAndKeys: @"1", @"1", @"", @"", @"3", @"3", nil]; + + // |length| filters out length 0 objects + NSEnumerator *e = [[numbers objectEnumerator] + gtm_filteredEnumeratorByMakingEachObjectPerformSelector:@selector(length) + withObject:nil]; + + NSArray *lengths = [e allObjects]; + NSArray *lengthsGood = [NSArray arrayWithObjects: @"1", @"3", nil]; + STAssertEqualObjects(lengths, lengthsGood, @""); +} + + + +@end diff --git a/Foundation/GTMNSString+HTML.h b/Foundation/GTMNSString+HTML.h new file mode 100644 index 0000000..1273cc3 --- /dev/null +++ b/Foundation/GTMNSString+HTML.h @@ -0,0 +1,66 @@ +// +// GTMNSString+HTML.h +// Dealing with NSStrings that contain HTML +// +// 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 + +/// Utilities for NSStrings containing HTML +@interface NSString (GTMNSStringHTMLAdditions) + +/// Get a string where internal characters that need escaping for HTML are escaped +// +/// For example, '&' become '&'. This will only cover characters from table +/// A.2.2 of http://www.w3.org/TR/xhtml1/dtds.html#a_dtd_Special_characters +/// which is what you want for a unicode encoded webpage. If you have a ascii +/// or non-encoded webpage, please use stringByEscapingAsciiHTML which will +/// encode all characters. +/// +/// For obvious reasons this call is only safe once. +// +// Returns: +// Autoreleased NSString +// +- (NSString *)gtm_stringByEscapingForHTML; + +/// Get a string where internal characters that need escaping for HTML are escaped +// +/// For example, '&' become '&' +/// All non-mapped characters (unicode that don't have a &keyword; mapping) +/// will be converted to the appropriate &#xxx; value. If your webpage is +/// unicode encoded (UTF16 or UTF8) use stringByEscapingHTML instead as it is +/// faster, and produces less bloated and more readable HTML (as long as you +/// are using a unicode compliant HTML reader). +/// +/// For obvious reasons this call is only safe once. +// +// Returns: +// Autoreleased NSString +// +- (NSString *)gtm_stringByEscapingForAsciiHTML; + +/// Get a string where internal characters that are escaped for HTML are unescaped +// +/// For example, '&' becomes '&' +/// Handles and 2 cases as well +/// +// Returns: +// Autoreleased NSString +// +- (NSString *)gtm_stringByUnescapingFromHTML; + +@end diff --git a/Foundation/GTMNSString+HTML.m b/Foundation/GTMNSString+HTML.m new file mode 100644 index 0000000..f9e99dc --- /dev/null +++ b/Foundation/GTMNSString+HTML.m @@ -0,0 +1,514 @@ +// +// GTMNSString+HTML.m +// Dealing with NSStrings that contain HTML +// +// 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 "GTMNSString+HTML.h" + +typedef struct { + NSString *escapeSequence; + unichar uchar; +} HTMLEscapeMap; + +// Taken from http://www.w3.org/TR/xhtml1/dtds.html#a_dtd_Special_characters +// Ordered by uchar lowest to highest for bsearching +static HTMLEscapeMap gAsciiHTMLEscapeMap[] = { + // A.2.2. Special characters + { @""", 34 }, + { @"&", 38 }, + { @"'", 39 }, + { @"<", 60 }, + { @">", 62 }, + + // A.2.1. Latin-1 characters + { @" ", 160 }, + { @"¡", 161 }, + { @"¢", 162 }, + { @"£", 163 }, + { @"¤", 164 }, + { @"¥", 165 }, + { @"¦", 166 }, + { @"§", 167 }, + { @"¨", 168 }, + { @"©", 169 }, + { @"ª", 170 }, + { @"«", 171 }, + { @"¬", 172 }, + { @"­", 173 }, + { @"®", 174 }, + { @"¯", 175 }, + { @"°", 176 }, + { @"±", 177 }, + { @"²", 178 }, + { @"³", 179 }, + { @"´", 180 }, + { @"µ", 181 }, + { @"¶", 182 }, + { @"·", 183 }, + { @"¸", 184 }, + { @"¹", 185 }, + { @"º", 186 }, + { @"»", 187 }, + { @"¼", 188 }, + { @"½", 189 }, + { @"¾", 190 }, + { @"¿", 191 }, + { @"À", 192 }, + { @"Á", 193 }, + { @"Â", 194 }, + { @"Ã", 195 }, + { @"Ä", 196 }, + { @"Å", 197 }, + { @"Æ", 198 }, + { @"Ç", 199 }, + { @"È", 200 }, + { @"É", 201 }, + { @"Ê", 202 }, + { @"Ë", 203 }, + { @"Ì", 204 }, + { @"Í", 205 }, + { @"Î", 206 }, + { @"Ï", 207 }, + { @"Ð", 208 }, + { @"Ñ", 209 }, + { @"Ò", 210 }, + { @"Ó", 211 }, + { @"Ô", 212 }, + { @"Õ", 213 }, + { @"Ö", 214 }, + { @"×", 215 }, + { @"Ø", 216 }, + { @"Ù", 217 }, + { @"Ú", 218 }, + { @"Û", 219 }, + { @"Ü", 220 }, + { @"Ý", 221 }, + { @"Þ", 222 }, + { @"ß", 223 }, + { @"à", 224 }, + { @"á", 225 }, + { @"â", 226 }, + { @"ã", 227 }, + { @"ä", 228 }, + { @"å", 229 }, + { @"æ", 230 }, + { @"ç", 231 }, + { @"è", 232 }, + { @"é", 233 }, + { @"ê", 234 }, + { @"ë", 235 }, + { @"ì", 236 }, + { @"í", 237 }, + { @"î", 238 }, + { @"ï", 239 }, + { @"ð", 240 }, + { @"ñ", 241 }, + { @"ò", 242 }, + { @"ó", 243 }, + { @"ô", 244 }, + { @"õ", 245 }, + { @"ö", 246 }, + { @"÷", 247 }, + { @"ø", 248 }, + { @"ù", 249 }, + { @"ú", 250 }, + { @"û", 251 }, + { @"ü", 252 }, + { @"ý", 253 }, + { @"þ", 254 }, + { @"ÿ", 255 }, + + // A.2.2. Special characters cont'd + { @"Œ", 338 }, + { @"œ", 339 }, + { @"Š", 352 }, + { @"š", 353 }, + { @"Ÿ", 376 }, + + // A.2.3. Symbols + { @"ƒ", 402 }, + + // A.2.2. Special characters cont'd + { @"ˆ", 710 }, + { @"˜", 732 }, + + // A.2.3. Symbols cont'd + { @"Α", 913 }, + { @"Β", 914 }, + { @"Γ", 915 }, + { @"Δ", 916 }, + { @"Ε", 917 }, + { @"Ζ", 918 }, + { @"Η", 919 }, + { @"Θ", 920 }, + { @"Ι", 921 }, + { @"Κ", 922 }, + { @"Λ", 923 }, + { @"Μ", 924 }, + { @"Ν", 925 }, + { @"Ξ", 926 }, + { @"Ο", 927 }, + { @"Π", 928 }, + { @"Ρ", 929 }, + { @"Σ", 931 }, + { @"Τ", 932 }, + { @"Υ", 933 }, + { @"Φ", 934 }, + { @"Χ", 935 }, + { @"Ψ", 936 }, + { @"Ω", 937 }, + { @"α", 945 }, + { @"β", 946 }, + { @"γ", 947 }, + { @"δ", 948 }, + { @"ε", 949 }, + { @"ζ", 950 }, + { @"η", 951 }, + { @"θ", 952 }, + { @"ι", 953 }, + { @"κ", 954 }, + { @"λ", 955 }, + { @"μ", 956 }, + { @"ν", 957 }, + { @"ξ", 958 }, + { @"ο", 959 }, + { @"π", 960 }, + { @"ρ", 961 }, + { @"ς", 962 }, + { @"σ", 963 }, + { @"τ", 964 }, + { @"υ", 965 }, + { @"φ", 966 }, + { @"χ", 967 }, + { @"ψ", 968 }, + { @"ω", 969 }, + { @"ϑ", 977 }, + { @"ϒ", 978 }, + { @"ϖ", 982 }, + + // A.2.2. Special characters cont'd + { @" ", 8194 }, + { @" ", 8195 }, + { @" ", 8201 }, + { @"‌", 8204 }, + { @"‍", 8205 }, + { @"‎", 8206 }, + { @"‏", 8207 }, + { @"–", 8211 }, + { @"—", 8212 }, + { @"‘", 8216 }, + { @"’", 8217 }, + { @"‚", 8218 }, + { @"“", 8220 }, + { @"”", 8221 }, + { @"„", 8222 }, + { @"†", 8224 }, + { @"‡", 8225 }, + // A.2.3. Symbols cont'd + { @"•", 8226 }, + { @"…", 8230 }, + + // A.2.2. Special characters cont'd + { @"‰", 8240 }, + + // A.2.3. Symbols cont'd + { @"′", 8242 }, + { @"″", 8243 }, + + // A.2.2. Special characters cont'd + { @"‹", 8249 }, + { @"›", 8250 }, + + // A.2.3. Symbols cont'd + { @"‾", 8254 }, + { @"⁄", 8260 }, + + // A.2.2. Special characters cont'd + { @"€", 8364 }, + + // A.2.3. Symbols cont'd + { @"ℑ", 8465 }, + { @"℘", 8472 }, + { @"ℜ", 8476 }, + { @"™", 8482 }, + { @"ℵ", 8501 }, + { @"←", 8592 }, + { @"↑", 8593 }, + { @"→", 8594 }, + { @"↓", 8595 }, + { @"↔", 8596 }, + { @"↵", 8629 }, + { @"⇐", 8656 }, + { @"⇑", 8657 }, + { @"⇒", 8658 }, + { @"⇓", 8659 }, + { @"⇔", 8660 }, + { @"∀", 8704 }, + { @"∂", 8706 }, + { @"∃", 8707 }, + { @"∅", 8709 }, + { @"∇", 8711 }, + { @"∈", 8712 }, + { @"∉", 8713 }, + { @"∋", 8715 }, + { @"∏", 8719 }, + { @"∑", 8721 }, + { @"−", 8722 }, + { @"∗", 8727 }, + { @"√", 8730 }, + { @"∝", 8733 }, + { @"∞", 8734 }, + { @"∠", 8736 }, + { @"∧", 8743 }, + { @"∨", 8744 }, + { @"∩", 8745 }, + { @"∪", 8746 }, + { @"∫", 8747 }, + { @"∴", 8756 }, + { @"∼", 8764 }, + { @"≅", 8773 }, + { @"≈", 8776 }, + { @"≠", 8800 }, + { @"≡", 8801 }, + { @"≤", 8804 }, + { @"≥", 8805 }, + { @"⊂", 8834 }, + { @"⊃", 8835 }, + { @"⊄", 8836 }, + { @"⊆", 8838 }, + { @"⊇", 8839 }, + { @"⊕", 8853 }, + { @"⊗", 8855 }, + { @"⊥", 8869 }, + { @"⋅", 8901 }, + { @"⌈", 8968 }, + { @"⌉", 8969 }, + { @"⌊", 8970 }, + { @"⌋", 8971 }, + { @"⟨", 9001 }, + { @"⟩", 9002 }, + { @"◊", 9674 }, + { @"♠", 9824 }, + { @"♣", 9827 }, + { @"♥", 9829 }, + { @"♦", 9830 } +}; + +// Taken from http://www.w3.org/TR/xhtml1/dtds.html#a_dtd_Special_characters +// This is table A.2.2 Special Characters +static HTMLEscapeMap gUnicodeHTMLEscapeMap[] = { + // C0 Controls and Basic Latin + { @""", 34 }, + { @"&", 38 }, + { @"'", 39 }, + { @"<", 60 }, + { @">", 62 }, + + // Latin Extended-A + { @"Œ", 338 }, + { @"œ", 339 }, + { @"Š", 352 }, + { @"š", 353 }, + { @"Ÿ", 376 }, + + // Spacing Modifier Letters + { @"ˆ", 710 }, + { @"˜", 732 }, + + // General Punctuation + { @" ", 8194 }, + { @" ", 8195 }, + { @" ", 8201 }, + { @"‌", 8204 }, + { @"‍", 8205 }, + { @"‎", 8206 }, + { @"‏", 8207 }, + { @"–", 8211 }, + { @"—", 8212 }, + { @"‘", 8216 }, + { @"’", 8217 }, + { @"‚", 8218 }, + { @"“", 8220 }, + { @"”", 8221 }, + { @"„", 8222 }, + { @"†", 8224 }, + { @"‡", 8225 }, + { @"‰", 8240 }, + { @"‹", 8249 }, + { @"›", 8250 }, + { @"€", 8364 }, +}; + + +// Utility function for Bsearching table above +static int escapeMapCompare(const void *ucharVoid, const void *mapVoid) { + unichar *uchar = (unichar*)ucharVoid; + HTMLEscapeMap *map = (HTMLEscapeMap*)mapVoid; + int val; + if (*uchar > map->uchar) { + val = 1; + } else if (*uchar < map->uchar) { + val = -1; + } else { + val = 0; + } + return val; +} + +@implementation NSString (GTMNSStringHTMLAdditions) + +- (NSString *)gtm_stringByEscapingHTMLUsingTable:(HTMLEscapeMap*)table + ofSize:(int)size + escapingUnicode:(BOOL)escapeUnicode { + NSMutableString *finalString = [NSMutableString string]; + int length = [self length]; + require_quiet(length != 0, cantConvertAnything); + + unichar *buffer = malloc(sizeof(unichar) * length); + require_action(buffer, cantAllocBuffer, finalString = nil); + unichar *buffer2 = malloc(sizeof(unichar) * length); + require_action(buffer2, cantAllocBuffer2, finalString = nil); + + [self getCharacters:buffer]; + int buffer2Length = 0; + + for (int i = 0; i < length; ++i) { + HTMLEscapeMap *val = bsearch(&buffer[i], table, + size / sizeof(HTMLEscapeMap), + sizeof(HTMLEscapeMap), escapeMapCompare); + if (val || (escapeUnicode && buffer[i] > 127)) { + if (buffer2Length) { + CFStringRef buffer2String = + CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, + buffer2, buffer2Length, + kCFAllocatorNull); + require_action(buffer2String, cantCreateString, finalString = nil); + [finalString appendString:(NSString*)buffer2String]; + CFRelease(buffer2String); + buffer2Length = 0; + } + if (val) { + [finalString appendString:val->escapeSequence]; + } + else if (escapeUnicode && buffer[i] > 127) { + [finalString appendFormat:@"&#%d;", buffer[i]]; + } else { + // Should never get here. Assert in debug. + require_action(NO, cantCreateString, finalString = nil); + } + } else { + buffer2[buffer2Length] = buffer[i]; + buffer2Length += 1; + } + } + if (buffer2Length) { + CFStringRef buffer2String = + CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, + buffer2, buffer2Length, + kCFAllocatorNull); + require_action(buffer2String, cantCreateString, finalString = nil); + [finalString appendString:(NSString*)buffer2String]; + CFRelease(buffer2String); + } +cantCreateString: + free(buffer2); +cantAllocBuffer2: + free(buffer); +cantAllocBuffer: +cantConvertAnything: + return finalString; +} + +- (NSString *)gtm_stringByEscapingForHTML { + return [self gtm_stringByEscapingHTMLUsingTable:gUnicodeHTMLEscapeMap + ofSize:sizeof(gUnicodeHTMLEscapeMap) + escapingUnicode:NO]; +} // gtm_stringByEscapingHTML + +- (NSString *)gtm_stringByEscapingForAsciiHTML { + return [self gtm_stringByEscapingHTMLUsingTable:gAsciiHTMLEscapeMap + ofSize:sizeof(gAsciiHTMLEscapeMap) + escapingUnicode:YES]; +} // gtm_stringByEscapingAsciiHTML + +- (NSString *)gtm_stringByUnescapingFromHTML { + NSRange range = NSMakeRange(0, [self length]); + NSRange subrange = [self rangeOfString:@"&" options:NSBackwardsSearch range:range]; + + // if no ampersands, we've got a quick way out + if (subrange.length == 0) return self; + NSMutableString *finalString = [NSMutableString stringWithString:self]; + do { + NSRange semiColonRange = NSMakeRange(subrange.location, NSMaxRange(range) - subrange.location); + semiColonRange = [self rangeOfString:@";" options:0 range:semiColonRange]; + range = NSMakeRange(0, subrange.location); + // if we don't find a semicolon in the range, we don't have a sequence + if (semiColonRange.location == NSNotFound) { + continue; + } + NSRange escapeRange = NSMakeRange(subrange.location, semiColonRange.location - subrange.location + 1); + NSString *escapeString = [self substringWithRange:escapeRange]; + unsigned length = [escapeString length]; + // a squence must be longer than 3 (<) and less than 11 (ϑ) + if (length > 3 && length < 11) { + if ([escapeString characterAtIndex:1] == '#') { + unichar char2 = [escapeString characterAtIndex:2]; + if (char2 == 'x' || char2 == 'X') { + // Hex escape squences £ + NSString *hexSequence = [escapeString substringWithRange:NSMakeRange(3, length - 4)]; + NSScanner *scanner = [NSScanner scannerWithString:hexSequence]; + unsigned value; + if ([scanner scanHexInt:&value] && + value < USHRT_MAX && + value > 0 + && [scanner scanLocation] == length - 4) { + unichar uchar = value; + NSString *charString = [NSString stringWithCharacters:&uchar length:1]; + [finalString replaceCharactersInRange:escapeRange withString:charString]; + } + + } else { + // Decimal Sequences { + NSString *numberSequence = [escapeString substringWithRange:NSMakeRange(2, length - 3)]; + NSScanner *scanner = [NSScanner scannerWithString:numberSequence]; + int value; + if ([scanner scanInt:&value] && + value < USHRT_MAX && + value > 0 + && [scanner scanLocation] == length - 3) { + unichar uchar = value; + NSString *charString = [NSString stringWithCharacters:&uchar length:1]; + [finalString replaceCharactersInRange:escapeRange withString:charString]; + } + } + } else { + // "standard" sequences + for (unsigned i = 0; i < sizeof(gAsciiHTMLEscapeMap) / sizeof(HTMLEscapeMap); ++i) { + if ([escapeString isEqualToString:gAsciiHTMLEscapeMap[i].escapeSequence]) { + [finalString replaceCharactersInRange:escapeRange withString:[NSString stringWithCharacters:&gAsciiHTMLEscapeMap[i].uchar length:1]]; + break; + } + } + } + } + } while ((subrange = [self rangeOfString:@"&" options:NSBackwardsSearch range:range]).length != 0); + return finalString; +} // gtm_stringByUnescapingHTML + + + +@end diff --git a/Foundation/GTMNSString+HTMLTest.m b/Foundation/GTMNSString+HTMLTest.m new file mode 100644 index 0000000..b60ed9d --- /dev/null +++ b/Foundation/GTMNSString+HTMLTest.m @@ -0,0 +1,240 @@ +// +// GTMNSString+HTMLTest.m +// +// Copyright 2005-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 +#import "GTMSenTestCase.h" +#import "GTMNSString+HTML.h" + +@interface GTMNSString_HTMLTest : SenTestCase +@end + +@implementation GTMNSString_HTMLTest + +- (void)testStringByEscapingHTML { + unichar chars[] = + { 34, 38, 39, 60, 62, 338, 339, 352, 353, 376, 710, 732, + 8194, 8195, 8201, 8204, 8205, 8206, 8207, 8211, 8212, 8216, 8217, 8218, + 8220, 8221, 8222, 8224, 8225, 8240, 8249, 8250, 8364, }; + + NSString *string1 = [NSString stringWithCharacters:chars + length:sizeof(chars) / sizeof(unichar)]; + NSString *string2 = + @""&'<>ŒœŠšŸ" + "ˆ˜   ‌‍‎‏–" + "—‘’‚“”„†‡" + "‰‹›€"; + + STAssertEqualObjects([string1 gtm_stringByEscapingForHTML], + string2, + @"HTML escaping failed"); + + STAssertEqualObjects([@"" gtm_stringByEscapingForHTML], + @"<this & that>", + @"HTML escaping failed"); + NSString *string = [NSString stringWithUTF8String:"パン・&ド・カンパーニュ"]; + NSString *escapeStr = [NSString stringWithUTF8String:"パン・&ド・カンパーニュ"]; + STAssertEqualObjects([string gtm_stringByEscapingForHTML], + escapeStr, + @"HTML escaping failed"); + + string = [NSString stringWithUTF8String:"abcا1ب<تdef&"]; + STAssertEqualObjects([string gtm_stringByEscapingForHTML], + [NSString stringWithUTF8String:"abcا1ب<تdef&"], + @"HTML escaping failed"); +} // testStringByEscapingHTML + +- (void)testStringByEscapingAsciiHTML { + unichar chars[] = + { 34, 38, 39, 60, 62, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, + 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, + 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, + 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, + 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, + 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, + 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 338, 339, 352, 353, 376, + 402, 710, 732, 913, 914, 915, 916, 917, 918, 919, 920, 921, 922, 923, 924, + 925, 926, 927, 928, 929, 931, 932, 933, 934, 935, 936, 937, 945, 946, 947, + 948, 949, 950, 951, 952, 953, 954, 955, 956, 957, 958, 959, 960, 961, 962, + 963, 964, 965, 966, 967, 968, 969, 977, 978, 982, 8194, 8195, 8201, 8204, + 8205, 8206, 8207, 8211, 8212, 8216, 8217, 8218, 8220, 8221, 8222, 8224, 8225, + 8226, 8230, 8240, 8242, 8243, 8249, 8250, 8254, 8260, 8364, 8472, 8465, 8476, + 8482, 8501, 8592, 8593, 8594, 8595, 8596, 8629, 8656, 8657, 8658, 8659, 8660, + 8704, 8706, 8707, 8709, 8711, 8712, 8713, 8715, 8719, 8721, 8722, 8727, 8730, + 8733, 8734, 8736, 8743, 8744, 8745, 8746, 8747, 8756, 8764, 8773, 8776, 8800, + 8801, 8804, 8805, 8834, 8835, 8836, 8838, 8839, 8853, 8855, 8869, 8901, 8968, + 8969, 8970, 8971, 9001, 9002, 9674, 9824, 9827, 9829, 9830 }; + + NSString *string1 = [NSString stringWithCharacters:chars + length:sizeof(chars) / sizeof(unichar)]; + NSString *string2 = + @""&'<> ¡¢£¤¥" + "¦§¨©ª«¬­®¯°" + "±²³´µ¶·¸¹" + "º»¼½¾¿ÀÁ" + "ÂÃÄÅÆÇÈÉ" + "ÊËÌÍÎÏÐÑÒ" + "ÓÔÕÖ×ØÙÚ" + "ÛÜÝÞßàáâã" + "äåæçèéêëì" + "íîïðñòóôõ" + "ö÷øùúûüýþ" + "ÿŒœŠšŸƒˆ˜" + "ΑΒΓΔΕΖΗΘΙ" + "ΚΛΜΝΞΟΠΡΣΤ" + "ΥΦΧΨΩαβγδ" + "εζηθικλμνξ" + "οπρςστυφχψ" + "ωϑϒϖ   ‌‍" + "‎‏–—‘’‚“”" + "„†‡•…‰′″" + "‹›‾⁄€℘ℑℜ™" + "ℵ←↑→↓↔↵⇐⇑⇒" + "⇓⇔∀∂∃∅∇∈∉∋" + "∏∑−∗√∝∞∠∧∨" + "∩∪∫∴∼≅≈≠≡≤≥" + "⊂⊃⊄⊆⊇⊕⊗⊥⋅⌈" + "⌉⌊⌋⟨⟩◊♠♣♥" + "♦"; + + STAssertEqualObjects([string1 gtm_stringByEscapingForAsciiHTML], + string2, + @"HTML escaping failed"); + + STAssertEqualObjects([@"" gtm_stringByEscapingForAsciiHTML], + @"<this & that>", + @"HTML escaping failed"); + NSString *string = [NSString stringWithUTF8String:"パン・ド・カンパーニュ"]; + STAssertEqualObjects([string gtm_stringByEscapingForAsciiHTML], + @"パン・ド・カ" + "ンパーニュ", + @"HTML escaping failed"); + + // Mix in some right - to left + string = [NSString stringWithUTF8String:"abcا1ب<تdef&"]; + STAssertEqualObjects([string gtm_stringByEscapingForAsciiHTML], + @"abcا1ب<تdef&", + @"HTML escaping failed"); +} // stringByEscapingAsciiHTML + +- (void)testStringByUnescapingHTML { + NSString *string1 = + @""&'<> ¡¢£¤¥" + "¦§¨©ª«¬­®¯°" + "±²³´µ¶·¸¹" + "º»¼½¾¿ÀÁ" + "ÂÃÄÅÆÇÈÉ" + "ÊËÌÍÎÏÐÑÒ" + "ÓÔÕÖ×ØÙÚ" + "ÛÜÝÞßàáâã" + "äåæçèéêëì" + "íîïðñòóôõ" + "ö÷øùúûüýþ" + "ÿŒœŠšŸƒˆ˜" + "ΑΒΓΔΕΖΗΘΙ" + "ΚΛΜΝΞΟΠΡΣΤ" + "ΥΦΧΨΩαβγδ" + "εζηθικλμνξ" + "οπρςστυφχψ" + "ωϑϒϖ   ‌‍" + "‎‏–—‘’‚“”" + "„†‡•…‰′″" + "‹›‾⁄€℘ℑℜ™" + "ℵ←↑→↓↔↵⇐⇑⇒" + "⇓⇔∀∂∃∅∇∈∉∋" + "∏∑−∗√∝∞∠∧∨" + "∩∪∫∴∼≅≈≠≡≤≥" + "⊂⊃⊄⊆⊇⊕⊗⊥⋅⌈" + "⌉⌊⌋⟨⟩◊♠♣♥" + "♦"; + + unichar chars[] = + { 34, 38, 39, 60, 62, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, + 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, + 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, + 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, + 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, + 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, + 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 338, 339, 352, 353, 376, + 402, 710, 732, 913, 914, 915, 916, 917, 918, 919, 920, 921, 922, 923, 924, + 925, 926, 927, 928, 929, 931, 932, 933, 934, 935, 936, 937, 945, 946, 947, + 948, 949, 950, 951, 952, 953, 954, 955, 956, 957, 958, 959, 960, 961, 962, + 963, 964, 965, 966, 967, 968, 969, 977, 978, 982, 8194, 8195, 8201, 8204, + 8205, 8206, 8207, 8211, 8212, 8216, 8217, 8218, 8220, 8221, 8222, 8224, 8225, + 8226, 8230, 8240, 8242, 8243, 8249, 8250, 8254, 8260, 8364, 8472, 8465, 8476, + 8482, 8501, 8592, 8593, 8594, 8595, 8596, 8629, 8656, 8657, 8658, 8659, 8660, + 8704, 8706, 8707, 8709, 8711, 8712, 8713, 8715, 8719, 8721, 8722, 8727, 8730, + 8733, 8734, 8736, 8743, 8744, 8745, 8746, 8747, 8756, 8764, 8773, 8776, 8800, + 8801, 8804, 8805, 8834, 8835, 8836, 8838, 8839, 8853, 8855, 8869, 8901, 8968, + 8969, 8970, 8971, 9001, 9002, 9674, 9824, 9827, 9829, 9830 }; + + NSString *string2 = [NSString stringWithCharacters:chars + length:sizeof(chars) / sizeof(unichar)]; + STAssertEqualObjects([string1 gtm_stringByUnescapingFromHTML], + string2, + @"HTML unescaping failed"); + + STAssertEqualObjects([@"ABC" gtm_stringByUnescapingFromHTML], + @"ABC", @"HTML unescaping failed"); + + STAssertEqualObjects([@"" gtm_stringByUnescapingFromHTML], + @"", @"HTML unescaping failed"); + + STAssertEqualObjects([@"A&Bang;C" gtm_stringByUnescapingFromHTML], + @"A&Bang;C", @"HTML unescaping failed"); + + STAssertEqualObjects([@"A&Bang;C" gtm_stringByUnescapingFromHTML], + @"A&Bang;C", @"HTML unescaping failed"); + + STAssertEqualObjects([@"A&Bang;C" gtm_stringByUnescapingFromHTML], + @"A&Bang;C", @"HTML unescaping failed"); + + STAssertEqualObjects([@"AA;" gtm_stringByUnescapingFromHTML], + @"AA;", @"HTML unescaping failed"); + + STAssertEqualObjects([@"&" gtm_stringByUnescapingFromHTML], + @"&", @"HTML unescaping failed"); + + STAssertEqualObjects([@"&;" gtm_stringByUnescapingFromHTML], + @"&;", @"HTML unescaping failed"); + + STAssertEqualObjects([@"&x;" gtm_stringByUnescapingFromHTML], + @"&x;", @"HTML unescaping failed"); + + STAssertEqualObjects([@"&X;" gtm_stringByUnescapingFromHTML], + @"&X;", @"HTML unescaping failed"); + + STAssertEqualObjects([@";" gtm_stringByUnescapingFromHTML], + @";", @"HTML unescaping failed"); + + STAssertEqualObjects([@"<this & that>" gtm_stringByUnescapingFromHTML], + @"", @"HTML unescaping failed"); + + +} // testStringByUnescapingHTML + +- (void)testStringRoundtrippingEscapedHTML { + NSString *string = [NSString stringWithUTF8String:"This test ©™®๒०᠐٧"]; + STAssertEqualObjects(string, + [[string gtm_stringByEscapingForHTML] gtm_stringByUnescapingFromHTML], + @"HTML Roundtripping failed"); + string = [NSString stringWithUTF8String:"This test ©™®๒०᠐٧"]; + STAssertEqualObjects(string, + [[string gtm_stringByEscapingForAsciiHTML] gtm_stringByUnescapingFromHTML], + @"HTML Roundtripping failed"); +} +@end diff --git a/Foundation/GTMNSString+XML.h b/Foundation/GTMNSString+XML.h new file mode 100644 index 0000000..ed2d161 --- /dev/null +++ b/Foundation/GTMNSString+XML.h @@ -0,0 +1,39 @@ +// +// GTMNSString+XML.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 + +/// Utilities for NSStrings containing XML +@interface NSString (GTMNSStringXMLAdditions) + +/// Get a string where characters that need escaping for XML are escaped and invalid characters removed +// +/// This call escapes '&', '<, '>', '\'', '"' per the xml spec and removes all +/// invalid characters as defined by Section 2.2 of the xml spec. +/// +/// For obvious reasons this call is only safe once. +// +// Returns: +// Autoreleased NSString +// +- (NSString *)gtm_stringByEscapingForXML; + +// There is no stringByUnescapingFromXML because the XML parser will do this. +// The above api is here just incase you need to create XML yourself. + +@end diff --git a/Foundation/GTMNSString+XML.m b/Foundation/GTMNSString+XML.m new file mode 100644 index 0000000..edc82a0 --- /dev/null +++ b/Foundation/GTMNSString+XML.m @@ -0,0 +1,162 @@ +// +// GTMNSString+XML.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 "GTMNSString+XML.h" + +typedef enum { + kGMXMLCharModeEncodeQUOT = 0, + kGMXMLCharModeEncodeAMP = 1, + kGMXMLCharModeEncodeAPOS = 2, + kGMXMLCharModeEncodeLT = 3, + kGMXMLCharModeEncodeGT = 4, + kGMXMLCharModeValid = 99, + kGMXMLCharModeInvalid = 100, +} GMXMLCharMode; + +static NSString *gXMLEntityList[] = { + // this must match the above order + @""", + @"&", + @"'", + @"<", + @">", +}; + +FOUNDATION_STATIC_INLINE GMXMLCharMode XMLModeForUnichar(unichar c) { + + // Per XML spec Section 2.2 Characters + // ( http://www.w3.org/TR/REC-xml/#charsets ) + // + // Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | + // [#x10000-#x10FFFF] + + if (c <= 0xd7ff) { + if (c >= 0x20) { + switch (c) { + case 34: + return kGMXMLCharModeEncodeQUOT; + case 38: + return kGMXMLCharModeEncodeAMP; + case 39: + return kGMXMLCharModeEncodeAPOS; + case 60: + return kGMXMLCharModeEncodeLT; + case 62: + return kGMXMLCharModeEncodeGT; + default: + return kGMXMLCharModeValid; + } + } else { + if (c == '\n') + return kGMXMLCharModeValid; + if (c == '\r') + return kGMXMLCharModeValid; + if (c == '\t') + return kGMXMLCharModeValid; + return kGMXMLCharModeInvalid; + } + } + + if (c < 0xE000) + return kGMXMLCharModeInvalid; + + if (c <= 0xFFFD) + return kGMXMLCharModeValid; + + // unichar can't have the following values + // if (c < 0x10000) + // return kGMXMLCharModeInvalid; + // if (c <= 0x10FFFF) + // return kGMXMLCharModeValid; + + return kGMXMLCharModeInvalid; +} // XMLModeForUnichar + +@implementation NSString (GTMNSStringXMLAdditions) + +- (NSString *)gtm_stringByEscapingForXML { + NSMutableString *finalString = [NSMutableString string]; + int length = [self length]; + require_quiet(length != 0, cantConvertAnything); + + // see if we can just use the interal version + BOOL freeBuffer = NO; + unichar *buffer = (unichar*)CFStringGetCharactersPtr((CFStringRef)self); + if (!buffer) { + // nope, alloc buffer and fetch the chars ourselves + buffer = malloc(sizeof(unichar) * length); + if (!buffer) return nil; + freeBuffer = YES; + [self getCharacters:buffer]; + } + + unichar *goodRun = buffer; + int goodRunLength = 0; + + for (int i = 0; i < length; ++i) { + + GMXMLCharMode cMode = XMLModeForUnichar(buffer[i]); + + if (cMode == kGMXMLCharModeValid) { + // goes as is + goodRunLength += 1; + } else { + // it's something we have to encode or something invalid + + // start by adding what we already collected (if anything) + if (goodRunLength) { + CFStringRef goodRunString = + CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, + goodRun, goodRunLength, + kCFAllocatorNull); + require_action(goodRunString != NULL, cantCreateString, finalString = nil); + [finalString appendString:(NSString*)goodRunString]; + CFRelease(goodRunString); + goodRunLength = 0; + } + + // if it wasn't invalid, add the encoded version + if (cMode != kGMXMLCharModeInvalid) { + // add this encoded + [finalString appendString:gXMLEntityList[cMode]]; + } + + // update goodRun to point to the next unichar + goodRun = buffer + i + 1; + } + } + + // anything left to add? + if (goodRunLength) { + CFStringRef goodRunString = + CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, + goodRun, goodRunLength, + kCFAllocatorNull); + require_action(goodRunString != NULL, cantCreateString2, finalString = nil); + [finalString appendString:(NSString*)goodRunString]; + CFRelease(goodRunString); + } +cantCreateString: +cantCreateString2: + if (freeBuffer) + free(buffer); +cantConvertAnything: + return finalString; +} // gtm_stringByEscapingForXML + +@end diff --git a/Foundation/GTMNSString+XMLTest.m b/Foundation/GTMNSString+XMLTest.m new file mode 100644 index 0000000..c53ce64 --- /dev/null +++ b/Foundation/GTMNSString+XMLTest.m @@ -0,0 +1,44 @@ +// +// NSString+XMLTest.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 +#import "GTMNSString+XML.h" + + +@interface GTMNSString_XMLTest : SenTestCase +@end + +@implementation GTMNSString_XMLTest + +- (void)testStringByEscapingForXML { + unichar chars[] = + { 0, 'z', 1, 'z', 4, 'z', 5, 'z', 34, 'z', 38, 'z', 39, 'z', + 60, 'z', 62, 'z', ' ', 'z', 0xd800, 'z', 0xDFFF, 'z', 0xFFFE, + 0xFFFF, 'z' }; + + NSString *string1 = [NSString stringWithCharacters:chars + length:sizeof(chars) / sizeof(unichar)]; + NSString *string2 = @"zzzz"z&z'z<z>z zzzz"; + + STAssertEqualObjects([string1 gtm_stringByEscapingForXML], + string2, + @"Escaped for XML failed"); +} + +@end diff --git a/Foundation/GTMObjectSingleton.h b/Foundation/GTMObjectSingleton.h new file mode 100644 index 0000000..ff03ea0 --- /dev/null +++ b/Foundation/GTMObjectSingleton.h @@ -0,0 +1,69 @@ +// +// GTMObjectSingleton.h +// Macro to implement methods for a singleton +// +// Copyright 2005-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. +// + +/// This macro implements the various methods needed to make a safe singleton. +// +/// This Singleton pattern was taken from: +/// http://developer.apple.com/documentation/Cocoa/Conceptual/CocoaFundamentals/CocoaObjects/chapter_3_section_10.html +/// +/// Sample usage: +/// +/// SINGLETON_BOILERPLATE(GMSomeUsefulManager, sharedSomeUsefulManager) +/// (with no trailing semicolon) +/// +#define GTMOBJECT_SINGLETON_BOILERPLATE(_object_name_, _shared_obj_name_) \ +static _object_name_ *z##_shared_obj_name_ = nil; \ ++ (_object_name_ *)_shared_obj_name_ { \ + @synchronized(self) { \ + if (z##_shared_obj_name_ == nil) { \ + /* Note that 'self' may not be the same as _object_name_ */ \ + /* first assignment done in allocWithZone but we must reassign in case init fails */ \ + z##_shared_obj_name_ = [[self alloc] init]; \ + GTMDebugAssert((z##_shared_obj_name_ != nil), @"didn't catch singleton allocation"); \ + } \ + } \ + return z##_shared_obj_name_; \ +} \ ++ (id)allocWithZone:(NSZone *)zone { \ + @synchronized(self) { \ + if (z##_shared_obj_name_ == nil) { \ + z##_shared_obj_name_ = [super allocWithZone:zone]; \ + return z##_shared_obj_name_; \ + } \ + } \ + \ + /* We can't return the shared instance, because it's been init'd */ \ + GTMDebugAssert(NO, @"use the singleton API, not alloc+init"); \ + return nil; \ +} \ +- (id)retain { \ + return self; \ +} \ +- (unsigned int)retainCount { \ + return UINT_MAX; \ +} \ +- (void)release { \ +} \ +- (id)autorelease { \ + return self; \ +} \ +- (id)copyWithZone:(NSZone *)zone { \ + return self; \ +} \ + diff --git a/Foundation/GTMSystemVersion.h b/Foundation/GTMSystemVersion.h new file mode 100644 index 0000000..c9f9d75 --- /dev/null +++ b/Foundation/GTMSystemVersion.h @@ -0,0 +1,45 @@ +// +// GTMSystemVersion.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 + +/// A class for getting information about what system we are running on +@interface GTMSystemVersion : NSObject + +/// Returns YES if running on 10.3, NO otherwise. ++ (BOOL)isPanther; + +/// Returns YES if running on 10.4, NO otherwise. ++ (BOOL)isTiger; + +/// Returns YES if running on 10.5, NO otherwise. ++ (BOOL)isLeopard; + +/// Returns a YES/NO if the system is 10.3 or better ++ (BOOL)isPantherOrGreater; + +/// Returns a YES/NO if the system is 10.4 or better ++ (BOOL)isTigerOrGreater; + +/// Returns a YES/NO if the system is 10.5 or better ++ (BOOL)isLeopardOrGreater; + +/// Returns the current system version major.minor.bugFix ++ (void)getMajor:(long*)major minor:(long*)minor bugFix:(long*)bugFix; + +@end diff --git a/Foundation/GTMSystemVersion.m b/Foundation/GTMSystemVersion.m new file mode 100644 index 0000000..d9b1923 --- /dev/null +++ b/Foundation/GTMSystemVersion.m @@ -0,0 +1,104 @@ +// +// GTMSystemVersion.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 "GTMSystemVersion.h" +#import +#import + +@implementation GTMSystemVersion + ++ (BOOL)isPanther { + long major, minor; + [self getMajor:&major minor:&minor bugFix:nil]; + return major == 10 && minor == 3; +} + ++ (BOOL)isTiger { + long major, minor; + [self getMajor:&major minor:&minor bugFix:nil]; + return major == 10 && minor == 4; +} + ++ (BOOL)isLeopard { + long major, minor; + [self getMajor:&major minor:&minor bugFix:nil]; + return major == 10 && minor == 5; +} + ++ (BOOL)isPantherOrGreater { + long major, minor; + [self getMajor:&major minor:&minor bugFix:nil]; + return (major > 10) || (major == 10 && minor >= 3); +} + ++ (BOOL)isTigerOrGreater { + long major, minor; + [self getMajor:&major minor:&minor bugFix:nil]; + return (major > 10) || (major == 10 && minor >= 4); +} + ++ (BOOL)isLeopardOrGreater { + long major, minor; + [self getMajor:&major minor:&minor bugFix:nil]; + return (major > 10) || (major == 10 && minor >= 5); +} + ++ (void)getMajor:(long*)major minor:(long*)minor bugFix:(long*)bugFix { + long binaryCodedDec; + + if (major) { + require_noerr(Gestalt(gestaltSystemVersionMajor, major), failedGestalt); + } + if (minor) { + require_noerr(Gestalt(gestaltSystemVersionMinor, minor), failedGestalt); + } + if (bugFix) { + require_noerr(Gestalt(gestaltSystemVersionBugFix, bugFix), failedGestalt); + } + return; + +failedGestalt: + // gestaltSystemVersionMajor et al are only on 10.4 and above, so they + // could fail if we have this code on 10.3. + if (Gestalt(gestaltSystemVersion, &binaryCodedDec)) { + // not much else we can do... + if (major) *major = 0; + if (minor) *minor = 0; + if (bugFix) *bugFix = 0; + return; + } + + // Note that this code will return x.9.9 for any system rev parts that are + // greater than 9 (ie 10.10.10 will be 10.9.9. This shouldn't ever be a + // problem as the code above takes care of this for any system above 10.4. + if (major) { + int msb = (binaryCodedDec & 0x0000F000L) >> 12; + msb *= 10; + int lsb = (binaryCodedDec & 0x00000F00L) >> 8; + *major = msb + lsb; + } + if (minor) { + *minor = (binaryCodedDec & 0x000000F0L) >> 4; + } + if (bugFix) { + *bugFix = (binaryCodedDec & 0x0000000FL); + } + +} + +@end diff --git a/Foundation/GTMSystemVersionTest.m b/Foundation/GTMSystemVersionTest.m new file mode 100644 index 0000000..57b22aa --- /dev/null +++ b/Foundation/GTMSystemVersionTest.m @@ -0,0 +1,47 @@ +// +// GTMSystemVersionTest.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 +#import "GTMSystemVersion.h" + +@interface GTMSystemVersionTest : SenTestCase +@end + +@implementation GTMSystemVersionTest +- (void)testBasics { + long major; + long minor; + long bugFix; + + [GTMSystemVersion getMajor:&major minor:&minor bugFix:&bugFix]; + STAssertTrue(major >= 10 && minor >= 3 && bugFix >= 0, nil); + STAssertTrue([GTMSystemVersion isPantherOrGreater], nil); + if (minor > 3) { + STAssertTrue([GTMSystemVersion isTigerOrGreater], nil); + } else { + STAssertFalse([GTMSystemVersion isTigerOrGreater], nil); + } + if (minor > 4) { + STAssertTrue([GTMSystemVersion isLeopardOrGreater], nil); + } else { + STAssertFalse([GTMSystemVersion isLeopardOrGreater], nil); + } + [GTMSystemVersion getMajor:nil minor:nil bugFix:nil]; +} + +@end diff --git a/GTM-Info.plist b/GTM-Info.plist new file mode 100644 index 0000000..41b915d --- /dev/null +++ b/GTM-Info.plist @@ -0,0 +1,20 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + com.google.GTM + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + FMWK + CFBundleSignature + ???? + CFBundleVersion + 1.0 + + diff --git a/GTM.xcodeproj/project.pbxproj b/GTM.xcodeproj/project.pbxproj new file mode 100644 index 0000000..9314398 --- /dev/null +++ b/GTM.xcodeproj/project.pbxproj @@ -0,0 +1,787 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 42; + objects = { + +/* Begin PBXAggregateTarget section */ + F472042B0D33BEAF00E9F378 /* All UnitTests */ = { + isa = PBXAggregateTarget; + buildConfigurationList = F47204340D33BEDF00E9F378 /* Build configuration list for PBXAggregateTarget "All UnitTests" */; + buildPhases = ( + ); + dependencies = ( + F472042F0D33BEB500E9F378 /* PBXTargetDependency */, + F472042D0D33BEB500E9F378 /* PBXTargetDependency */, + ); + name = "All UnitTests"; + productName = "All UnitTests"; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + F428FF030D48E55E00382ED1 /* GTMNSBezierPath+CGPath.h in Headers */ = {isa = PBXBuildFile; fileRef = F428FEFF0D48E55E00382ED1 /* GTMNSBezierPath+CGPath.h */; }; + F428FF040D48E55E00382ED1 /* GTMNSBezierPath+CGPath.m in Sources */ = {isa = PBXBuildFile; fileRef = F428FF000D48E55E00382ED1 /* GTMNSBezierPath+CGPath.m */; }; + F428FF090D48E57300382ED1 /* GTMNSBezierPath+CGPathTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F428FF010D48E55E00382ED1 /* GTMNSBezierPath+CGPathTest.m */; }; + F428FF0A0D48E57300382ED1 /* GTMNSBezierPath+CGPathTest.tif in Resources */ = {isa = PBXBuildFile; fileRef = F428FF020D48E55E00382ED1 /* GTMNSBezierPath+CGPathTest.tif */; }; + F42E082E0D19991400D5DDE0 /* GTMGeometryUtilsTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F48FE2800D198D0E009257D2 /* GTMGeometryUtilsTest.m */; }; + F42E082F0D19991400D5DDE0 /* GTMNSBezierPath+RoundRectTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F48FE2830D198D0E009257D2 /* GTMNSBezierPath+RoundRectTest.m */; }; + F42E08300D19991400D5DDE0 /* GTMNSBezierPath+RoundRectTest.tif in Resources */ = {isa = PBXBuildFile; fileRef = F48FE2840D198D0E009257D2 /* GTMNSBezierPath+RoundRectTest.tif */; }; + F42E08330D19992100D5DDE0 /* GTMNSString+HTMLTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F48FE2900D198D24009257D2 /* GTMNSString+HTMLTest.m */; }; + F42E08340D19992100D5DDE0 /* GTMSystemVersionTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F48FE2E10D198E4C009257D2 /* GTMSystemVersionTest.m */; }; + F42E08610D199A2B00D5DDE0 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7B1FEA5585E11CA2CBB /* Cocoa.framework */; }; + F42E08640D199A2F00D5DDE0 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0867D69BFE84028FC02AAC07 /* Foundation.framework */; }; + F42E087F0D199AB400D5DDE0 /* GTM.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F42E086D0D199A5B00D5DDE0 /* GTM.framework */; }; + F42E08800D199AB500D5DDE0 /* GTM.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F42E086D0D199A5B00D5DDE0 /* GTM.framework */; }; + F42E089C0D199B1800D5DDE0 /* SenTestingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F42E089B0D199B1800D5DDE0 /* SenTestingKit.framework */; }; + F42E089D0D199B1800D5DDE0 /* SenTestingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F42E089B0D199B1800D5DDE0 /* SenTestingKit.framework */; }; + F42E09450D199BA400D5DDE0 /* GTMNSObject+UnitTesting.m in Sources */ = {isa = PBXBuildFile; fileRef = F48FE29C0D198D36009257D2 /* GTMNSObject+UnitTesting.m */; }; + F42E09460D199BA400D5DDE0 /* GTMNSView+UnitTesting.m in Sources */ = {isa = PBXBuildFile; fileRef = F48FE29E0D198D36009257D2 /* GTMNSView+UnitTesting.m */; }; + F42E09490D199BBF00D5DDE0 /* GTMDelegatingTableColumn.h in Headers */ = {isa = PBXBuildFile; fileRef = F48FE27C0D198D0E009257D2 /* GTMDelegatingTableColumn.h */; }; + F42E094A0D199BBF00D5DDE0 /* GTMDelegatingTableColumn.m in Sources */ = {isa = PBXBuildFile; fileRef = F48FE27D0D198D0E009257D2 /* GTMDelegatingTableColumn.m */; }; + F42E094B0D199BBF00D5DDE0 /* GTMGarbageCollection.h in Headers */ = {isa = PBXBuildFile; fileRef = F48FE28D0D198D24009257D2 /* GTMGarbageCollection.h */; }; + F42E094C0D199BBF00D5DDE0 /* GTMGeometryUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = F48FE27E0D198D0E009257D2 /* GTMGeometryUtils.h */; }; + F42E094D0D199BBF00D5DDE0 /* GTMGeometryUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = F48FE27F0D198D0E009257D2 /* GTMGeometryUtils.m */; }; + F42E094F0D199BBF00D5DDE0 /* GTMNSBezierPath+RoundRect.h in Headers */ = {isa = PBXBuildFile; fileRef = F48FE2810D198D0E009257D2 /* GTMNSBezierPath+RoundRect.h */; }; + F42E09500D199BBF00D5DDE0 /* GTMNSBezierPath+RoundRect.m in Sources */ = {isa = PBXBuildFile; fileRef = F48FE2820D198D0E009257D2 /* GTMNSBezierPath+RoundRect.m */; }; + F42E09510D199BBF00D5DDE0 /* GTMNSString+HTML.h in Headers */ = {isa = PBXBuildFile; fileRef = F48FE28E0D198D24009257D2 /* GTMNSString+HTML.h */; }; + F42E09520D199BBF00D5DDE0 /* GTMNSString+HTML.m in Sources */ = {isa = PBXBuildFile; fileRef = F48FE28F0D198D24009257D2 /* GTMNSString+HTML.m */; }; + F42E09530D199BBF00D5DDE0 /* GTMObjectSingleton.h in Headers */ = {isa = PBXBuildFile; fileRef = F48FE2910D198D24009257D2 /* GTMObjectSingleton.h */; }; + F42E09540D199BBF00D5DDE0 /* GTMSystemVersion.h in Headers */ = {isa = PBXBuildFile; fileRef = F48FE2920D198D24009257D2 /* GTMSystemVersion.h */; }; + F42E09550D199BBF00D5DDE0 /* GTMSystemVersion.m in Sources */ = {isa = PBXBuildFile; fileRef = F48FE2930D198D24009257D2 /* GTMSystemVersion.m */; }; + F42E095E0D199BD600D5DDE0 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7B1FEA5585E11CA2CBB /* Cocoa.framework */; }; + F42E09AA0D19A5E300D5DDE0 /* GTMNSWorkspace+Theme.h in Headers */ = {isa = PBXBuildFile; fileRef = F42E09A80D19A5E300D5DDE0 /* GTMNSWorkspace+Theme.h */; }; + F42E09AB0D19A5E300D5DDE0 /* GTMNSWorkspace+Theme.m in Sources */ = {isa = PBXBuildFile; fileRef = F42E09A90D19A5E300D5DDE0 /* GTMNSWorkspace+Theme.m */; }; + F42E09AE0D19A62F00D5DDE0 /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F42E09AD0D19A62F00D5DDE0 /* Carbon.framework */; }; + F42E0B0A0D19A6FB00D5DDE0 /* GTMNSWorkspace+ThemeTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F42E0B090D19A6FB00D5DDE0 /* GTMNSWorkspace+ThemeTest.m */; }; + F43DCDCD0D4796C600959A62 /* GTMLoginItems.h in Headers */ = {isa = PBXBuildFile; fileRef = F43DCDCB0D4796C600959A62 /* GTMLoginItems.h */; }; + F43DCDCE0D4796C600959A62 /* GTMLoginItems.m in Sources */ = {isa = PBXBuildFile; fileRef = F43DCDCC0D4796C600959A62 /* GTMLoginItems.m */; }; + F43DCEC70D47BEA000959A62 /* GTMLoginItemsTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F43DCEC60D47BEA000959A62 /* GTMLoginItemsTest.m */; }; + F43E447A0D4918B20041161F /* GTMLinearRGBShading.h in Headers */ = {isa = PBXBuildFile; fileRef = F43E44770D4918B20041161F /* GTMLinearRGBShading.h */; }; + F43E447B0D4918B20041161F /* GTMLinearRGBShading.m in Sources */ = {isa = PBXBuildFile; fileRef = F43E44780D4918B20041161F /* GTMLinearRGBShading.m */; }; + F43E447F0D4918BC0041161F /* GTMLinearRGBShadingTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F43E44790D4918B20041161F /* GTMLinearRGBShadingTest.m */; }; + F43E4C280D4E361D0041161F /* GTMNSString+XML.h in Headers */ = {isa = PBXBuildFile; fileRef = F43E4C250D4E361D0041161F /* GTMNSString+XML.h */; }; + F43E4C290D4E361D0041161F /* GTMNSString+XML.m in Sources */ = {isa = PBXBuildFile; fileRef = F43E4C260D4E361D0041161F /* GTMNSString+XML.m */; }; + F43E4C2D0D4E36230041161F /* GTMNSString+XMLTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F43E4C270D4E361D0041161F /* GTMNSString+XMLTest.m */; }; + F43E4DD90D4E56320041161F /* GTMNSEnumerator+Filter.h in Headers */ = {isa = PBXBuildFile; fileRef = F43E4DD60D4E56320041161F /* GTMNSEnumerator+Filter.h */; }; + F43E4DDA0D4E56320041161F /* GTMNSEnumerator+Filter.m in Sources */ = {isa = PBXBuildFile; fileRef = F43E4DD70D4E56320041161F /* GTMNSEnumerator+Filter.m */; }; + F43E4DDE0D4E56380041161F /* GTMNSEnumerator+FilterTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F43E4DD80D4E56320041161F /* GTMNSEnumerator+FilterTest.m */; }; + F43E4E610D4E5EC90041161F /* GTMNSData+zlib.h in Headers */ = {isa = PBXBuildFile; fileRef = F43E4E5E0D4E5EC90041161F /* GTMNSData+zlib.h */; }; + F43E4E620D4E5EC90041161F /* GTMNSData+zlib.m in Sources */ = {isa = PBXBuildFile; fileRef = F43E4E5F0D4E5EC90041161F /* GTMNSData+zlib.m */; }; + F43E4E660D4E5ED40041161F /* GTMNSData+zlibTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F43E4E600D4E5EC90041161F /* GTMNSData+zlibTest.m */; }; + F43E4F6D0D4E60C50041161F /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = F43E4F6C0D4E60C50041161F /* libz.dylib */; }; + F47F1C120D490BC000925B8F /* GTMNSBezierPath+Shading.h in Headers */ = {isa = PBXBuildFile; fileRef = F47F1C0D0D490BC000925B8F /* GTMNSBezierPath+Shading.h */; }; + F47F1C130D490BC000925B8F /* GTMNSBezierPath+Shading.m in Sources */ = {isa = PBXBuildFile; fileRef = F47F1C0E0D490BC000925B8F /* GTMNSBezierPath+Shading.m */; }; + F47F1C190D490BD200925B8F /* GTMNSBezierPath+ShadingTest.10.4.tif in Resources */ = {isa = PBXBuildFile; fileRef = F47F1C0F0D490BC000925B8F /* GTMNSBezierPath+ShadingTest.10.4.tif */; }; + F47F1C1A0D490BD200925B8F /* GTMNSBezierPath+ShadingTest.10.5.tif in Resources */ = {isa = PBXBuildFile; fileRef = F47F1C100D490BC000925B8F /* GTMNSBezierPath+ShadingTest.10.5.tif */; }; + F47F1C1B0D490BD200925B8F /* GTMNSBezierPath+ShadingTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F47F1C110D490BC000925B8F /* GTMNSBezierPath+ShadingTest.m */; }; + F47F1C750D490E5C00925B8F /* GTMShading.h in Headers */ = {isa = PBXBuildFile; fileRef = F47F1C740D490E5C00925B8F /* GTMShading.h */; }; + F47F1CAF0D4910FD00925B8F /* GTMNSColor+Theme.h in Headers */ = {isa = PBXBuildFile; fileRef = F47F1CAC0D4910FD00925B8F /* GTMNSColor+Theme.h */; }; + F47F1CB00D4910FD00925B8F /* GTMNSColor+Theme.m in Sources */ = {isa = PBXBuildFile; fileRef = F47F1CAD0D4910FD00925B8F /* GTMNSColor+Theme.m */; }; + F47F1CB60D49110900925B8F /* GTMNSColor+ThemeTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F47F1CAE0D4910FD00925B8F /* GTMNSColor+ThemeTest.m */; }; + F47F1D300D4914AD00925B8F /* GTMCalculatedRange.h in Headers */ = {isa = PBXBuildFile; fileRef = F47F1D2D0D4914AD00925B8F /* GTMCalculatedRange.h */; }; + F47F1D310D4914AD00925B8F /* GTMCalculatedRange.m in Sources */ = {isa = PBXBuildFile; fileRef = F47F1D2E0D4914AD00925B8F /* GTMCalculatedRange.m */; }; + F47F1D350D4914B600925B8F /* GTMCalculatedRangeTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F47F1D2F0D4914AD00925B8F /* GTMCalculatedRangeTest.m */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + F42E08760D199A9B00D5DDE0 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0867D690FE84028FC02AAC07 /* Project object */; + proxyType = 1; + remoteGlobalIDString = F42E086C0D199A5B00D5DDE0; + remoteInfo = GTM; + }; + F42E08780D199AA600D5DDE0 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0867D690FE84028FC02AAC07 /* Project object */; + proxyType = 1; + remoteGlobalIDString = F42E086C0D199A5B00D5DDE0; + remoteInfo = GTM; + }; + F472042C0D33BEB500E9F378 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0867D690FE84028FC02AAC07 /* Project object */; + proxyType = 1; + remoteGlobalIDString = F48FE2630D198C1E009257D2; + remoteInfo = "UnitTest - AppKit"; + }; + F472042E0D33BEB500E9F378 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0867D690FE84028FC02AAC07 /* Project object */; + proxyType = 1; + remoteGlobalIDString = F42E08110D19987200D5DDE0; + remoteInfo = "UnitTest - Foundation"; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 0867D69BFE84028FC02AAC07 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = ""; }; + 0867D6A5FE840307C02AAC07 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = ""; }; + 1058C7B1FEA5585E11CA2CBB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = /System/Library/Frameworks/Cocoa.framework; sourceTree = ""; }; + 32DBCF5E0370ADEE00C91783 /* GTM_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTM_Prefix.pch; sourceTree = ""; }; + F428FEFF0D48E55E00382ED1 /* GTMNSBezierPath+CGPath.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSBezierPath+CGPath.h"; sourceTree = ""; }; + F428FF000D48E55E00382ED1 /* GTMNSBezierPath+CGPath.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSBezierPath+CGPath.m"; sourceTree = ""; }; + F428FF010D48E55E00382ED1 /* GTMNSBezierPath+CGPathTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSBezierPath+CGPathTest.m"; sourceTree = ""; }; + F428FF020D48E55E00382ED1 /* GTMNSBezierPath+CGPathTest.tif */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = "GTMNSBezierPath+CGPathTest.tif"; sourceTree = ""; }; + F42E08210D19987200D5DDE0 /* UnitTest - Foundation.octest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "UnitTest - Foundation.octest"; sourceTree = BUILT_PRODUCTS_DIR; }; + F42E086D0D199A5B00D5DDE0 /* GTM.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = GTM.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + F42E086E0D199A5B00D5DDE0 /* GTM-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = "GTM-Info.plist"; sourceTree = ""; }; + F42E089B0D199B1800D5DDE0 /* SenTestingKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SenTestingKit.framework; path = /System/Library/Frameworks/SenTestingKit.framework; sourceTree = ""; }; + F42E09A80D19A5E300D5DDE0 /* GTMNSWorkspace+Theme.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSWorkspace+Theme.h"; sourceTree = ""; }; + F42E09A90D19A5E300D5DDE0 /* GTMNSWorkspace+Theme.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSWorkspace+Theme.m"; sourceTree = ""; }; + F42E09AD0D19A62F00D5DDE0 /* Carbon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = /System/Library/Frameworks/Carbon.framework; sourceTree = ""; }; + F42E0B090D19A6FB00D5DDE0 /* GTMNSWorkspace+ThemeTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSWorkspace+ThemeTest.m"; sourceTree = ""; }; + F43DCDCB0D4796C600959A62 /* GTMLoginItems.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMLoginItems.h; sourceTree = ""; }; + F43DCDCC0D4796C600959A62 /* GTMLoginItems.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMLoginItems.m; sourceTree = ""; }; + F43DCEC60D47BEA000959A62 /* GTMLoginItemsTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMLoginItemsTest.m; sourceTree = ""; }; + F43E44770D4918B20041161F /* GTMLinearRGBShading.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMLinearRGBShading.h; sourceTree = ""; }; + F43E44780D4918B20041161F /* GTMLinearRGBShading.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMLinearRGBShading.m; sourceTree = ""; }; + F43E44790D4918B20041161F /* GTMLinearRGBShadingTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMLinearRGBShadingTest.m; sourceTree = ""; }; + F43E4C250D4E361D0041161F /* GTMNSString+XML.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSString+XML.h"; sourceTree = ""; }; + F43E4C260D4E361D0041161F /* GTMNSString+XML.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSString+XML.m"; sourceTree = ""; }; + F43E4C270D4E361D0041161F /* GTMNSString+XMLTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSString+XMLTest.m"; sourceTree = ""; }; + F43E4DD60D4E56320041161F /* GTMNSEnumerator+Filter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSEnumerator+Filter.h"; sourceTree = ""; }; + F43E4DD70D4E56320041161F /* GTMNSEnumerator+Filter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSEnumerator+Filter.m"; sourceTree = ""; }; + F43E4DD80D4E56320041161F /* GTMNSEnumerator+FilterTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSEnumerator+FilterTest.m"; sourceTree = ""; }; + F43E4E5E0D4E5EC90041161F /* GTMNSData+zlib.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSData+zlib.h"; sourceTree = ""; }; + F43E4E5F0D4E5EC90041161F /* GTMNSData+zlib.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSData+zlib.m"; sourceTree = ""; }; + F43E4E600D4E5EC90041161F /* GTMNSData+zlibTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSData+zlibTest.m"; sourceTree = ""; }; + F43E4F6C0D4E60C50041161F /* libz.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.dylib; path = /usr/lib/libz.dylib; sourceTree = ""; }; + F47F1C0D0D490BC000925B8F /* GTMNSBezierPath+Shading.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSBezierPath+Shading.h"; sourceTree = ""; }; + F47F1C0E0D490BC000925B8F /* GTMNSBezierPath+Shading.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSBezierPath+Shading.m"; sourceTree = ""; }; + F47F1C0F0D490BC000925B8F /* GTMNSBezierPath+ShadingTest.10.4.tif */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = "GTMNSBezierPath+ShadingTest.10.4.tif"; sourceTree = ""; }; + F47F1C100D490BC000925B8F /* GTMNSBezierPath+ShadingTest.10.5.tif */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = "GTMNSBezierPath+ShadingTest.10.5.tif"; sourceTree = ""; }; + F47F1C110D490BC000925B8F /* GTMNSBezierPath+ShadingTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSBezierPath+ShadingTest.m"; sourceTree = ""; }; + F47F1C740D490E5C00925B8F /* GTMShading.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMShading.h; sourceTree = ""; }; + F47F1CAC0D4910FD00925B8F /* GTMNSColor+Theme.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSColor+Theme.h"; sourceTree = ""; }; + F47F1CAD0D4910FD00925B8F /* GTMNSColor+Theme.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSColor+Theme.m"; sourceTree = ""; }; + F47F1CAE0D4910FD00925B8F /* GTMNSColor+ThemeTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSColor+ThemeTest.m"; sourceTree = ""; }; + F47F1D2D0D4914AD00925B8F /* GTMCalculatedRange.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMCalculatedRange.h; sourceTree = ""; }; + F47F1D2E0D4914AD00925B8F /* GTMCalculatedRange.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMCalculatedRange.m; sourceTree = ""; }; + F47F1D2F0D4914AD00925B8F /* GTMCalculatedRangeTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMCalculatedRangeTest.m; sourceTree = ""; }; + F48FE2410D197F9A009257D2 /* DebugTigerOrLater.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = DebugTigerOrLater.xcconfig; sourceTree = ""; }; + F48FE2420D197F9A009257D2 /* DebugUnittest.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = DebugUnittest.xcconfig; sourceTree = ""; }; + F48FE2430D197F9A009257D2 /* LoadableBundle.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = LoadableBundle.xcconfig; sourceTree = ""; }; + F48FE2440D197F9A009257D2 /* ReleaseTigerOrLater.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = ReleaseTigerOrLater.xcconfig; sourceTree = ""; }; + F48FE2450D197F9A009257D2 /* ReleaseUnittest.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = ReleaseUnittest.xcconfig; sourceTree = ""; }; + F48FE2460D197F9A009257D2 /* SharedLibrary.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = SharedLibrary.xcconfig; sourceTree = ""; }; + F48FE2470D197F9A009257D2 /* StaticLibrary.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = StaticLibrary.xcconfig; sourceTree = ""; }; + F48FE2640D198C1E009257D2 /* UnitTest - AppKit.octest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "UnitTest - AppKit.octest"; sourceTree = BUILT_PRODUCTS_DIR; }; + F48FE26E0D198CAD009257D2 /* UnitTest-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = "UnitTest-Info.plist"; sourceTree = ""; }; + F48FE27C0D198D0E009257D2 /* GTMDelegatingTableColumn.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMDelegatingTableColumn.h; sourceTree = ""; }; + F48FE27D0D198D0E009257D2 /* GTMDelegatingTableColumn.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMDelegatingTableColumn.m; sourceTree = ""; }; + F48FE27E0D198D0E009257D2 /* GTMGeometryUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMGeometryUtils.h; sourceTree = ""; }; + F48FE27F0D198D0E009257D2 /* GTMGeometryUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMGeometryUtils.m; sourceTree = ""; }; + F48FE2800D198D0E009257D2 /* GTMGeometryUtilsTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMGeometryUtilsTest.m; sourceTree = ""; }; + F48FE2810D198D0E009257D2 /* GTMNSBezierPath+RoundRect.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSBezierPath+RoundRect.h"; sourceTree = ""; }; + F48FE2820D198D0E009257D2 /* GTMNSBezierPath+RoundRect.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSBezierPath+RoundRect.m"; sourceTree = ""; }; + F48FE2830D198D0E009257D2 /* GTMNSBezierPath+RoundRectTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSBezierPath+RoundRectTest.m"; sourceTree = ""; }; + F48FE2840D198D0E009257D2 /* GTMNSBezierPath+RoundRectTest.tif */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = "GTMNSBezierPath+RoundRectTest.tif"; sourceTree = ""; }; + F48FE28D0D198D24009257D2 /* GTMGarbageCollection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMGarbageCollection.h; sourceTree = ""; }; + F48FE28E0D198D24009257D2 /* GTMNSString+HTML.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSString+HTML.h"; sourceTree = ""; }; + F48FE28F0D198D24009257D2 /* GTMNSString+HTML.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSString+HTML.m"; sourceTree = ""; }; + F48FE2900D198D24009257D2 /* GTMNSString+HTMLTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSString+HTMLTest.m"; sourceTree = ""; }; + F48FE2910D198D24009257D2 /* GTMObjectSingleton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMObjectSingleton.h; sourceTree = ""; }; + F48FE2920D198D24009257D2 /* GTMSystemVersion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMSystemVersion.h; sourceTree = ""; }; + F48FE2930D198D24009257D2 /* GTMSystemVersion.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMSystemVersion.m; sourceTree = ""; }; + F48FE29B0D198D36009257D2 /* GTMNSObject+UnitTesting.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSObject+UnitTesting.h"; sourceTree = ""; }; + F48FE29C0D198D36009257D2 /* GTMNSObject+UnitTesting.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSObject+UnitTesting.m"; sourceTree = ""; }; + F48FE29D0D198D36009257D2 /* GTMNSView+UnitTesting.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSView+UnitTesting.h"; sourceTree = ""; }; + F48FE29E0D198D36009257D2 /* GTMNSView+UnitTesting.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSView+UnitTesting.m"; sourceTree = ""; }; + F48FE29F0D198D36009257D2 /* GTMSenTestCase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMSenTestCase.h; sourceTree = ""; }; + F48FE2E10D198E4C009257D2 /* GTMSystemVersionTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMSystemVersionTest.m; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + F42E081C0D19987200D5DDE0 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + F42E08640D199A2F00D5DDE0 /* Foundation.framework in Frameworks */, + F42E08800D199AB500D5DDE0 /* GTM.framework in Frameworks */, + F42E089D0D199B1800D5DDE0 /* SenTestingKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F42E086B0D199A5B00D5DDE0 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + F42E095E0D199BD600D5DDE0 /* Cocoa.framework in Frameworks */, + F42E09AE0D19A62F00D5DDE0 /* Carbon.framework in Frameworks */, + F43E4F6D0D4E60C50041161F /* libz.dylib in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F48FE2610D198C1E009257D2 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + F42E08610D199A2B00D5DDE0 /* Cocoa.framework in Frameworks */, + F42E087F0D199AB400D5DDE0 /* GTM.framework in Frameworks */, + F42E089C0D199B1800D5DDE0 /* SenTestingKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 034768DFFF38A50411DB9C8B /* Products */ = { + isa = PBXGroup; + children = ( + F48FE2640D198C1E009257D2 /* UnitTest - AppKit.octest */, + F42E08210D19987200D5DDE0 /* UnitTest - Foundation.octest */, + F42E086D0D199A5B00D5DDE0 /* GTM.framework */, + ); + name = Products; + sourceTree = ""; + }; + 0867D691FE84028FC02AAC07 /* GTM */ = { + isa = PBXGroup; + children = ( + 32DBCF5E0370ADEE00C91783 /* GTM_Prefix.pch */, + F48FE26F0D198CBA009257D2 /* AppKit */, + F48FE2720D198CCE009257D2 /* Foundation */, + F48FE2770D198CEA009257D2 /* UnitTesting */, + F48FE23E0D197F70009257D2 /* XcodeConfig */, + 0867D69AFE84028FC02AAC07 /* External Frameworks and Libraries */, + F43E4F6C0D4E60C50041161F /* libz.dylib */, + 034768DFFF38A50411DB9C8B /* Products */, + F42E086E0D199A5B00D5DDE0 /* GTM-Info.plist */, + F48FE26E0D198CAD009257D2 /* UnitTest-Info.plist */, + ); + name = GTM; + sourceTree = ""; + }; + 0867D69AFE84028FC02AAC07 /* External Frameworks and Libraries */ = { + isa = PBXGroup; + children = ( + 0867D6A5FE840307C02AAC07 /* AppKit.framework */, + F42E09AD0D19A62F00D5DDE0 /* Carbon.framework */, + 1058C7B1FEA5585E11CA2CBB /* Cocoa.framework */, + 0867D69BFE84028FC02AAC07 /* Foundation.framework */, + F42E089B0D199B1800D5DDE0 /* SenTestingKit.framework */, + ); + name = "External Frameworks and Libraries"; + sourceTree = ""; + }; + F48FE23E0D197F70009257D2 /* XcodeConfig */ = { + isa = PBXGroup; + children = ( + F48FE2410D197F9A009257D2 /* DebugTigerOrLater.xcconfig */, + F48FE2420D197F9A009257D2 /* DebugUnittest.xcconfig */, + F48FE2430D197F9A009257D2 /* LoadableBundle.xcconfig */, + F48FE2440D197F9A009257D2 /* ReleaseTigerOrLater.xcconfig */, + F48FE2450D197F9A009257D2 /* ReleaseUnittest.xcconfig */, + F48FE2460D197F9A009257D2 /* SharedLibrary.xcconfig */, + F48FE2470D197F9A009257D2 /* StaticLibrary.xcconfig */, + ); + path = XcodeConfig; + sourceTree = ""; + }; + F48FE26F0D198CBA009257D2 /* AppKit */ = { + isa = PBXGroup; + children = ( + F48FE27C0D198D0E009257D2 /* GTMDelegatingTableColumn.h */, + F48FE27D0D198D0E009257D2 /* GTMDelegatingTableColumn.m */, + F48FE27E0D198D0E009257D2 /* GTMGeometryUtils.h */, + F48FE27F0D198D0E009257D2 /* GTMGeometryUtils.m */, + F48FE2800D198D0E009257D2 /* GTMGeometryUtilsTest.m */, + F43E44770D4918B20041161F /* GTMLinearRGBShading.h */, + F43E44780D4918B20041161F /* GTMLinearRGBShading.m */, + F43E44790D4918B20041161F /* GTMLinearRGBShadingTest.m */, + F43DCDCB0D4796C600959A62 /* GTMLoginItems.h */, + F43DCDCC0D4796C600959A62 /* GTMLoginItems.m */, + F43DCEC60D47BEA000959A62 /* GTMLoginItemsTest.m */, + F428FEFF0D48E55E00382ED1 /* GTMNSBezierPath+CGPath.h */, + F428FF000D48E55E00382ED1 /* GTMNSBezierPath+CGPath.m */, + F428FF010D48E55E00382ED1 /* GTMNSBezierPath+CGPathTest.m */, + F428FF020D48E55E00382ED1 /* GTMNSBezierPath+CGPathTest.tif */, + F48FE2810D198D0E009257D2 /* GTMNSBezierPath+RoundRect.h */, + F48FE2820D198D0E009257D2 /* GTMNSBezierPath+RoundRect.m */, + F48FE2830D198D0E009257D2 /* GTMNSBezierPath+RoundRectTest.m */, + F48FE2840D198D0E009257D2 /* GTMNSBezierPath+RoundRectTest.tif */, + F47F1C0D0D490BC000925B8F /* GTMNSBezierPath+Shading.h */, + F47F1C0E0D490BC000925B8F /* GTMNSBezierPath+Shading.m */, + F47F1C0F0D490BC000925B8F /* GTMNSBezierPath+ShadingTest.10.4.tif */, + F47F1C100D490BC000925B8F /* GTMNSBezierPath+ShadingTest.10.5.tif */, + F47F1C110D490BC000925B8F /* GTMNSBezierPath+ShadingTest.m */, + F47F1CAC0D4910FD00925B8F /* GTMNSColor+Theme.h */, + F47F1CAD0D4910FD00925B8F /* GTMNSColor+Theme.m */, + F47F1CAE0D4910FD00925B8F /* GTMNSColor+ThemeTest.m */, + F47F1C740D490E5C00925B8F /* GTMShading.h */, + F42E09A80D19A5E300D5DDE0 /* GTMNSWorkspace+Theme.h */, + F42E09A90D19A5E300D5DDE0 /* GTMNSWorkspace+Theme.m */, + F42E0B090D19A6FB00D5DDE0 /* GTMNSWorkspace+ThemeTest.m */, + ); + path = AppKit; + sourceTree = ""; + }; + F48FE2720D198CCE009257D2 /* Foundation */ = { + isa = PBXGroup; + children = ( + F47F1D2D0D4914AD00925B8F /* GTMCalculatedRange.h */, + F47F1D2E0D4914AD00925B8F /* GTMCalculatedRange.m */, + F47F1D2F0D4914AD00925B8F /* GTMCalculatedRangeTest.m */, + F48FE28D0D198D24009257D2 /* GTMGarbageCollection.h */, + F43E4DD60D4E56320041161F /* GTMNSEnumerator+Filter.h */, + F43E4DD70D4E56320041161F /* GTMNSEnumerator+Filter.m */, + F43E4DD80D4E56320041161F /* GTMNSEnumerator+FilterTest.m */, + F48FE28E0D198D24009257D2 /* GTMNSString+HTML.h */, + F48FE28F0D198D24009257D2 /* GTMNSString+HTML.m */, + F48FE2900D198D24009257D2 /* GTMNSString+HTMLTest.m */, + F43E4C250D4E361D0041161F /* GTMNSString+XML.h */, + F43E4C260D4E361D0041161F /* GTMNSString+XML.m */, + F43E4C270D4E361D0041161F /* GTMNSString+XMLTest.m */, + F43E4E5E0D4E5EC90041161F /* GTMNSData+zlib.h */, + F43E4E5F0D4E5EC90041161F /* GTMNSData+zlib.m */, + F43E4E600D4E5EC90041161F /* GTMNSData+zlibTest.m */, + F48FE2910D198D24009257D2 /* GTMObjectSingleton.h */, + F48FE2920D198D24009257D2 /* GTMSystemVersion.h */, + F48FE2930D198D24009257D2 /* GTMSystemVersion.m */, + F48FE2E10D198E4C009257D2 /* GTMSystemVersionTest.m */, + ); + path = Foundation; + sourceTree = ""; + }; + F48FE2770D198CEA009257D2 /* UnitTesting */ = { + isa = PBXGroup; + children = ( + F48FE29B0D198D36009257D2 /* GTMNSObject+UnitTesting.h */, + F48FE29C0D198D36009257D2 /* GTMNSObject+UnitTesting.m */, + F48FE29D0D198D36009257D2 /* GTMNSView+UnitTesting.h */, + F48FE29E0D198D36009257D2 /* GTMNSView+UnitTesting.m */, + F48FE29F0D198D36009257D2 /* GTMSenTestCase.h */, + ); + path = UnitTesting; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + F42E08680D199A5B00D5DDE0 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + F42E09490D199BBF00D5DDE0 /* GTMDelegatingTableColumn.h in Headers */, + F42E094B0D199BBF00D5DDE0 /* GTMGarbageCollection.h in Headers */, + F42E094C0D199BBF00D5DDE0 /* GTMGeometryUtils.h in Headers */, + F42E094F0D199BBF00D5DDE0 /* GTMNSBezierPath+RoundRect.h in Headers */, + F42E09510D199BBF00D5DDE0 /* GTMNSString+HTML.h in Headers */, + F42E09530D199BBF00D5DDE0 /* GTMObjectSingleton.h in Headers */, + F42E09540D199BBF00D5DDE0 /* GTMSystemVersion.h in Headers */, + F42E09AA0D19A5E300D5DDE0 /* GTMNSWorkspace+Theme.h in Headers */, + F43DCDCD0D4796C600959A62 /* GTMLoginItems.h in Headers */, + F428FF030D48E55E00382ED1 /* GTMNSBezierPath+CGPath.h in Headers */, + F47F1C120D490BC000925B8F /* GTMNSBezierPath+Shading.h in Headers */, + F47F1C750D490E5C00925B8F /* GTMShading.h in Headers */, + F47F1CAF0D4910FD00925B8F /* GTMNSColor+Theme.h in Headers */, + F47F1D300D4914AD00925B8F /* GTMCalculatedRange.h in Headers */, + F43E447A0D4918B20041161F /* GTMLinearRGBShading.h in Headers */, + F43E4C280D4E361D0041161F /* GTMNSString+XML.h in Headers */, + F43E4DD90D4E56320041161F /* GTMNSEnumerator+Filter.h in Headers */, + F43E4E610D4E5EC90041161F /* GTMNSData+zlib.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + F42E08110D19987200D5DDE0 /* UnitTest - Foundation */ = { + isa = PBXNativeTarget; + buildConfigurationList = F42E081E0D19987200D5DDE0 /* Build configuration list for PBXNativeTarget "UnitTest - Foundation" */; + buildPhases = ( + F42E08140D19987200D5DDE0 /* Resources */, + F42E08160D19987200D5DDE0 /* Sources */, + F42E081C0D19987200D5DDE0 /* Frameworks */, + F42E081D0D19987200D5DDE0 /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + F42E08790D199AA600D5DDE0 /* PBXTargetDependency */, + ); + name = "UnitTest - Foundation"; + productName = "UnitTest - AppKit"; + productReference = F42E08210D19987200D5DDE0 /* UnitTest - Foundation.octest */; + productType = "com.apple.product-type.bundle"; + }; + F42E086C0D199A5B00D5DDE0 /* GTM */ = { + isa = PBXNativeTarget; + buildConfigurationList = F42E086F0D199A5C00D5DDE0 /* Build configuration list for PBXNativeTarget "GTM" */; + buildPhases = ( + F42E08680D199A5B00D5DDE0 /* Headers */, + F42E08690D199A5B00D5DDE0 /* Resources */, + F42E086A0D199A5B00D5DDE0 /* Sources */, + F42E086B0D199A5B00D5DDE0 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = GTM; + productName = GTM; + productReference = F42E086D0D199A5B00D5DDE0 /* GTM.framework */; + productType = "com.apple.product-type.framework"; + }; + F48FE2630D198C1E009257D2 /* UnitTest - AppKit */ = { + isa = PBXNativeTarget; + buildConfigurationList = F48FE2660D198C1F009257D2 /* Build configuration list for PBXNativeTarget "UnitTest - AppKit" */; + buildPhases = ( + F48FE25F0D198C1E009257D2 /* Resources */, + F48FE2600D198C1E009257D2 /* Sources */, + F48FE2610D198C1E009257D2 /* Frameworks */, + F48FE2620D198C1E009257D2 /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + F42E08770D199A9B00D5DDE0 /* PBXTargetDependency */, + ); + name = "UnitTest - AppKit"; + productName = "UnitTest - AppKit"; + productReference = F48FE2640D198C1E009257D2 /* UnitTest - AppKit.octest */; + productType = "com.apple.product-type.bundle"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 0867D690FE84028FC02AAC07 /* Project object */ = { + isa = PBXProject; + buildConfigurationList = 1DEB918108733D990010E9CD /* Build configuration list for PBXProject "GTM" */; + hasScannedForEncodings = 1; + mainGroup = 0867D691FE84028FC02AAC07 /* GTM */; + productRefGroup = 034768DFFF38A50411DB9C8B /* Products */; + projectDirPath = ""; + targets = ( + F42E086C0D199A5B00D5DDE0 /* GTM */, + F472042B0D33BEAF00E9F378 /* All UnitTests */, + F48FE2630D198C1E009257D2 /* UnitTest - AppKit */, + F42E08110D19987200D5DDE0 /* UnitTest - Foundation */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + F42E08140D19987200D5DDE0 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F42E08690D199A5B00D5DDE0 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F48FE25F0D198C1E009257D2 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F42E08300D19991400D5DDE0 /* GTMNSBezierPath+RoundRectTest.tif in Resources */, + F428FF0A0D48E57300382ED1 /* GTMNSBezierPath+CGPathTest.tif in Resources */, + F47F1C190D490BD200925B8F /* GTMNSBezierPath+ShadingTest.10.4.tif in Resources */, + F47F1C1A0D490BD200925B8F /* GTMNSBezierPath+ShadingTest.10.5.tif in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + F42E081D0D19987200D5DDE0 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "# Run the unit tests in this test bundle.\n\"${SYSTEM_DEVELOPER_DIR}/Tools/RunUnitTests\"\n"; + }; + F48FE2620D198C1E009257D2 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "# Run the unit tests in this test bundle.\n\"${SYSTEM_DEVELOPER_DIR}/Tools/RunUnitTests\"\n"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + F42E08160D19987200D5DDE0 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F42E08330D19992100D5DDE0 /* GTMNSString+HTMLTest.m in Sources */, + F42E08340D19992100D5DDE0 /* GTMSystemVersionTest.m in Sources */, + F47F1D350D4914B600925B8F /* GTMCalculatedRangeTest.m in Sources */, + F43E4C2D0D4E36230041161F /* GTMNSString+XMLTest.m in Sources */, + F43E4DDE0D4E56380041161F /* GTMNSEnumerator+FilterTest.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F42E086A0D199A5B00D5DDE0 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F42E094A0D199BBF00D5DDE0 /* GTMDelegatingTableColumn.m in Sources */, + F42E094D0D199BBF00D5DDE0 /* GTMGeometryUtils.m in Sources */, + F42E09500D199BBF00D5DDE0 /* GTMNSBezierPath+RoundRect.m in Sources */, + F42E09520D199BBF00D5DDE0 /* GTMNSString+HTML.m in Sources */, + F42E09550D199BBF00D5DDE0 /* GTMSystemVersion.m in Sources */, + F42E09AB0D19A5E300D5DDE0 /* GTMNSWorkspace+Theme.m in Sources */, + F43DCDCE0D4796C600959A62 /* GTMLoginItems.m in Sources */, + F428FF040D48E55E00382ED1 /* GTMNSBezierPath+CGPath.m in Sources */, + F47F1C130D490BC000925B8F /* GTMNSBezierPath+Shading.m in Sources */, + F47F1CB00D4910FD00925B8F /* GTMNSColor+Theme.m in Sources */, + F47F1D310D4914AD00925B8F /* GTMCalculatedRange.m in Sources */, + F43E447B0D4918B20041161F /* GTMLinearRGBShading.m in Sources */, + F43E4C290D4E361D0041161F /* GTMNSString+XML.m in Sources */, + F43E4DDA0D4E56320041161F /* GTMNSEnumerator+Filter.m in Sources */, + F43E4E620D4E5EC90041161F /* GTMNSData+zlib.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F48FE2600D198C1E009257D2 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F42E082E0D19991400D5DDE0 /* GTMGeometryUtilsTest.m in Sources */, + F42E082F0D19991400D5DDE0 /* GTMNSBezierPath+RoundRectTest.m in Sources */, + F42E09450D199BA400D5DDE0 /* GTMNSObject+UnitTesting.m in Sources */, + F42E09460D199BA400D5DDE0 /* GTMNSView+UnitTesting.m in Sources */, + F42E0B0A0D19A6FB00D5DDE0 /* GTMNSWorkspace+ThemeTest.m in Sources */, + F43DCEC70D47BEA000959A62 /* GTMLoginItemsTest.m in Sources */, + F428FF090D48E57300382ED1 /* GTMNSBezierPath+CGPathTest.m in Sources */, + F47F1C1B0D490BD200925B8F /* GTMNSBezierPath+ShadingTest.m in Sources */, + F47F1CB60D49110900925B8F /* GTMNSColor+ThemeTest.m in Sources */, + F43E447F0D4918BC0041161F /* GTMLinearRGBShadingTest.m in Sources */, + F43E4E660D4E5ED40041161F /* GTMNSData+zlibTest.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + F42E08770D199A9B00D5DDE0 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = F42E086C0D199A5B00D5DDE0 /* GTM */; + targetProxy = F42E08760D199A9B00D5DDE0 /* PBXContainerItemProxy */; + }; + F42E08790D199AA600D5DDE0 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = F42E086C0D199A5B00D5DDE0 /* GTM */; + targetProxy = F42E08780D199AA600D5DDE0 /* PBXContainerItemProxy */; + }; + F472042D0D33BEB500E9F378 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = F48FE2630D198C1E009257D2 /* UnitTest - AppKit */; + targetProxy = F472042C0D33BEB500E9F378 /* PBXContainerItemProxy */; + }; + F472042F0D33BEB500E9F378 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = F42E08110D19987200D5DDE0 /* UnitTest - Foundation */; + targetProxy = F472042E0D33BEB500E9F378 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 1DEB918208733D990010E9CD /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F48FE2410D197F9A009257D2 /* DebugTigerOrLater.xcconfig */; + buildSettings = { + }; + name = Debug; + }; + 1DEB918308733D990010E9CD /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F48FE2440D197F9A009257D2 /* ReleaseTigerOrLater.xcconfig */; + buildSettings = { + }; + name = Release; + }; + F42E081F0D19987200D5DDE0 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F48FE2420D197F9A009257D2 /* DebugUnittest.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(value)", + "$(DEVELOPER_FRAMEWORKS_DIR_QUOTED)", + "$(FRAMEWORK_SEARCH_PATHS_QUOTED_FOR_TARGET_1)", + ); + FRAMEWORK_SEARCH_PATHS_QUOTED_FOR_TARGET_1 = "\"$(DEVELOPER_FRAMEWORKS_DIR)\""; + GCC_PREFIX_HEADER = GTM_Prefix.pch; + INFOPLIST_FILE = "UnitTest-Info.plist"; + PRODUCT_NAME = "UnitTest - Foundation"; + WRAPPER_EXTENSION = octest; + }; + name = Debug; + }; + F42E08200D19987200D5DDE0 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F48FE2450D197F9A009257D2 /* ReleaseUnittest.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(value)", + "$(DEVELOPER_FRAMEWORKS_DIR_QUOTED)", + "$(FRAMEWORK_SEARCH_PATHS_QUOTED_FOR_TARGET_1)", + ); + FRAMEWORK_SEARCH_PATHS_QUOTED_FOR_TARGET_1 = "\"$(DEVELOPER_FRAMEWORKS_DIR)\""; + GCC_PREFIX_HEADER = GTM_Prefix.pch; + INFOPLIST_FILE = "UnitTest-Info.plist"; + PRODUCT_NAME = "UnitTest - Foundation"; + WRAPPER_EXTENSION = octest; + }; + name = Release; + }; + F42E08700D199A5C00D5DDE0 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F48FE2430D197F9A009257D2 /* LoadableBundle.xcconfig */; + buildSettings = { + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + FRAMEWORK_VERSION = A; + GCC_PREFIX_HEADER = GTM_prefix.pch; + INFOPLIST_FILE = "GTM-Info.plist"; + INSTALL_PATH = "@loader_path/../Frameworks"; + PRODUCT_NAME = GTM; + }; + name = Debug; + }; + F42E08710D199A5C00D5DDE0 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F48FE2430D197F9A009257D2 /* LoadableBundle.xcconfig */; + buildSettings = { + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + FRAMEWORK_VERSION = A; + GCC_PREFIX_HEADER = GTM_prefix.pch; + INFOPLIST_FILE = "GTM-Info.plist"; + INSTALL_PATH = "@loader_path/../Frameworks"; + PRODUCT_NAME = GTM; + }; + name = Release; + }; + F47204350D33BEDF00E9F378 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + COPY_PHASE_STRIP = NO; + GCC_DYNAMIC_NO_PIC = NO; + GCC_GENERATE_DEBUGGING_SYMBOLS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + PRODUCT_NAME = "All UnitTests"; + }; + name = Debug; + }; + F47204360D33BEDF00E9F378 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + COPY_PHASE_STRIP = YES; + GCC_ENABLE_FIX_AND_CONTINUE = NO; + GCC_GENERATE_DEBUGGING_SYMBOLS = NO; + PRODUCT_NAME = "All UnitTests"; + ZERO_LINK = NO; + }; + name = Release; + }; + F48FE2670D198C1F009257D2 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F48FE2420D197F9A009257D2 /* DebugUnittest.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(value)", + "$(DEVELOPER_FRAMEWORKS_DIR_QUOTED)", + "$(FRAMEWORK_SEARCH_PATHS_QUOTED_FOR_TARGET_1)", + ); + FRAMEWORK_SEARCH_PATHS_QUOTED_FOR_TARGET_1 = "\"$(DEVELOPER_FRAMEWORKS_DIR)\""; + GCC_PREFIX_HEADER = GTM_Prefix.pch; + INFOPLIST_FILE = "UnitTest-Info.plist"; + PRODUCT_NAME = "UnitTest - AppKit"; + WRAPPER_EXTENSION = octest; + }; + name = Debug; + }; + F48FE2680D198C1F009257D2 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F48FE2450D197F9A009257D2 /* ReleaseUnittest.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(value)", + "$(DEVELOPER_FRAMEWORKS_DIR_QUOTED)", + "$(FRAMEWORK_SEARCH_PATHS_QUOTED_FOR_TARGET_1)", + ); + FRAMEWORK_SEARCH_PATHS_QUOTED_FOR_TARGET_1 = "\"$(DEVELOPER_FRAMEWORKS_DIR)\""; + GCC_PREFIX_HEADER = GTM_Prefix.pch; + INFOPLIST_FILE = "UnitTest-Info.plist"; + PRODUCT_NAME = "UnitTest - AppKit"; + WRAPPER_EXTENSION = octest; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 1DEB918108733D990010E9CD /* Build configuration list for PBXProject "GTM" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1DEB918208733D990010E9CD /* Debug */, + 1DEB918308733D990010E9CD /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + F42E081E0D19987200D5DDE0 /* Build configuration list for PBXNativeTarget "UnitTest - Foundation" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F42E081F0D19987200D5DDE0 /* Debug */, + F42E08200D19987200D5DDE0 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + F42E086F0D199A5C00D5DDE0 /* Build configuration list for PBXNativeTarget "GTM" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F42E08700D199A5C00D5DDE0 /* Debug */, + F42E08710D199A5C00D5DDE0 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + F47204340D33BEDF00E9F378 /* Build configuration list for PBXAggregateTarget "All UnitTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F47204350D33BEDF00E9F378 /* Debug */, + F47204360D33BEDF00E9F378 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + F48FE2660D198C1F009257D2 /* Build configuration list for PBXNativeTarget "UnitTest - AppKit" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F48FE2670D198C1F009257D2 /* Debug */, + F48FE2680D198C1F009257D2 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 0867D690FE84028FC02AAC07 /* Project object */; +} diff --git a/GTM_Prefix.pch b/GTM_Prefix.pch new file mode 100644 index 0000000..c745642 --- /dev/null +++ b/GTM_Prefix.pch @@ -0,0 +1,19 @@ +// +// 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. +// + +#ifdef __OBJC__ + #import +#endif diff --git a/ReleaseNotes.txt b/ReleaseNotes.txt new file mode 100644 index 0000000..0233235 --- /dev/null +++ b/ReleaseNotes.txt @@ -0,0 +1,5 @@ +Release 1.0.0 +14-January-2008 + +Initial public release. Includes some simple utils, xcode configs, and +some support for doing unit tests of graphical things. diff --git a/UnitTest-Info.plist b/UnitTest-Info.plist new file mode 100644 index 0000000..71daf7d --- /dev/null +++ b/UnitTest-Info.plist @@ -0,0 +1,20 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + com.google.${PRODUCT_NAME} + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + BNDL + CFBundleSignature + ???? + CFBundleVersion + 1.0 + + diff --git a/UnitTesting/GTMNSObject+UnitTesting.h b/UnitTesting/GTMNSObject+UnitTesting.h new file mode 100644 index 0000000..73a2c9a --- /dev/null +++ b/UnitTesting/GTMNSObject+UnitTesting.h @@ -0,0 +1,534 @@ +// +// GTMNSObject+UnitTesting.h +// +// Utilities for doing advanced unittesting with objects. +// +// 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 + +/// Fails when image of |a1| does not equal image in TIFF file named |a2| +// +// Generates a failure when the unittest image of |a1| is not equal to the +// image stored in the TIFF file named |a2|, or |a2| does not exist in the +// executable code's bundle. +// If |a2| does not exist in the executable code's bundle, we save a TIFF +// representation of |a1| on the desktop with name |a2|. This can then be +// included in the bundle as the master to test against. +// If |a2| != |a1|, we save a TIFF representation of |a1| on the desktop +// with name |a2|_Failed so that we can compare the two files to see what +// has changed. +// See pathForTIFFNamed to see how name is searched for. +// Implemented as a macro to match the rest of the SenTest macros. +// +// Args: +// a1: The object to be checked. Must implement the -unitTestImage method. +// a2: The name of the TIFF file to check against. +// Do not include the extension +// description: A format string as in the printf() function. +// Can be nil or an empty string but must be present. +// ...: A variable number of arguments to the format string. Can be absent. +// +#define GTMAssertObjectImageEqualToTIFFNamed(a1, a2, description, ...) \ +do { \ + NSObject* a1Object = (a1); \ + NSString* a2String = (a2); \ + NSString *failString = nil; \ + BOOL isGood = [a1Object respondsToSelector:@selector(unitTestImage)]; \ + if (isGood) { \ + if (![a1Object areSystemSettingsValidForDoingImage]) { \ + break; \ + } \ + NSString *aPath = [a1Object pathForTIFFNamed:a2String]; \ + isGood = aPath != nil; \ + if (isGood) { \ + isGood = [a1Object compareWithTIFFAt:aPath]; \ + } \ + if (!isGood) { \ + if (aPath != nil) { \ + a2String = [a2String stringByAppendingString:@"_Failed"]; \ + } \ + BOOL aSaved = [a1Object saveToTIFFNamed:a2String]; \ + if (NO == aSaved) {\ + if (aPath == nil) { \ + failString = [NSString stringWithFormat:@"File %@ did not exist in bundle. Tried to save %@ to desktop and failed.", a2String, a2String]; \ + } else { \ + failString = [NSString stringWithFormat:@"Object image different than file %@. Tried to save to desktop as %@ and failed.", aPath, a2String]; \ + } \ + } else { \ + if (aPath == nil) { \ + failString = [NSString stringWithFormat:@"File %@ did not exist in bundle. Saved to ~/Desktop/%@", a2String, a2String]; \ + } else { \ + failString = [NSString stringWithFormat:@"Object image different than file %@. Saved image to desktop as %@.", aPath, a2String]; \ + } \ + } \ + } \ + } else { \ + failString = @"Object does not respond to -unitTestImage"; \ + } \ + if (!isGood) { \ + if (nil != description) { \ + STFail(@"%@: %@", STComposeString(description, ##__VA_ARGS__), failString); \ + } else { \ + STFail(@"%@", failString); \ + } \ + } \ +} while(0) + +/// Fails when state of |a1| does not equal state in file |a2| +// +// Generates a failure when the unittest state of |a1| is not equal to the +// state stored in the state file named |a2|, or |a2| does not exist in the +// executable code's bundle. +// If |a2| does not exist in the executable code's bundle, we save a state +// representation of |a1| on the desktop with name |a2|. This can then be +// included in the bundle as the master to test against. +// If |a2| != |a1|, we save a state representation of |a1| on the desktop +// with name |a2|_Failed so that we can compare the two files to see what +// has changed. +// Implemented as a macro to match the rest of the SenTest macros. +// +// Args: +// a1: The object to be checked. Must implement the -unitTestImage method. +// a2: The name of the state file to check against. +// Do not include the extension +// description: A format string as in the printf() function. +// Can be nil or an empty string but must be present. +// ...: A variable number of arguments to the format string. Can be absent. +// +#define GTMAssertObjectStateEqualToStateNamed(a1, a2, description, ...) \ +do { \ + NSObject* a1Object = (a1); \ + NSString* a2String = (a2); \ + NSString *failString = nil; \ + BOOL isGood = [a1Object respondsToSelector:@selector(unitTestEncodeState:)]; \ + if (isGood) { \ + NSString *aPath = [a1Object pathForStateNamed:a2String]; \ + isGood = aPath != nil; \ + if (isGood) { \ + isGood = [a1Object compareWithStateAt:aPath]; \ + } \ + if (!isGood) { \ + if (aPath != nil) { \ + a2String = [a2String stringByAppendingString:@"_Failed"]; \ + } \ + BOOL aSaved = [a1Object saveToStateNamed:a2String]; \ + if (NO == aSaved) {\ + if (aPath == nil) { \ + failString = [NSString stringWithFormat:@"File %@ did not exist in bundle. Tried to save %@ to desktop and failed.", a2String, a2String]; \ + } else { \ + failString = [NSString stringWithFormat:@"Object state different than file %@. Tried to save to desktop as %@ and failed.", aPath, a2String]; \ + } \ + } else { \ + if (aPath == nil) { \ + failString = [NSString stringWithFormat:@"File %@ did not exist in bundle. Saved to ~/Desktop/%@", a2String, a2String]; \ + } else { \ + failString = [NSString stringWithFormat:@"Object state different than file %@. Saved image to desktop as %@.", aPath, a2String]; \ + } \ + } \ + } \ + } else { \ + failString = @"Object does not respond to -unitTestEncodeState:"; \ + } \ + if (!isGood) { \ + if (nil != description) { \ + STFail(@"%@: %@", STComposeString(description, ##__VA_ARGS__), failString); \ + } else { \ + STFail(@"%@", failString); \ + } \ + } \ +} while(0) + +/// test both GTMAssertObjectImageEqualToTIFFNamed and GTMAssertObjectStateEqualToStateNamed +// +// Combines the above two macros into a single ubermacro for comparing +// both state and image. When only the best will do... +#define GTMAssertObjectEqualToStateAndImageNamed(a1, a2, description, ...) \ +do { \ + GTMAssertObjectImageEqualToTIFFNamed(a1, a2, description, ##__VA_ARGS__); \ + GTMAssertObjectStateEqualToStateNamed(a1, a2, description, ##__VA_ARGS__); \ +} while (0) + +/// Tests the setters and getters for exposed bindings +// For objects that expose bindings, this tests them for you, saving you from +// having to write a whole pile of set/get test code if you add binding support. +// You will need to implement valueClassForBinding: for your bindings, +// and you may possibly want to implement unitTestExposedBindingsToIgnore +// and unitTestExposedBindingsTestValues. See descriptions of those +// methods below for details. +// Implemented as a macro to match the rest of the SenTest macros. +// +// Args: +// a1: The object to be checked. +// description: A format string as in the printf() function. +// Can be nil or an empty string but must be present. +// ...: A variable number of arguments to the format string. Can be absent. +// +#define GTMTestExposedBindings(a1, description, ...) \ +do { \ + NSArray *bindings = [a1 exposedBindings]; \ + if (bindings) { \ + NSArray *bindingsToIgnore = [a1 unitTestExposedBindingsToIgnore]; \ + NSEnumerator *bindingsEnum = [bindings objectEnumerator]; \ + NSString *bindingKey; \ + while ((bindingKey = [bindingsEnum nextObject])) { \ + if (![bindingsToIgnore containsObject:bindingKey]) { \ + Class theClass = [a1 valueClassForBinding:bindingKey]; \ + STAssertNotNil(theClass, @"Should have valueClassForBinding %@", bindingKey); \ + NSDictionary *testValues = [a1 unitTestExposedBindingsTestValues:bindingKey]; \ + NSEnumerator *testEnum = [testValues keyEnumerator]; \ + id testValue; \ + while ((testValue = [testEnum nextObject])) { \ + [a1 setValue:testValue forKey:bindingKey]; \ + id value = [a1 valueForKey:bindingKey]; \ + STAssertEqualObjects([testValues objectForKey:testValue], value, description, ##__VA_ARGS__); \ + } \ + } \ + } \ + } \ +} while(0) + +/// \cond Protocols + +// GTMUnitTestingEncoding protocol is for objects which need to save their +// "state" for using with the unit testing categories +@protocol GTMUnitTestingEncoding +// Encodes the state of an object in a manner suitable for comparing +// against a master state file so we can determine whether the +// object is in a suitable state. Encode data in the coder in the same +// manner that you would encode data in any other Keyed NSCoder subclass. +// +// Arguments: +// inCoder - the coder to encode our state into +- (void)unitTestEncodeState:(NSCoder*)inCoder; +@end + +/// Category for saving and comparing object state and image for unit tests +// +// The GTMUnitTestAdditions category gives object the ability to store their +// state for use in unittesting in two different manners. +// 1) Objects can elect to save their "image" as a TIFF that we can compare at +// runtime to a TIFF on file to make sure that the representation hasn't +// changed. All views and Windows can save their image. In the case of Windows, +// they are "bluescreened" so that any transparent areas can be compared between +// machines. For this to work, the appearance must be set to "Aqua blue" In the +// case of NSWindows and NSScreens, we do a screen capture operation to capture +// their image. In these cases, font smoothing settings must be set consistently +// across machines. The current standard is +// Font Smoothing Style: Standard - Best For CRT +// Turn Off Text Smoothing For Font Sizes: 8 And Smaller +// If you do not have these settings, any unit tests depending on them will not +// be executed, and a warning will be logged. +// Also, we need to be careful about avoiding ColorSync. In most cases the +// unittesting system handles this for you. If you are running into troubles +// make sure that you are using device colors, and not calibrated colors +// wherever you are doing drawing. +// 2) Objects can elect to save their "state". State is the attributes that we +// want to verify when running unit tests. Applications, Windows, Views, +// Controls and Cells currently return a variety of state information. If you +// want to customize the state information that a particular object returns, you +// can do it via the GTMUnitTestingEncodedObjectNotification. Items that have +// delegates (Applications/Windows) can also have their delegates return state +// information if appropriate via the unitTestEncoderDidEncode:inCoder: delegate +// method. +// To compare state/image in your unit tests, you can use the three macros above +// GTMAssertObjectStateEqualToStateNamed, GTMAssertObjectImageEqualToTIFFNamed and +// GTMAssertObjectEqualToStateAndImageNamed. +@interface NSObject (GTMUnitTestingAdditions) +/// Returns an image containing a representation suitable for use in comparing against a master image. +// +// NB this means that all colors should be +// device based, as colorsynced colors will be different on different devices. +// +// Returns: +// an image of the object +- (NSImage*)unitTestImage; + +/// Checks to see that system settings are valid for doing an image comparison. +// The main issue is that we make sure that we are set to using Blue Aqua as +// our appearance. +// Instead of directly overriding this, a unit test can just use: +// needsAquaBlueAppearanceForDoingImage +// needsScrollBarArrowsLowerRightForDoingImage +// to enable those tests w/in this base implementation. +// The other issues are for NSScreen and NSWindow images as they are affected by +// the font smoothing settings in the system preferences. For things to work +// these settings must be set to: +// Font Smoothing Style: Standard - Best For CRT +// Turn Off Text Smoothing For Font Sizes: 8 And Smaller +// +// Returns: +// YES if we can do image comparisons for this object type. +- (BOOL)areSystemSettingsValidForDoingImage; + +/// Checks if this test needs the AquaBlue Appearance for doing the image comparison. +// If the test uses the appearance colors, this should be overriden to return +// YES (ie-default is no). This provides a hook so the unittest can be skipped +// if the running user's settings aren't the "standard" for the UI unitttests. +// +// Returns: +// YES if this test needs the AquaBlue Appearance. +- (BOOL)needsAquaBlueAppearanceForDoingImage; + +/// Checks if this test needs the ScrollBarArrows LowerRight for doing the image comparison. +// If the test uses the scrollbar drawing, this should be overriden to return +// YES (ie-default is no). This provides a hook so the unittest can be skipped +// if the running user's settings aren't the "standard" for the UI unitttests. +// +// Returns: +// YES if this test needs the ScrollBarArrows LowerRight. +- (BOOL)needsScrollBarArrowsLowerRightForDoingImage; + +/// Save the unitTestImage to a TIFF file with name |name| at ~/Desktop/|name|.tif. +// The TIFF will be compressed with LZW. +// +// Args: +// name: The name for the TIFF file you would like saved. +// +// Returns: +// YES if the file was successfully saved. +// +- (BOOL)saveToTIFFNamed:(NSString*)name; + +/// Save unitTestImage of |self| to a TIFF file at path |path|. +// The TIFF will be compressed with LZW. All non-drawn areas will be transparent. +// +// Args: +// name: The name for the TIFF file you would like saved. +// +// Returns: +// YES if the file was successfully saved. +// +- (BOOL)saveToTIFFAt:(NSString*)path; + +/// Compares unitTestImage of |self| to the TIFF located at |path| +// +// Args: +// path: the path to the TIFF file you want to compare against. +// +// Returns: +// YES if they are equal, NO is they are not +// +- (BOOL)compareWithTIFFNamed:(NSString*)name; + +/// Compares unitTestImage of |self| to the TIFF located at |path| +// +// Args: +// path: the path to the TIFF file you want to compare against. +// +// Returns: +// YES if they are equal, NO is they are not +// +- (BOOL)compareWithTIFFAt:(NSString*)path; + +/// Find the path for a TIFF by name in your bundle. +// Searches for the following: +// "name.tif", +// "name.arch.tif", +// "name.arch.OSVersionMajor.tif" +// "name.arch.OSVersionMajor.OSVersionMinor.tif" +// "name.arch.OSVersionMajor.OSVersionMinor.OSVersion.bugfix.tif" +// "name.arch.OSVersionMajor.tif" +// "name.OSVersionMajor.arch.tif" +// "name.OSVersionMajor.OSVersionMinor.arch.tif" +// "name.OSVersionMajor.OSVersionMinor.OSVersion.bugfix.arch.tif" +// "name.OSVersionMajor.tif" +// "name.OSVersionMajor.OSVersionMinor.tif" +// "name.OSVersionMajor.OSVersionMinor.OSVersion.bugfix.tif" +// Do not include the ".tif" extension on your name. +// +// Args: +// name: The name for the TIFF file you would like to find. +// +// Returns: +// the path if the TIFF exists in your bundle +// or nil if no TIFF to be found +// +- (NSString *)pathForTIFFNamed:(NSString*)name; + + +/// Gives us a LZW compressed representation of unitTestImage of |self|. +// +// Returns: +// a LZW compressed TIFF if successful +// nil if failed +// +- (NSData *)TIFFRepresentation; + + +/// Save the encoded unit test state to a .gtmUTState file with name |name| at ~/Desktop/|name|.gtmUTState. +// +// Args: +// name: The name for the state file you would like saved. +// +// Returns: +// YES if the file was successfully saved. +// +- (BOOL)saveToStateNamed:(NSString*)name; + +/// Save encoded unit test state of |self| to a .gtmUTState file at path |path|. +// +// Args: +// name: The name for the state file you would like saved. +// +// Returns: +// YES if the file was successfully saved. +// +- (BOOL)saveToStateAt:(NSString*)path; + +/// Compares encoded unit test state of |self| to the .gtmUTState named |name| +// +// Args: +// name: the name of the state file you want to compare against. +// +// Returns: +// YES if they are equal, NO is they are not +// +- (BOOL)compareWithStateNamed:(NSString*)name; + +/// Compares encoded unit test state of |self| to the .gtmUTState located at |path|. +// +// Args: +// path: the path to the state file you want to compare against. +// +// Returns: +// YES if they are equal, NO is they are not +// +- (BOOL)compareWithStateAt:(NSString*)path; + +/// Find the path for a state by name in your bundle. +// Searches for: +// "name.gtmUTState", +// "name.arch.gtmUTState", +// "name.arch.OSVersionMajor.gtmUTState" +// "name.arch.OSVersionMajor.OSVersionMinor.gtmUTState" +// "name.arch.OSVersionMajor.OSVersionMinor.OSVersion.bugfix.gtmUTState" +// "name.arch.OSVersionMajor.gtmUTState" +// "name.OSVersionMajor.arch.gtmUTState" +// "name.OSVersionMajor.OSVersionMinor.arch.gtmUTState" +// "name.OSVersionMajor.OSVersionMinor.OSVersion.bugfix.arch.gtmUTState" +// "name.OSVersionMajor.gtmUTState" +// "name.OSVersionMajor.OSVersionMinor.gtmUTState" +// "name.OSVersionMajor.OSVersionMinor.OSVersion.bugfix.gtmUTState" +// Do not include the ".gtmUTState" extension on your name. +// +// Args: +// name: The name for the state file you would like to find. +// +// Returns: +// the path if the state exists in your bundle +// or nil if no state to be found +// +- (NSString *)pathForStateNamed:(NSString*)name; + + +/// Gives us the encoded unit test state for |self| +// +// Returns: +// the encoded state if successful +// nil if failed +// +- (NSDictionary *)stateRepresentation; + + +/// Encodes the state of an object +// Encodes the state of an object in a manner suitable for comparing +// against a master state file so we can determine whether the +// object is in a suitable state. Encode data in the coder in the same +// manner that you would encode data in any other Keyed NSCoder subclass. +// +// Arguments: +// inCoder - the coder to encode our state into +- (void)unitTestEncodeState:(NSCoder*)inCoder; + +/// Allows you to ignore certain bindings when running GTMTestExposedBindings +// If you have bindings you want to ignore, add them to the array returned +// by this method. The standard way to implement this would be: +// - (NSMutableArray*)unitTestExposedBindingsToIgnore { +// NSMutableArray *array = [super unitTestExposedBindingsToIgnore]; +// [array addObject:@"bindingToIgnore1"]; +// ... +// return array; +// } +// The NSObject implementation by default will ignore NSFontBoldBinding, +// NSFontFamilyNameBinding, NSFontItalicBinding, NSFontNameBinding and +// NSFontSizeBinding if your exposed bindings contains NSFontBinding because +// the NSFont*Bindings are NOT KVC/KVO compliant, and they just happen to work +// through what can only be described as magic :) +- (NSMutableArray*)unitTestExposedBindingsToIgnore; + +/// Allows you to set up test values for your different bindings. +// if you have certain values you want to test against your bindings, add +// them to the dictionary returned by this method. The dictionary is a "value" key +// and an "expected return" object. +// The standard way to implement this would be: +// - (NSMutableDictionary*)unitTestExposedBindingsTestValues:(NSString*)binding { +// NSMutableDictionary *dict = [super unitTestExposedBindingsTestValues:binding]; +// if ([binding isEqualToString:@"myBinding"]) { +// [dict setObject:[[[MySpecialBindingValueSet alloc] init] autorelease] +// forKey:[[[MySpecialBindingValueGet alloc] init] autorelease]]; +// [dict setObjectAndKey:[[[MySpecialBindingValue alloc] init] autorelease]]; +// ... +// else if ([binding isEqualToString:@"myBinding2"]) { +// ... +// } +// return array; +// } +// The NSObject implementation handles many of the default bindings, and +// gives you a reasonable set of test values to start. +// See the implementation for the current list of bindings, and values that we +// set for those bindings. +- (NSMutableDictionary*)unitTestExposedBindingsTestValues:(NSString*)binding; +@end + +// Utility for simplifying unitTestExposedBindingsTestValues implementations +@interface NSMutableDictionary (GTMUnitTestingAdditions) +// Sets an object and a key to the same value in a dictionary. +- (void)setObjectAndKey:(id)objectAndKey; +@end + +// Informal protocol for delegates that wanst to be able to add state info +// when state info is collected for their "owned" objects +@interface NSObject (GTMUnitTestingEncodingAdditions) +// Delegate function for unit test objects that have delegates. Delegates have +// the option of encoding more data into the coder to store their state for +// unittest usage. +- (void)unitTestEncoderDidEncode:(id)sender inCoder:(NSCoder*)inCoder; +@end + +/// \endcond + +// Whenever an object is encoded by the unit test encoder, it send out a +// notification so that objects who want to add data to the encoded objects unit +// test state can do so. The Coder will be in the userInfo dictionary for the +// notification under the GTMUnitTestingEncoderKey key. +extern NSString *const GTMUnitTestingEncodedObjectNotification; + +// Key for finding the encoder in the userInfo dictionary for +// GTMUnitTestingEncodedObjectNotification notifications. +extern NSString *const GTMUnitTestingEncoderKey; + + +/// Support for Pulse automated builds +@interface NSObject (GTMUnitTestingPulseAdditions) + +// Determine if the current unittest is running under Pulse +- (BOOL)isRunningUnderPulse; + +// Get the current base directory for Pulse +- (NSString *)pulseBaseDirectory; + +@end diff --git a/UnitTesting/GTMNSObject+UnitTesting.m b/UnitTesting/GTMNSObject+UnitTesting.m new file mode 100644 index 0000000..5be6b9f --- /dev/null +++ b/UnitTesting/GTMNSObject+UnitTesting.m @@ -0,0 +1,730 @@ +// +// GTMNSObject+UnitTesting.m +// +// An informal protocol for doing advanced unittesting with objects. +// +// 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 +#include + +#import "GTMNSObject+UnitTesting.h" +#import "GTMNSWorkspace+Theme.h" +#import "GTMSystemVersion.h" + +NSString *const GTMUnitTestingEncodedObjectNotification = @"GTMUnitTestingEncodedObjectNotification"; +NSString *const GTMUnitTestingEncoderKey = @"GTMUnitTestingEncoderKey"; + +// This class exists so that we can locate our bundle using [NSBundle +// bundleForClass:]. We don't use [NSBundle mainBundle] because when we are +// being run as a unit test, we aren't the mainBundle +@interface GTMUnitTestingAdditionsBundleFinder : NSObject { + // Nothing here +} +// or here +@end + +@implementation GTMUnitTestingAdditionsBundleFinder +// Nothing here. We're just interested in the name for finding our bundle. +@end + +@interface NSObject (GTMUnitTestingAdditionsPrivate) +/// Find the path for a file named name.extension in your bundle. +// Searches for the following: +// "name.extension", +// "name.arch.extension", +// "name.arch.OSVersionMajor.extension" +// "name.arch.OSVersionMajor.OSVersionMinor.extension" +// "name.arch.OSVersionMajor.OSVersionMinor.OSVersion.bugfix.extension" +// "name.arch.OSVersionMajor.extension" +// "name.OSVersionMajor.arch.extension" +// "name.OSVersionMajor.OSVersionMinor.arch.extension" +// "name.OSVersionMajor.OSVersionMinor.OSVersion.bugfix.arch.extension" +// "name.OSVersionMajor.extension" +// "name.OSVersionMajor.OSVersionMinor.extension" +// "name.OSVersionMajor.OSVersionMinor.OSVersion.bugfix.extension" +// Do not include the ".extension" extension on your name. +// +// Args: +// name: The name for the file you would like to find. +// extension: the extension for the file you would like to find +// +// Returns: +// the path if the file exists in your bundle +// or nil if no file is found +// +- (NSString *)pathForFileNamed:(NSString*)name extension:(NSString*)extension; +- (NSString *)saveToPathForFileNamed:(NSString*)name + extension:(NSString*)extension; +@end + +// This is a keyed coder for storing unit test state data. It is used only by +// the GTMUnitTestingAdditions category. Most of the work is done in +// encodeObject:forKey:. +@interface GTMUnitTestingKeyedCoder : NSCoder { + NSMutableDictionary *dictionary_; // storage for data (STRONG) +} + +// get the data stored in coder. +// +// Returns: +// NSDictionary with currently stored data. +- (NSDictionary*)dictionary; +@end + +@implementation GTMUnitTestingKeyedCoder + +// Set up storage for coder. Stores type and version. +// Version 1 +// +// Returns: +// self +- (id)init { + self = [super init]; + if (self != nil) { + dictionary_ = [[NSMutableDictionary alloc] initWithCapacity:0]; + [dictionary_ setObject:@"GTMUnitTestingArchive" forKey:@"$GTMArchive"]; + + // Version number can be changed here. + [dictionary_ setObject:[NSNumber numberWithInt:1] forKey:@"$GTMVersion"]; + } + return self; +} + +// Standard dealloc +- (void)dealloc { + [dictionary_ release]; + [super dealloc]; +} + +// Utility function for checking for a key value. We don't want duplicate keys +// in any of our dictionaries as we may be writing over data stored by previous +// objects. +// +// Arguments: +// key - key to check for in dictionary +- (void)checkForKey:(NSString*)key { + NSAssert1(![dictionary_ objectForKey:key], @"Key already exists for %@", key); +} + +// Key routine for the encoder. We store objects in our dictionary based on +// their key. As we encode objects we send out notifications to let other +// classes doing tests add their specific data to the base types. If we can't +// encode the object (it doesn't support unitTestEncodeState) and we don't get +// any info back from the notifier, we attempt to store it's description. +// +// Arguments: +// objv - object to be encoded +// key - key to encode it with +// +- (void)encodeObject:(id)objv forKey:(NSString *)key { + // Sanity checks + if (!objv) return; + [self checkForKey:key]; + + // Set up a new dictionary for the current object + NSMutableDictionary *curDictionary = dictionary_; + dictionary_ = [[NSMutableDictionary alloc] initWithCapacity:0]; + + // If objv responds to unitTestEncodeState get it to record + // its data. + if ([objv respondsToSelector:@selector(unitTestEncodeState:)]) { + [objv unitTestEncodeState:self]; + } + + // We then send out a notification to let other folks + // add data for this object + NSDictionary *notificationDict = [NSDictionary dictionaryWithObject:self + forKey:GTMUnitTestingEncoderKey]; + [[NSNotificationCenter defaultCenter] postNotificationName:GTMUnitTestingEncodedObjectNotification + object:objv + userInfo:notificationDict]; + + // If we got anything from the object, or from the notification, store it in + // our dictionary. Otherwise store the description. + if ([dictionary_ count] > 0) { + [curDictionary setObject:dictionary_ forKey:key]; + } else { + NSString *description = [objv description]; + // If description has a pointer value in it, we don't want to store it + // as the pointer value can change from run to run + if (description && [description rangeOfString:@"0x"].length == 0) { + [curDictionary setObject:description forKey:key]; + } else { + NSAssert1(NO, @"Unable to encode forKey: %@", key); + } + } + [dictionary_ release]; + dictionary_ = curDictionary; +} + +// Basic encoding methods for POD types. +// +// Arguments: +// *v - value to encode +// key - key to encode it in +- (void)encodeConditionalObject:(id)objv forKey:(NSString *)key { + [self checkForKey:key]; + [self encodeObject:(id)objv forKey:key]; +} + +- (void)encodeBool:(BOOL)boolv forKey:(NSString *)key { + [self checkForKey:key]; + [dictionary_ setObject:[NSNumber numberWithBool:boolv] forKey:key]; +} + +- (void)encodeInt:(int)intv forKey:(NSString *)key { + [self checkForKey:key]; + [dictionary_ setObject:[NSNumber numberWithInt:intv] forKey:key]; +} + +- (void)encodeInt32:(int32_t)intv forKey:(NSString *)key { + [self checkForKey:key]; + [dictionary_ setObject:[NSNumber numberWithLong:intv] forKey:key]; +} + +- (void)encodeInt64:(int64_t)intv forKey:(NSString *)key { + [self checkForKey:key]; + [dictionary_ setObject:[NSNumber numberWithLongLong:intv] forKey:key]; +} + +- (void)encodeFloat:(float)realv forKey:(NSString *)key { + [self checkForKey:key]; + [dictionary_ setObject:[NSNumber numberWithFloat:realv] forKey:key]; +} + +- (void)encodeDouble:(double)realv forKey:(NSString *)key { + [self checkForKey:key]; + [dictionary_ setObject:[NSNumber numberWithDouble:realv] forKey:key]; +} + +- (void)encodeBytes:(const uint8_t *)bytesp length:(unsigned)lenv forKey:(NSString *)key { + [self checkForKey:key]; + [dictionary_ setObject:[NSData dataWithBytesNoCopy:(uint8_t*)bytesp length:lenv] forKey:key]; +} + +// Get our storage back as an NSDictionary +// +// Returns: +// NSDictionary containing our encoded info +-(NSDictionary*)dictionary { + return [[dictionary_ retain] autorelease]; +} + +@end + + +@implementation NSObject (GTMUnitTestingAdditions) + +// GTM_METHOD_CHECK(NSWorkspace, themeAppearance); +// GTM_METHOD_CHECK(NSWorkspace, themeScrollBarArrowStyle); + +/// Find the path for a file named name.extension in your bundle. +// Searches for the following: +// "name.extension", +// "name.arch.extension", +// "name.arch.OSVersionMajor.extension" +// "name.arch.OSVersionMajor.OSVersionMinor.extension" +// "name.arch.OSVersionMajor.OSVersionMinor.OSVersion.bugfix.extension" +// "name.arch.OSVersionMajor.extension" +// "name.OSVersionMajor.arch.extension" +// "name.OSVersionMajor.OSVersionMinor.arch.extension" +// "name.OSVersionMajor.OSVersionMinor.OSVersion.bugfix.arch.extension" +// "name.OSVersionMajor.extension" +// "name.OSVersionMajor.OSVersionMinor.extension" +// "name.OSVersionMajor.OSVersionMinor.OSVersion.bugfix.extension" +// Do not include the ".extension" extension on your name. +// +// Args: +// name: The name for the file you would like to find. +// extension: the extension for the file you would like to find +// +// Returns: +// the path if the file exists in your bundle +// or nil if no file is found +// +- (NSString *)pathForFileNamed:(NSString*)name extension:(NSString*)extension { + NSString *thePath = nil; + Class bundleClass = [GTMUnitTestingAdditionsBundleFinder class]; + NSBundle *myBundle = [NSBundle bundleForClass:bundleClass]; + NSAssert3(myBundle, @"Couldn't find bundle for class: %@ searching for file:%@.%@", + NSStringFromClass(bundleClass), name, extension); + + // Extensions + NSString *extensions[2]; + const NXArchInfo *localInfo = NXGetLocalArchInfo(); + NSAssert(localInfo && localInfo->name, @"Couldn't get NXArchInfo"); + extensions[0] = [NSString stringWithUTF8String:localInfo->name]; + extensions[1] = @""; + + // System Version + long major, minor, bugFix; + [GTMSystemVersion getMajor:&major minor:&minor bugFix:&bugFix]; + NSString *systemVersions[4]; + systemVersions[0] = [NSString stringWithFormat:@".%d.%d.%d", major, minor, bugFix]; + systemVersions[1] = [NSString stringWithFormat:@".%d.%d", major, minor]; + systemVersions[2] = [NSString stringWithFormat:@".%d", major]; + systemVersions[3] = @""; + + // Note that we are searching for the most exact match first. + for (int i = 0; !thePath && i < sizeof(extensions) / sizeof(*extensions); ++i) { + for (int j = 0; !thePath && j < sizeof(systemVersions) / sizeof(*systemVersions); j++) { + NSString *fullName = [NSString stringWithFormat:@"%@%@%@", name, extensions[i], systemVersions[j]]; + thePath = [myBundle pathForResource:fullName ofType:extension]; + if (thePath) break; + fullName = [NSString stringWithFormat:@"%@%@%@", name, systemVersions[j], extensions[i]]; + thePath = [myBundle pathForResource:fullName ofType:extension]; + } + } + + return thePath; +} + +- (NSString *)saveToPathForFileNamed:(NSString*)name + extension:(NSString*)extension { + NSString *newPath = nil; + const NXArchInfo *localInfo = NXGetLocalArchInfo(); + NSAssert(localInfo && localInfo->name, @"Couldn't get NXArchInfo"); + long major, minor, bugFix; + [GTMSystemVersion getMajor:&major minor:&minor bugFix:&bugFix]; + + NSString *fullName = [NSString stringWithFormat:@"%@.%s.%d.%d.%d", + name, localInfo->name, major, minor, bugFix]; + + // Is this build under Pulse? + if ([self isRunningUnderPulse]) { + // Use the Pulse base directory + newPath = [[[self pulseBaseDirectory] + stringByAppendingPathComponent:fullName] + stringByAppendingPathExtension:extension]; + } else { + // Developer build, use their home directory Desktop. + newPath = [[[NSHomeDirectory() + stringByAppendingPathComponent:@"Desktop"] + stringByAppendingPathComponent:fullName] + stringByAppendingPathExtension:extension]; + } + return newPath; +} + +#pragma mark UnitTestImage + +// Returns an image containing a representation of the object suitable for use +// in comparing against a master image. +// NB this means that all colors should be device based, as colorsynced colors +// will be different on different devices. +// +// Returns: +// an image of the object +- (NSImage*)unitTestImage { + // Must be overridden by subclasses + [NSException raise:NSInternalInconsistencyException + format:@"%@ must override -%@", + NSStringFromClass([self class]), + NSStringFromSelector(_cmd)]; + + return nil; // appease the compiler +} + +// Checks to see that system settings are valid for doing an image comparison. +// The main issue is that we make sure that we are set to using Blue Aqua as +// our appearance and that the scroll arrows are set correctly. +// +// Returns: +// YES if we can do image comparisons for this object type. +- (BOOL)areSystemSettingsValidForDoingImage { + NSWorkspace *ws = [NSWorkspace sharedWorkspace]; + BOOL isGood = YES; + + if ([self needsAquaBlueAppearanceForDoingImage] && + ![[ws gtm_themeAppearance] isEqualToString:(NSString *)kThemeAppearanceAquaBlue]) { + NSLog(@"Cannot do image test as appearance is not blue. " + "Please set it in the Appearance System Preference."); + isGood = NO; + } + + if ([self needsScrollBarArrowsLowerRightForDoingImage] && + [ws gtm_themeScrollBarArrowStyle] != kThemeScrollBarArrowsLowerRight) { + NSLog(@"Cannot do image test as scroll bar arrows are not together" + "bottom right. Please set it in the Appearance System Preference."); + isGood = NO; + } + + return isGood; +} + +// Defaults to the appearance not mattering, individual tests override. +- (BOOL)needsAquaBlueAppearanceForDoingImage { + return NO; +} + +// Defaults to the arrows not mattering, individual tests override. +- (BOOL)needsScrollBarArrowsLowerRightForDoingImage { + return NO; +} + +// Save the unitTestImage to a TIFF file with name |name| at +// ~/Desktop/|name|.tif. The TIFF will be compressed with LZW. +// +// Note: When running under Pulse automation output is redirected to the +// Pulse base directory. +// +// Args: +// name: The name for the TIFF file you would like saved. +// +// Returns: +// YES if the file was successfully saved. +// +- (BOOL)saveToTIFFNamed:(NSString*)name { + NSString *newPath = [self saveToPathForFileNamed:name extension:@"tif"]; + return [self saveToTIFFAt:newPath]; +} + +// Save unitTestImage of |self| to a TIFF file at path |path|. +// The TIFF will be compressed with LZW. +// +// Args: +// name: The name for the TIFF file you would like saved. +// +// Returns: +// YES if the file was successfully saved. +// +- (BOOL)saveToTIFFAt:(NSString*)path { + if (!path) return NO; + NSData *data = [self TIFFRepresentation]; + return [data writeToFile:path atomically:YES]; +} + +// Compares unitTestImage of |self| to the TIFF located at |path| +// +// Args: +// path: the path to the TIFF file you want to compare against. +// +// Returns: +// YES if they are equal, NO is they are not +// +- (BOOL)compareWithTIFFNamed:(NSString*)name { + NSString *path = [self pathForTIFFNamed:name]; + return [self compareWithTIFFAt:path]; +} + +// Compares unitTestImage of |self| to the TIFF located at |path| +// +// Args: +// path: the path to the TIFF file you want to compare against. +// +// Returns: +// YES if they are equal, NO is they are not +// +- (BOOL)compareWithTIFFAt:(NSString*)path { + BOOL answer = NO; + NSData *fileData = [NSData dataWithContentsOfFile:path]; + if (fileData) { + NSData *imageData = [self TIFFRepresentation]; + if (imageData) { + NSBitmapImageRep *fileRep = [NSBitmapImageRep imageRepWithData:fileData]; + if (fileRep) { + NSBitmapImageRep *imageRep = [NSBitmapImageRep imageRepWithData:imageData]; + if (imageRep) { + NSSize fileSize = [fileRep size]; + NSSize imageSize = [imageRep size]; + if (NSEqualSizes(fileSize,imageSize)) { + // if all the sizes are equal, run through the bytes and compare + // them for equality. + answer = YES; + for (int row = 0; row < fileSize.height; row++) { + for (int col = 0; col < fileSize.width && answer == YES; col++) { + NSColor *imageColor = [imageRep colorAtX:col y:row]; + NSColor *fileColor = [fileRep colorAtX:col y:row]; + + answer = [imageColor isEqual:fileColor]; + } + } + } + } + } + } + } + return answer; +} + +// Find the path for a TIFF by name in your bundle. +// Do not include the ".tif" extension on your name. +// +// Args: +// name: The name for the TIFF file you would like to find. +// +// Returns: +// the path if the TIFF exists in your bundle +// or nil if no TIFF to be found +// +- (NSString *)pathForTIFFNamed:(NSString*)name { + return [self pathForFileNamed:name extension:@"tif"]; +} + +// Gives us a LZW compressed representation of unitTestImage of |self|. +// +// Returns: +// a LZW compressed TIFF if successful +// nil if failed +// +- (NSData *)TIFFRepresentation { + NSImage *image = [self unitTestImage]; + // factor is ignored unless compression style is NSJPEGCompression + return [image TIFFRepresentationUsingCompression:NSTIFFCompressionLZW factor:0.0f]; +} + +#pragma mark UnitTestState + +static NSString* const kGTMStateFileExtension = @"gtmUTState"; + +// Save the encoded unit test state to a .gtmUTState file with name |name| at +// ~/Desktop/|name|.gtmUTState. +// +// Note: When running under Pulse automation output is redirected to the +// Pulse base directory. +// +// Args: +// name: The name for the state file you would like saved. +// +// Returns: +// YES if the file was successfully saved. +// +- (BOOL)saveToStateNamed:(NSString*)name { + NSString *newPath = [self saveToPathForFileNamed:name + extension:kGTMStateFileExtension]; + return [self saveToStateAt:newPath]; +} + +// Save encoded unit test state of |self| to a .gtmUTState file at path |path|. +// +// Args: +// name: The name for the state file you would like saved. +// +// Returns: +// YES if the file was successfully saved. +// +- (BOOL)saveToStateAt:(NSString*)path { + if (!path) return NO; + NSDictionary *dictionary = [self stateRepresentation]; + return [dictionary writeToFile:path atomically:YES]; +} + +// Compares encoded unit test state of |self| to the .gtmUTState named |name| +// +// Args: +// name: the name of the state file you want to compare against. +// +// Returns: +// YES if they are equal, NO is they are not +// +- (BOOL)compareWithStateNamed:(NSString*)name { + NSString *path = [self pathForStateNamed:name]; + return [self compareWithStateAt:path]; + +} + +// Compares encoded unit test state of |self| to the .gtmUTState located at +// |path| +// +// Args: +// path: the path to the state file you want to compare against. +// +// Returns: +// YES if they are equal, NO is they are not +// +- (BOOL)compareWithStateAt:(NSString*)path { + NSDictionary *masterDict = [NSDictionary dictionaryWithContentsOfFile:path]; + NSAssert1(masterDict, @"Unable to create dictionary from %@", path); + NSDictionary *selfDict = [self stateRepresentation]; + return [selfDict isEqualTo: masterDict]; +} + +// Find the path for a state by name in your bundle. +// Do not include the ".gtmUTState" extension. +// +// Args: +// name: The name for the state file you would like to find. +// +// Returns: +// the path if the state exists in your bundle +// or nil if no state to be found +// +- (NSString *)pathForStateNamed:(NSString*)name { + return [self pathForFileNamed:name extension:kGTMStateFileExtension]; +} + +// Gives us the encoded unit test state |self| +// +// Returns: +// the encoded state if successful +// nil if failed +// +- (NSDictionary *)stateRepresentation { + NSDictionary *dictionary = nil; + if ([self conformsToProtocol:@protocol(GTMUnitTestingEncoding)]) { + id encoder = (id)self; + GTMUnitTestingKeyedCoder *archiver = [[[GTMUnitTestingKeyedCoder alloc] init] autorelease]; + [encoder unitTestEncodeState:archiver]; + dictionary = [archiver dictionary]; + } + return dictionary; +} + +// Encodes the state of an object in a manner suitable for comparing +// against a master state file so we can determine whether the +// object is in a suitable state. Encode data in the coder in the same +// manner that you would encode data in any other Keyed NSCoder subclass. +// +// Arguments: +// inCoder - the coder to encode our state into +- (void)unitTestEncodeState:(NSCoder*)inCoder { + // Currently does nothing, but all impls of unitTestEncodeState + // should be calling [super unitTestEncodeState] as their first action. +} + +- (NSMutableArray*)unitTestExposedBindingsToIgnore { + NSMutableArray *array; + if ([[self exposedBindings] containsObject:NSFontBinding]) { + array = [NSMutableArray arrayWithObjects: + NSFontBoldBinding, NSFontFamilyNameBinding, NSFontItalicBinding, + NSFontNameBinding, NSFontSizeBinding, nil]; + } else { + array = [NSMutableArray array]; + } + return array; +} + +- (NSMutableDictionary*)unitTestExposedBindingsTestValues:(NSString*)binding { + // Always test identity + id value = [self valueForKey:binding]; + if (!value) { + value = [NSNull null]; + } + NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithObject:value + forKey:value]; + + // Now some default test values for a variety of bindings to make + // sure that we cover all the bases and save other people writing lots of + // duplicate test code. + + // If anybody can think of more to add, please go nuts. + if ([binding isEqualToString:NSAlignmentBinding]) { + [dict setObjectAndKey:[NSNumber numberWithInt:NSLeftTextAlignment]]; + [dict setObjectAndKey:[NSNumber numberWithInt:NSRightTextAlignment]]; + [dict setObjectAndKey:[NSNumber numberWithInt:NSCenterTextAlignment]]; + [dict setObjectAndKey:[NSNumber numberWithInt:NSJustifiedTextAlignment]]; + NSNumber *natural = [NSNumber numberWithInt:NSNaturalTextAlignment]; + [dict setObjectAndKey:natural]; + [dict setObject:natural forKey:[NSNumber numberWithInt:500]]; + [dict setObject:natural forKey:[NSNumber numberWithInt:-1]]; + } else if ([binding isEqualToString:NSAlternateImageBinding] || + [binding isEqualToString:NSImageBinding] || + [binding isEqualToString:NSMixedStateImageBinding] || + [binding isEqualToString:NSOffStateImageBinding] || + [binding isEqualToString:NSOnStateImageBinding]) { + // This handles all image bindings + [dict setObjectAndKey:[NSImage imageNamed:@"NSApplicationIcon"]]; + [dict setObjectAndKey:[NSNull null]]; + } else if ([binding isEqualToString:NSAnimateBinding] || + [binding isEqualToString:NSDocumentEditedBinding] || + [binding isEqualToString:NSEditableBinding] || + [binding isEqualToString:NSEnabledBinding] || + [binding isEqualToString:NSHiddenBinding] || + [binding isEqualToString:NSVisibleBinding]) { + // This handles all bool value bindings + [dict setObjectAndKey:[NSNumber numberWithBool:YES]]; + [dict setObjectAndKey:[NSNumber numberWithBool:NO]]; + } else if ([binding isEqualToString:NSAlternateTitleBinding] || + [binding isEqualToString:NSHeaderTitleBinding] || + [binding isEqualToString:NSLabelBinding] || + [binding isEqualToString:NSRepresentedFilenameBinding] || + [binding isEqualToString:NSTitleBinding] || + [binding isEqualToString:NSToolTipBinding]) { + // This handles all string value bindings + [dict setObjectAndKey:@"happy"]; + [dict setObjectAndKey:[NSNull null]]; + // Test some non-ascii roman text + char a_not_alpha[] = { 'A', 0xE2, 0x89, 0xA2, 0xCE, 0x91, '.', 0x00 }; + [dict setObjectAndKey:[NSString stringWithUTF8String:a_not_alpha]]; + // Test some korean + char hangugo[] + = { 0xED, 0x95, 0x9C, 0xEA, 0xB5, 0xAD, 0xEC, 0x96, 0xB4, 0x00 }; + [dict setObjectAndKey:[NSString stringWithUTF8String:hangugo]]; + // Test some japanese + char nihongo[] + = { 0xE6, 0x97, 0xA5, 0xE6, 0x9C, 0xAC, 0xE8, 0xAA, 0x9E, 0x00 }; + [dict setObjectAndKey:[NSString stringWithUTF8String:nihongo]]; + // Test some arabic (right to left baby! ;-) + char arabic[] = { 0xd9, 0x83, 0xd8, 0xa7, 0xd9, 0x83, 0xd8, 0xa7, 0x00 }; + [dict setObjectAndKey:[NSString stringWithUTF8String:arabic]]; + } else if ([binding isEqualToString:NSMaximumRecentsBinding] || + [binding isEqualToString:NSMaxValueBinding] || + [binding isEqualToString:NSMaxWidthBinding] || + [binding isEqualToString:NSMinValueBinding] || + [binding isEqualToString:NSMinWidthBinding] || + [binding isEqualToString:NSRecentSearchesBinding] || + [binding isEqualToString:NSRowHeightBinding] || + [binding isEqualToString:NSWidthBinding]) { + // This handles all int value bindings + [dict setObjectAndKey:[NSNumber numberWithInt:0]]; + [dict setObjectAndKey:[NSNumber numberWithInt:-1]]; + [dict setObjectAndKey:[NSNumber numberWithInt:INT16_MAX]]; + [dict setObjectAndKey:[NSNumber numberWithInt:INT16_MIN]]; + } else if ([binding isEqualToString:NSTextColorBinding]) { + // This handles all color value bindings + [dict setObjectAndKey:[NSColor colorWithDeviceWhite:1.0 alpha:1.0]]; + [dict setObjectAndKey:[NSColor colorWithDeviceWhite:1.0 alpha:0.0]]; + [dict setObjectAndKey:[NSColor colorWithDeviceWhite:1.0 alpha:0.5]]; + [dict setObjectAndKey:[NSColor colorWithCalibratedRed:0.5 green:0.5 blue:0.5 alpha:0.5]]; + [dict setObjectAndKey:[NSColor colorWithDeviceCyan:0.25 magenta:0.25 yellow:0.25 black:0.25 alpha:0.25]]; + } else if ([binding isEqualToString:NSFontBinding]) { + // This handles all font value bindings + [dict setObjectAndKey:[NSFont boldSystemFontOfSize:[NSFont systemFontSize]]]; + [dict setObjectAndKey:[NSFont toolTipsFontOfSize:[NSFont smallSystemFontSize]]]; + [dict setObjectAndKey:[NSFont labelFontOfSize:144.0]]; + } + return dict; +} + +@end + +@implementation NSMutableDictionary (GTMUnitTestingAdditions) +// Sets an object and a key to the same value in a dictionary. +- (void)setObjectAndKey:(id)objectAndKey { + [self setObject:objectAndKey forKey:objectAndKey]; +} +@end + + +@implementation NSObject (GTMUnitTestingPulseAdditions) + +- (BOOL)isRunningUnderPulse { + + if ([[[NSProcessInfo processInfo] environment] objectForKey:@"PULSE_BUILD_NUMBER"]) return YES; + return NO; + +} + +- (NSString *)pulseBaseDirectory { + + return [[[NSProcessInfo processInfo] environment] objectForKey:@"PULSE_BASE_DIR"]; + +} + +@end + + diff --git a/UnitTesting/GTMNSView+UnitTesting.h b/UnitTesting/GTMNSView+UnitTesting.h new file mode 100644 index 0000000..fcda16b --- /dev/null +++ b/UnitTesting/GTMNSView+UnitTesting.h @@ -0,0 +1,138 @@ +// +// GTMNSView+UnitTesting.h +// +// Code for making unit testing of graphics/UI easier. Generally you +// will only want to look at the macros: +// GTMAssertDrawingEqualToFile +// GTMAssertViewRepEqualToFile +// and the protocol GTMUnitTestViewDrawer. When using these routines +// make sure you are using device colors and not calibrated/generic colors +// or else your test graphics WILL NOT match across devices/graphics cards. +// +// 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 +#import "GTMNSObject+UnitTesting.h" + +@protocol GTMUnitTestViewDrawer; + +/// Fails when the |a1|'s drawing in an area |a2| does not equal the TIFF file named |a3|. +// See the description of the GTMAssertViewRepEqualToFile macro +// to understand how |a3| is found and written out. +// See the description of the GTMUnitTestView for a better idea +// how the view works. +// Implemented as a macro to match the rest of the SenTest macros. +// +// Args: +// a1: The object that implements the GTMUnitTestViewDrawer protocol +// that is doing the drawing. +// a2: The size of the drawing +// a3: The name of the TIFF file to check against. +// Do not include the extension +// a4: contextInfo to pass to drawer +// description: A format string as in the printf() function. +// Can be nil or an empty string but must be present. +// ...: A variable number of arguments to the format string. Can be absent. +// + + +#define GTMAssertDrawingEqualToFile(a1, a2, a3, a4, description, ...) \ + do { \ + id a1Object = (a1); \ + NSSize a2Size = (a2); \ + NSString* a3String = (a3); \ + void *a4ContextInfo = (a4); \ + NSRect frame = NSMakeRect(0, 0, a2Size.width, a2Size.height); \ + GTMUnitTestView *view = [[[GTMUnitTestView alloc] initWithFrame:frame drawer:a1Object contextInfo:a4ContextInfo] autorelease]; \ + GTMAssertObjectImageEqualToTIFFNamed(view, a3String, STComposeString(description, ##__VA_ARGS__)); \ + } while(0) + +// Category for making unit testing of graphics/UI easier. + +/// Allows you to take a state of a view. Supports both image and state. +// See NSObject+UnitTesting.h for details. +@interface NSView (GTMUnitTestingAdditions) + +/// Returns an image containing a representation suitable for use in comparing against a master image. +// +// NB this means that all colors should be device based. +// +// Returns: +// an image of the object +- (NSImage*)unitTestImage; + +/// Encodes the state of an object in a manner suitable for comparing against a master state file +// This enables us to determine whether the object is in a suitable state. +// +// Arguments: +// inCoder - the coder to encode our state into +- (void)unitTestEncodeState:(NSCoder*)inCoder; + +/// Returns whether unitTestEncodeState should recurse into subviews +// +// Dan Waylonis discovered that if you have "Full keyboard access" in the +// Keyboard & Mouse > Keyboard Shortcuts preferences pane set to "Text boxes +// and Lists only" that Apple adds a set of subviews to NSTextFields. So in the +// case of NSTextFields we don't want to recurse into their subviews. There may +// be other cases like this, so instead of specializing unitTestEncodeState: to +// look for NSTextFields, NSTextFields will just not allow us to recurse into +// their subviews. +// +// Returns: +// should unitTestEncodeState pick up subview state. +- (BOOL)shouldEncodeStateRecurseIntoSubviews; + +@end + +/// A view that allows you to delegate out drawing using the formal GTMUnitTestViewDelegate protocol +// This is useful when writing up unit tests for visual elements. +// Your test will often end up looking like this: +// - (void)testFoo { +// GTMAssertDrawingEqualToFile(self, NSMakeSize(200, 200), @"Foo", nil, nil); +// } +// and your testSuite will also implement the unitTestViewDrawRect method to do +// it's actual drawing. The above creates a view of size 200x200 that draws +// it's content using |self|'s unitTestViewDrawRect method and compares it to +// the contents of the file Foo.tif to make sure it's valid +@interface GTMUnitTestView : NSView { + id drawer_; // delegate for doing drawing (STRONG) + void* contextInfo_; // info passed in by user for them to use when drawing +} + +// Create a GTMUnitTestView. +// +// Args: +// rect: the area to draw. +// drawer: the object that will do the drawing via the GTMUnitTestViewDrawer +// protocol +// contextInfo: +- (id)initWithFrame:(NSRect)frame drawer:(id)drawer contextInfo:(void*)contextInfo; +@end + +/// \cond Protocols + +// Formal protocol for doing unit testing of views. See description of +// GTMUnitTestView for details. +@protocol GTMUnitTestViewDrawer + +// Draw the view. Equivalent to drawRect on a standard NSView. +// +// Args: +// rect: the area to draw. +- (void)unitTestViewDrawRect:(NSRect)rect contextInfo:(void*)contextInfo; +@end + +/// \endcond diff --git a/UnitTesting/GTMNSView+UnitTesting.m b/UnitTesting/GTMNSView+UnitTesting.m new file mode 100644 index 0000000..dd3e423 --- /dev/null +++ b/UnitTesting/GTMNSView+UnitTesting.m @@ -0,0 +1,135 @@ +// +// GTMNSView+UnitTesting.m +// +// Category for making unit testing of graphics/UI easier. +// Allows you to save a view out to a TIFF file, and compare a view +// with a previously stored representation to make sure it hasn't changed. +// +// 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 "GTMNSView+UnitTesting.h" +#import +#import "GTMGeometryUtils.h" + +// A view that allows you to delegate out drawing using the formal +// GTMUnitTestViewDelegate protocol above. This is useful when writing up unit +// tests for visual elements. +// Your test will often end up looking like this: +// - (void)testFoo { +// GTMAssertDrawingEqualToFile(self, NSMakeSize(200, 200), @"Foo", nil, nil); +// } +// and your testSuite will also implement the unitTestViewDrawRect method to do +// it's actual drawing. The above creates a view of size 200x200 that draws +// it's content using |self|'s unitTestViewDrawRect method and compares it to +// the contents of the file Foo.tif to make sure it's valid +@implementation GTMUnitTestView + +- (id)initWithFrame:(NSRect)frame drawer:(id)drawer contextInfo:(void*)contextInfo{ + self = [super initWithFrame:frame]; + if (self != nil) { + drawer_ = [drawer retain]; + contextInfo_ = contextInfo; + } + return self; +} + +- (void) dealloc { + [drawer_ release]; + [super dealloc]; +} + + +- (void)drawRect:(NSRect)rect { + [drawer_ unitTestViewDrawRect:rect contextInfo:contextInfo_]; +} + + +@end + +@implementation NSView (GTMUnitTestingAdditions) + +// Returns an image containing a representation of the object +// suitable for use in comparing against a master image. +// NB this means that all colors should be from "NSDevice" color space +// Does all of it's drawing with smoothfonts and antialiasing off +// to avoid issues with font smoothing settings and antialias differences +// between ppc and x86. +// +// Returns: +// an image of the object +- (NSImage*)unitTestImage { + // Create up a context + NSBitmapImageRep *imageRep = [self bitmapImageRepForCachingDisplayInRect:[self bounds]]; + NSGraphicsContext *bitmapContext = [NSGraphicsContext graphicsContextWithBitmapImageRep:imageRep]; + + // Store Current Context and switch to bitmap context + [NSGraphicsContext saveGraphicsState]; + [NSGraphicsContext setCurrentContext: bitmapContext]; + CGContextRef contextRef = (CGContextRef)[bitmapContext graphicsPort]; + + // Save our state and turn off font smoothing and antialias. + CGContextSaveGState(contextRef); + CGContextSetShouldSmoothFonts(contextRef, false); + CGContextSetShouldAntialias(contextRef, false); + CGContextClearRect(contextRef, GTMNSRectToCGRect([self bounds])); + [self displayRectIgnoringOpacity:[self bounds] inContext:bitmapContext]; + + // Clean up and create image + CGContextRestoreGState(contextRef); + [NSGraphicsContext restoreGraphicsState]; + NSImage *image = [[[NSImage alloc] init] autorelease]; + [image addRepresentation:imageRep]; + return image; +} + +// Returns whether unitTestEncodeState should recurse into subviews +// of a particular view. +// Dan Waylonis discovered that if you have "Full keyboard access" in the +// Keyboard & Mouse > Keyboard Shortcuts preferences pane set to "Text boxes +// and Lists only" that Apple adds a set of subviews to NSTextFields. So in the +// case of NSTextFields we don't want to recurse into their subviews. There may +// be other cases like this, so instead of specializing unitTestEncodeState: to +// look for NSTextFields, NSTextFields will just not allow us to recurse into +// their subviews. +// +// Returns: +// should unitTestEncodeState pick up subview state. +- (BOOL)shouldEncodeStateRecurseIntoSubviews { + return YES; +} + +// Encodes the state of an object in a manner suitable for comparing +// against a master state file so we can determine whether the +// object is in a suitable state. +// +// Arguments: +// inCoder - the coder to encode our state into +- (void)unitTestEncodeState:(NSCoder*)inCoder { + [super unitTestEncodeState:inCoder]; + [inCoder encodeBool:[self isHidden] forKey:@"ViewIsHidden"]; + if ([self shouldEncodeStateRecurseIntoSubviews]) { + NSEnumerator *subviewEnum = [[self subviews] objectEnumerator]; + NSView *subview = nil; + int i = 0; + while ((subview = [subviewEnum nextObject])) { + [inCoder encodeObject:subview forKey:[NSString stringWithFormat:@"ViewSubView %d", i]]; + i = i + 1; + } + } +} + +@end + diff --git a/UnitTesting/GTMSenTestCase.h b/UnitTesting/GTMSenTestCase.h new file mode 100644 index 0000000..75adc13 --- /dev/null +++ b/UnitTesting/GTMSenTestCase.h @@ -0,0 +1,429 @@ +// +// GTMSenTestCase.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. +// + +// Some extra test case macros that would have been convenient for SenTestingKit +// to provide. I didn't stick GTM in front of the Macro names, so that they would +// be easy to remember. + +#import + +// Generates a failure when a1 != noErr +// Args: +// a1: should be either an OSErr or an OSStatus +// description: A format string as in the printf() function. Can be nil or +// an empty string but must be present. +// ...: A variable number of arguments to the format string. Can be absent. +#define STAssertNoErr(a1, description, ...) \ +do { \ + @try {\ + OSStatus a1value = (a1); \ + if (a1value != noErr) { \ + NSString *_expression = [NSString stringWithFormat:@"Expected noErr, got %ld for (%s)", a1value, #a1]; \ + if (description) { \ + _expression = [NSString stringWithFormat:@"%@: %@", _expression, STComposeString(description, ##__VA_ARGS__)]; \ + } \ + [self failWithException:[NSException failureInFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:_expression]]; \ + } \ + }\ + @catch (id anException) {\ + [self failWithException:[NSException failureInRaise:[NSString stringWithFormat:@"(%s) == noErr fails", #a1] \ + exception:anException \ + inFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:STComposeString(description, ##__VA_ARGS__)]]; \ + }\ +} while(0) + +// Generates a failure when a1 != a2 +// Args: +// a1: received value. Should be either an OSErr or an OSStatus +// a2: expected value. Should be either an OSErr or an OSStatus +// description: A format string as in the printf() function. Can be nil or +// an empty string but must be present. +// ...: A variable number of arguments to the format string. Can be absent. +#define STAssertErr(a1, a2, description, ...) \ +do { \ + @try {\ + OSStatus a1value = (a1); \ + OSStatus a2value = (a2); \ + if (a1value != a2value) { \ + NSString *_expression = [NSString stringWithFormat:@"Expected %s(%ld) but got %ld for (%s)", #a2, a2value, a1value, #a1]; \ + if (description) { \ + _expression = [NSString stringWithFormat:@"%@: %@", _expression, STComposeString(description, ##__VA_ARGS__)]; \ + } \ + [self failWithException:[NSException failureInFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:_expression]]; \ + } \ + }\ + @catch (id anException) {\ + [self failWithException:[NSException failureInRaise:[NSString stringWithFormat:@"(%s) == noErr fails", #a1] \ + exception:anException \ + inFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:STComposeString(description, ##__VA_ARGS__)]]; \ + }\ +} while(0) + + +// Generates a failure when a1 is NULL +// Args: +// a1: should be a pointer (use STAssertNotNil for an object) +// description: A format string as in the printf() function. Can be nil or +// an empty string but must be present. +// ...: A variable number of arguments to the format string. Can be absent. +#define STAssertNotNULL(a1, description, ...) \ +do { \ + @try {\ + char* a1value = (char*)(a1); \ + if (a1value == NULL) { \ + NSString *_expression = [NSString stringWithFormat:@"(%s) != NULL", #a1]; \ + if (description) { \ + _expression = [NSString stringWithFormat:@"%@: %@", _expression, STComposeString(description, ##__VA_ARGS__)]; \ + } \ + [self failWithException:[NSException failureInFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:_expression]]; \ + } \ + }\ + @catch (id anException) {\ + [self failWithException:[NSException failureInRaise:[NSString stringWithFormat:@"(%s) != NULL fails", #a1] \ + exception:anException \ + inFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:STComposeString(description, ##__VA_ARGS__)]]; \ + }\ +} while(0) + +// Generates a failure when a1 is not NULL +// Args: +// a1: should be a pointer (use STAssertNil for an object) +// description: A format string as in the printf() function. Can be nil or +// an empty string but must be present. +// ...: A variable number of arguments to the format string. Can be absent. +#define STAssertNULL(a1, description, ...) \ +do { \ + @try {\ + char* a1value = (char*)(a1); \ + if (a1value != NULL) { \ + NSString *_expression = [NSString stringWithFormat:@"(%s) == NULL", #a1]; \ + if (description) { \ + _expression = [NSString stringWithFormat:@"%@: %@", _expression, STComposeString(description, ##__VA_ARGS__)]; \ + } \ + [self failWithException:[NSException failureInFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:_expression]]; \ + } \ + }\ + @catch (id anException) {\ + [self failWithException:[NSException failureInRaise:[NSString stringWithFormat:@"(%s) == NULL fails", #a1] \ + exception:anException \ + inFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:STComposeString(description, ##__VA_ARGS__)]]; \ + }\ +} while(0) + +// Generates a failure when a1 is unequal to a2. This test is for C scalars, +// structs and unions. +// Args: +// a1: argument 1 +// a2: argument 2 +// description: A format string as in the printf() function. Can be nil or +// an empty string but must be present. +// ...: A variable number of arguments to the format string. Can be absent. +#define STAssertNotEquals(a1, a2, description, ...) \ +do { \ + @try {\ + if (@encode(typeof(a1)) != @encode(typeof(a2))) { \ + [self failWithException:[NSException failureInFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:[@"Type mismatch -- " stringByAppendingString:STComposeString(description, ##__VA_ARGS__)]]]; \ + } \ + else { \ + typeof(a1) a1value = (a1); \ + typeof(a2) a2value = (a2); \ + NSValue *a1encoded = [NSValue value:&a1value withObjCType:@encode(typeof(a1))]; \ + NSValue *a2encoded = [NSValue value:&a2value withObjCType:@encode(typeof(a2))]; \ + if ([a1encoded isEqualToValue:a2encoded]) { \ + NSString *_expression = [NSString stringWithFormat:@"(%s) != (%s)", #a1, #a2]; \ + if (description) { \ + _expression = [NSString stringWithFormat:@"%@: %@", _expression, STComposeString(description, ##__VA_ARGS__)]; \ + } \ + [self failWithException:[NSException failureInFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:_expression]]; \ + } \ + } \ + } \ + @catch (id anException) {\ + [self failWithException:[NSException \ + failureInRaise:[NSString stringWithFormat:@"(%s) != (%s)", #a1, #a2] \ + exception:anException \ + inFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:STComposeString(description, ##__VA_ARGS__)]]; \ + }\ +} while(0) + +// Generates a failure when a1 is equal to a2. This test is for objects. +// Args: +// a1: argument 1. object. +// a2: argument 2. object. +// description: A format string as in the printf() function. Can be nil or +// an empty string but must be present. +// ...: A variable number of arguments to the format string. Can be absent. +#define STAssertNotEqualObjects(a1, a2, desc, ...) \ +do { \ + @try {\ + id a1value = (a1); \ + id a2value = (a2); \ + if ( (@encode(typeof(a1value)) == @encode(id)) && \ + (@encode(typeof(a2value)) == @encode(id)) && \ + ![(id)a1value isEqual:(id)a2value] ) continue; \ + NSString *_expression = [NSString stringWithFormat:@"%s('%@') != %s('%@')", #a1, [a1 description], #a2, [a2 description]]; \ + if (desc) { \ + _expression = [NSString stringWithFormat:@"%@: %@", _expression, STComposeString(desc, ##__VA_ARGS__)]; \ + } \ + [self failWithException:[NSException failureInFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:_expression]]; \ + }\ + @catch (id anException) {\ + [self failWithException:[NSException failureInRaise:[NSString stringWithFormat: @"(%s) != (%s)", #a1, #a2] \ + exception:anException \ + inFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:STComposeString(desc, ##__VA_ARGS__)]]; \ + }\ +} while(0) + +// Generates a failure when a1 is not 'op' to a2. This test is for C scalars. +// Args: +// a1: argument 1 +// a2: argument 2 +// op: operation +// description: A format string as in the printf() function. Can be nil or +// an empty string but must be present. +// ...: A variable number of arguments to the format string. Can be absent. +#define STAssertOperation(a1, a2, op, description, ...) \ +do { \ + @try {\ + if (@encode(typeof(a1)) != @encode(typeof(a2))) { \ + [self failWithException:[NSException failureInFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:[@"Type mismatch -- " stringByAppendingString:STComposeString(description, ##__VA_ARGS__)]]]; \ + } \ + else { \ + typeof(a1) a1value = (a1); \ + typeof(a2) a2value = (a2); \ + if (!(a1value op a2value)) { \ + double a1DoubleValue = a1value; \ + double a2DoubleValue = a2value; \ + NSString *_expression = [NSString stringWithFormat:@"%s (%lg) %s %s (%lg)", #a1, a1DoubleValue, #op, #a2, a2DoubleValue]; \ + if (description) { \ + _expression = [NSString stringWithFormat:@"%@: %@", _expression, STComposeString(description, ##__VA_ARGS__)]; \ + } \ + [self failWithException:[NSException failureInFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:_expression]]; \ + } \ + } \ + } \ + @catch (id anException) {\ + [self failWithException:[NSException \ + failureInRaise:[NSString stringWithFormat:@"(%s) %s (%s)", #a1, #op, #a2] \ + exception:anException \ + inFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:STComposeString(description, ##__VA_ARGS__)]]; \ + }\ +} while(0) + +// Generates a failure when a1 is not > a2. This test is for C scalars. +// Args: +// a1: argument 1 +// a2: argument 2 +// op: operation +// description: A format string as in the printf() function. Can be nil or +// an empty string but must be present. +// ...: A variable number of arguments to the format string. Can be absent. +#define STAssertGreaterThan(a1, a2, description, ...) \ + STAssertOperation(a1, a2, >, description, ##__VA_ARGS__) + +// Generates a failure when a1 is not >= a2. This test is for C scalars. +// Args: +// a1: argument 1 +// a2: argument 2 +// op: operation +// description: A format string as in the printf() function. Can be nil or +// an empty string but must be present. +// ...: A variable number of arguments to the format string. Can be absent. +#define STAssertGreaterThanOrEqual(a1, a2, description, ...) \ + STAssertOperation(a1, a2, >=, description, ##__VA_ARGS__) + +// Generates a failure when a1 is not < a2. This test is for C scalars. +// Args: +// a1: argument 1 +// a2: argument 2 +// op: operation +// description: A format string as in the printf() function. Can be nil or +// an empty string but must be present. +// ...: A variable number of arguments to the format string. Can be absent. +#define STAssertLessThan(a1, a2, description, ...) \ + STAssertOperation(a1, a2, <, description, ##__VA_ARGS__) + +// Generates a failure when a1 is not <= a2. This test is for C scalars. +// Args: +// a1: argument 1 +// a2: argument 2 +// op: operation +// description: A format string as in the printf() function. Can be nil or +// an empty string but must be present. +// ...: A variable number of arguments to the format string. Can be absent. +#define STAssertLessThanOrEqual(a1, a2, description, ...) \ + STAssertOperation(a1, a2, <=, description, ##__VA_ARGS__) + +// Generates a failure when string a1 is not equal to string a2. This call +// differs from STAssertEqualObjects in that strings that are different in +// composition (precomposed vs decomposed) will compare equal if their final +// representation is equal. +// ex O + umlaut decomposed is the same as O + umlaut composed. +// Args: +// a1: string 1 +// a2: string 2 +// description: A format string as in the printf() function. Can be nil or +// an empty string but must be present. +// ...: A variable number of arguments to the format string. Can be absent. +#define STAssertEqualStrings(a1, a2, description, ...) \ +do { \ + @try {\ + id a1value = (a1); \ + id a2value = (a2); \ + if (a1value == a2value) continue; \ + if ([a1value isKindOfClass:[NSString class]] && \ + [a2value isKindOfClass:[NSString class]] && \ + [a1value compare:a2value options:0] == NSOrderedSame) continue; \ + [self failWithException:[NSException failureInEqualityBetweenObject: a1value \ + andObject: a2value \ + inFile: [NSString stringWithUTF8String:__FILE__] \ + atLine: __LINE__ \ + withDescription: STComposeString(description, ##__VA_ARGS__)]]; \ + }\ + @catch (id anException) {\ + [self failWithException:[NSException failureInRaise:[NSString stringWithFormat: @"(%s) == (%s)", #a1, #a2] \ + exception:anException \ + inFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:STComposeString(description, ##__VA_ARGS__)]]; \ + }\ + } while(0) + +// Generates a failure when string a1 is equal to string a2. This call +// differs from STAssertEqualObjects in that strings that are different in +// composition (precomposed vs decomposed) will compare equal if their final +// representation is equal. +// ex O + umlaut decomposed is the same as O + umlaut composed. +// Args: +// a1: string 1 +// a2: string 2 +// description: A format string as in the printf() function. Can be nil or +// an empty string but must be present. +// ...: A variable number of arguments to the format string. Can be absent. +#define STAssertNotEqualStrings(a1, a2, description, ...) \ +do { \ + @try {\ + id a1value = (a1); \ + id a2value = (a2); \ + if ([a1value isKindOfClass:[NSString class]] && \ + [a2value isKindOfClass:[NSString class]] && \ + [a1value compare:a2value options:0] != NSOrderedSame) continue; \ + [self failWithException:[NSException failureInEqualityBetweenObject: a1value \ + andObject: a2value \ + inFile: [NSString stringWithUTF8String:__FILE__] \ + atLine: __LINE__ \ + withDescription: STComposeString(description, ##__VA_ARGS__)]]; \ + }\ + @catch (id anException) {\ + [self failWithException:[NSException failureInRaise:[NSString stringWithFormat: @"(%s) != (%s)", #a1, #a2] \ + exception:anException \ + inFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:STComposeString(description, ##__VA_ARGS__)]]; \ + }\ + } while(0) + +// Generates a failure when c-string a1 is not equal to c-string a2. +// Args: +// a1: string 1 +// a2: string 2 +// description: A format string as in the printf() function. Can be nil or +// an empty string but must be present. +// ...: A variable number of arguments to the format string. Can be absent. +#define STAssertEqualCStrings(a1, a2, description, ...) \ +do { \ + @try {\ + const char* a1value = (a1); \ + const char* a2value = (a2); \ + if (a1value == a2value) continue; \ + if (strcmp(a1value, a2value) == 0) continue; \ + [self failWithException:[NSException failureInEqualityBetweenObject: [NSString stringWithUTF8String:a1value] \ + andObject: [NSString stringWithUTF8String:a2value] \ + inFile: [NSString stringWithUTF8String:__FILE__] \ + atLine: __LINE__ \ + withDescription: STComposeString(description, ##__VA_ARGS__)]]; \ + }\ + @catch (id anException) {\ + [self failWithException:[NSException failureInRaise:[NSString stringWithFormat: @"(%s) == (%s)", #a1, #a2] \ + exception:anException \ + inFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:STComposeString(description, ##__VA_ARGS__)]]; \ + }\ + } while(0) + +// Generates a failure when c-string a1 is equal to c-string a2. +// Args: +// a1: string 1 +// a2: string 2 +// description: A format string as in the printf() function. Can be nil or +// an empty string but must be present. +// ...: A variable number of arguments to the format string. Can be absent. +#define STAssertNotEqualCStrings(a1, a2, description, ...) \ +do { \ + @try {\ + const char* a1value = (a1); \ + const char* a2value = (a2); \ + if (strcmp(a1value, a2value) != 0) continue; \ + [self failWithException:[NSException failureInEqualityBetweenObject: [NSString stringWithUTF8String:a1value] \ + andObject: [NSString stringWithUTF8String:a2value] \ + inFile: [NSString stringWithUTF8String:__FILE__] \ + atLine: __LINE__ \ + withDescription: STComposeString(description, ##__VA_ARGS__)]]; \ + }\ + @catch (id anException) {\ + [self failWithException:[NSException failureInRaise:[NSString stringWithFormat: @"(%s) != (%s)", #a1, #a2] \ + exception:anException \ + inFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:STComposeString(description, ##__VA_ARGS__)]]; \ + }\ + } while(0) + diff --git a/XcodeConfig/DebugTigerOrLater.xcconfig b/XcodeConfig/DebugTigerOrLater.xcconfig new file mode 100644 index 0000000..7a78a13 --- /dev/null +++ b/XcodeConfig/DebugTigerOrLater.xcconfig @@ -0,0 +1,34 @@ +// +// DebugTigerOrLater.xcconfig +// +// Xcode configuration file for building a Debug target on Tiger or later. +// +// This is a _Configuration_ Xcode config file for use in the "Based on" popup +// of the project configuration editor. Do _not_ use this as the config base +// and individual Xcode target, there are other configuration files for that +// purpose. +// +// 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. +// + +// Pull in our general Google settings +#include "subconfig/General.xcconfig" + +// Tiger or later +#include "subconfig/TigerOrLater.xcconfig" + +// Debug settings +#include "subconfig/Debug.xcconfig" + diff --git a/XcodeConfig/DebugUnittest.xcconfig b/XcodeConfig/DebugUnittest.xcconfig new file mode 100644 index 0000000..c32c63d --- /dev/null +++ b/XcodeConfig/DebugUnittest.xcconfig @@ -0,0 +1,29 @@ +// +// DebugUnittest.xcconfig +// +// Xcode configuration file for a debug unittest target. +// +// This is a _Target_ config file, for use in the "Based on" popup of the +// settings dialog for a target. Do not attempt to apply this as the base +// of an Xcode configuration in the project settings dialog. +// +// 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. +// + +// Unittests are loadable bundles +#include "subconfig/Unittest.xcconfig" + +// See comments in ReleaseUnittest.xcconfig regarding BUNDLE_LOADER and +// how it should be set if you are running into link errors. \ No newline at end of file diff --git a/XcodeConfig/LoadableBundle.xcconfig b/XcodeConfig/LoadableBundle.xcconfig new file mode 100644 index 0000000..78ed39a --- /dev/null +++ b/XcodeConfig/LoadableBundle.xcconfig @@ -0,0 +1,30 @@ +// +// LoadableBundle.xcconfig +// +// Xcode configuration file for a loadable bundle. Usually a Cocoa plugin or +// similar. +// +// This is a _Target_ config file, for use in the "Based on" popup of the +// settings dialog for a target. Do not attempt to apply this as the base +// of an Xcode configuration in the project settings dialog. +// +// 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. +// + +// Bundles should not have their external symbols stripped. +STRIP_STYLE = non-global + +// Bundles need to be position independent +GCC_DYNAMIC_NO_PIC = NO diff --git a/XcodeConfig/ReleaseTigerOrLater.xcconfig b/XcodeConfig/ReleaseTigerOrLater.xcconfig new file mode 100644 index 0000000..ee30238 --- /dev/null +++ b/XcodeConfig/ReleaseTigerOrLater.xcconfig @@ -0,0 +1,33 @@ +// +// ReleaseTigerOrLater.xcconfig +// +// Xcode configuration file for building a Release target on Tiger or later. +// +// This is a _Configuration_ Xcode config file for use in the "Based on" popup +// of the project configuration editor. Do _not_ use this as the config base +// and individual Xcode target, there are other configuration files for that +// purpose. +// +// 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. +// + +// Pull in our general Google settings +#include "subconfig/General.xcconfig" + +// Tiger or later +#include "subconfig/TigerOrLater.xcconfig" + +// Release settings +#include "subconfig/Release.xcconfig" diff --git a/XcodeConfig/ReleaseUnittest.xcconfig b/XcodeConfig/ReleaseUnittest.xcconfig new file mode 100644 index 0000000..9ec14de --- /dev/null +++ b/XcodeConfig/ReleaseUnittest.xcconfig @@ -0,0 +1,37 @@ +// +// ReleaseUnittest.xcconfig +// +// Xcode configuration file for a release unittest target. +// +// This is a _Target_ config file, for use in the "Based on" popup of the +// settings dialog for a target. Do not attempt to apply this as the base +// of an Xcode configuration in the project settings dialog. +// +// 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. +// + +// Unittests are loadable bundles +#include "subconfig/Unittest.xcconfig" + +// When running OCUnit tests in Release mode the unittests BUNDLE_LOADER +// is probably stripped (or at least it will be if they are using our +// config files). In that case the Unittest will fail to link because +// the symbols have been removed from the bundle's loader's symbol table. +// This flag tells the unittest to trust that the values will be available +// at runtime (or error out) and not to force an error at link time. +// Do NOT set BUNDLE_LOADER (Bundle Loader) setting in a release unittest target +// because you will run into interesting link issues +// "indirect symbol table entry n past the end of the symbol table" +OTHER_LDFLAGS = $(OTHER_LDFLAGS) -undefined dynamic_lookup diff --git a/XcodeConfig/SharedLibrary.xcconfig b/XcodeConfig/SharedLibrary.xcconfig new file mode 100644 index 0000000..7593392 --- /dev/null +++ b/XcodeConfig/SharedLibrary.xcconfig @@ -0,0 +1,29 @@ +// +// SharedLibrary.xcconfig +// +// Xcode configuration file for a shared library. +// +// This is a _Target_ config file, for use in the "Based on" popup of the +// settings dialog for a target. Do not attempt to apply this as the base +// of an Xcode configuration in the project settings dialog. +// +// 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. +// + +// Dynamic libs need to be position independent +GCC_DYNAMIC_NO_PIC = NO + +// Dynamic libs should not have their external symbols stripped. +STRIP_STYLE = non-global \ No newline at end of file diff --git a/XcodeConfig/StaticLibrary.xcconfig b/XcodeConfig/StaticLibrary.xcconfig new file mode 100644 index 0000000..f459e83 --- /dev/null +++ b/XcodeConfig/StaticLibrary.xcconfig @@ -0,0 +1,30 @@ +// +// StaticLibrary.xcconfig +// +// Xcode configuration file for a static library. +// +// This is a _Target_ config file, for use in the "Based on" popup of the +// settings dialog for a target. Do not attempt to apply this as the base +// of an Xcode configuration in the project settings dialog. +// +// 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. +// + +// Static libs can be included in bundles so make them position independent +GCC_DYNAMIC_NO_PIC = NO + +// Static libs should not have their internal globals or external symbols +// stripped. +STRIP_STYLE = debugging diff --git a/XcodeConfig/subconfig/CodeCoverage.xcconfig b/XcodeConfig/subconfig/CodeCoverage.xcconfig new file mode 100644 index 0000000..438d5c6 --- /dev/null +++ b/XcodeConfig/subconfig/CodeCoverage.xcconfig @@ -0,0 +1,25 @@ +// +// CodeCoverage.xcconfig +// +// Xcode configuration file for building executables that need code coverage. +// +// 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. +// + +// Static Code Coverage +#include "CodeCoverageStatic.xcconfig" + +// Need gcov library +OTHER_LDFLAGS = ${OTHER_LDFLAGS} -lgcov diff --git a/XcodeConfig/subconfig/CodeCoverageStatic.xcconfig b/XcodeConfig/subconfig/CodeCoverageStatic.xcconfig new file mode 100644 index 0000000..f6cca92 --- /dev/null +++ b/XcodeConfig/subconfig/CodeCoverageStatic.xcconfig @@ -0,0 +1,24 @@ +// +// CodeCoverageStatic.xcconfig +// +// Xcode configuration file for building static libs that need code coverage. +// +// 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. +// + + +// For measuring code coverage +GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = YES +GCC_GENERATE_TEST_COVERAGE_FILES = YES diff --git a/XcodeConfig/subconfig/Debug.xcconfig b/XcodeConfig/subconfig/Debug.xcconfig new file mode 100644 index 0000000..482d451 --- /dev/null +++ b/XcodeConfig/subconfig/Debug.xcconfig @@ -0,0 +1,40 @@ +// +// Debug.xcconfig +// +// General Xcode configuration file for Debug targets. +// +// 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. +// + +// No optimization +GCC_OPTIMIZATION_LEVEL = 0 + +// Deployment postprocessing is what triggers Xcode to strip, turn it off +DEPLOYMENT_POSTPROCESSING = NO + +// Dead code stripping off +DEAD_CODE_STRIPPING = NO + +// Debug symbols should be on obviously +GCC_GENERATE_DEBUGGING_SYMBOLS = YES + +// Define the DEBUG macro in all debug builds +OTHER_CFLAGS = $(OTHER_CFLAGS) -DDEBUG=1 + +// Turns on special C++ STL checks to "encourage" good STL use +GCC_PREPROCESSOR_DEFINITIONS = $(GCC_PREPROCESSOR_DEFINITIONS) _GLIBCXX_DEBUG_PEDANTIC _GLIBCXX_DEBUG _GLIBCPP_CONCEPT_CHECKS + +// Sets Debug info to DWARF +DEBUG_INFORMATION_FORMAT = dwarf diff --git a/XcodeConfig/subconfig/General.xcconfig b/XcodeConfig/subconfig/General.xcconfig new file mode 100644 index 0000000..b9f7313 --- /dev/null +++ b/XcodeConfig/subconfig/General.xcconfig @@ -0,0 +1,48 @@ +// +// General.xcconfig +// +// Xcode configuration file for general build settings applicable to all +// Google projects and targets. +// +// 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. +// + +// Build for PPC and Intel +ARCHS = ppc i386 + +// Zerolink prevents link warnings so turn it off +ZERO_LINK = NO + +// Prebinding considered unhelpful in 10.3 and later +PREBINDING = NO + +// Strictest warning policy +WARNING_CFLAGS = -Wall -Werror -Wendif-labels -Wnewline-eof + +// Work around Xcode bugs by using external strip. See: +// http://lists.apple.com/archives/Xcode-users/2006/Feb/msg00050.html +SEPARATE_STRIP = YES + +// Force C99 dialect +GCC_C_LANGUAGE_STANDARD = c99 + +// Obj-C exceptions are needed for @synchronized(self) +GCC_ENABLE_OBJC_EXCEPTIONS = YES + +// not sure why apple defaults this on, but it's pretty risky +ALWAYS_SEARCH_USER_PATHS = NO + +// Turn on position dependent code for most cases (overridden where appropriate) +GCC_DYNAMIC_NO_PIC = YES diff --git a/XcodeConfig/subconfig/Release.xcconfig b/XcodeConfig/subconfig/Release.xcconfig new file mode 100644 index 0000000..74df258 --- /dev/null +++ b/XcodeConfig/subconfig/Release.xcconfig @@ -0,0 +1,38 @@ +// +// Release.xcconfig +// +// General Xcode configuration file for Release targets. +// +// 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. +// + +// Optimize for space and size (Apple recommendation) +GCC_OPTIMIZATION_LEVEL = s + +// Deploment postprocessing is what triggers Xcode to strip +DEPLOYMENT_POSTPROCESSING = YES + +// No symbols +GCC_GENERATE_DEBUGGING_SYMBOLS = NO + +// Dead code strip does not affect ObjC code but can help for C +DEAD_CODE_STRIPPING = YES + +// NDEBUG is used by things like assert.h, so define it for general compat. +// ASSERT going away in release tends to create unused vars. +OTHER_CFLAGS = $(OTHER_CFLAGS) -DNDEBUG=1 -Wno-unused-variable + +// When we strip we want to strip all symbols in release, but save externals. +STRIP_STYLE = all diff --git a/XcodeConfig/subconfig/TigerOrLater.xcconfig b/XcodeConfig/subconfig/TigerOrLater.xcconfig new file mode 100644 index 0000000..d46e1a2 --- /dev/null +++ b/XcodeConfig/subconfig/TigerOrLater.xcconfig @@ -0,0 +1,29 @@ +// +// TigerOrLater.xcconfig +// +// Xcode configuration file for Google applications targeting 10.4 Tiger or +// later. These settings produce a Universal binary compatible with 10.4 for +// PPC and Intel. +// +// 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. +// + +// Default SDK and minimum OS version is 10.4 +SDKROOT = /Developer/SDKs/MacOSX10.4u.sdk +MACOSX_DEPLOYMENT_TARGET = 10.4 +GCC_VERSION = 4.0 + +// On Tiger use Obj-C fast dispatch +GCC_FAST_OBJC_DISPATCH = YES diff --git a/XcodeConfig/subconfig/Unittest.xcconfig b/XcodeConfig/subconfig/Unittest.xcconfig new file mode 100644 index 0000000..d3bdee8 --- /dev/null +++ b/XcodeConfig/subconfig/Unittest.xcconfig @@ -0,0 +1,37 @@ +// +// Unittest.xcconfig +// +// Xcode configuration file for a unittest target. +// +// This is a _Target_ config file, for use in the "Based on" popup of the +// settings dialog for a target. Do not attempt to apply this as the base +// of an Xcode configuration in the project settings dialog. +// +// 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. +// + +// Unittests are loadable bundles +#include "../LoadableBundle.xcconfig" + +// No prefix +GCC_PRECOMPILE_PREFIX_HEADER = NO +GCC_PREFIX_HEADER = + +// Force C99 dialect with GNU extensions (needed for OCUnit) +GCC_C_LANGUAGE_STANDARD = gnu99 + +// Deploment postprocessing is what triggers Xcode to strip, we don't strip +// unittests +DEPLOYMENT_POSTPROCESSING = NO -- cgit v1.2.3