aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar thomasvl <thomasvl@7dc7ac4e-7543-0410-b95c-c1676fc8e2a3>2008-01-28 20:19:42 +0000
committerGravatar thomasvl <thomasvl@7dc7ac4e-7543-0410-b95c-c1676fc8e2a3>2008-01-28 20:19:42 +0000
commit2a5219567634ab7ab74314ff3615132becadff4a (patch)
tree8e6f447544e5eaf460da741bf57771f929b4a70c
initial drop of a few sources to start things out
-rw-r--r--AppKit/GTMDelegatingTableColumn.h32
-rw-r--r--AppKit/GTMDelegatingTableColumn.m39
-rw-r--r--AppKit/GTMGeometryUtils.h560
-rw-r--r--AppKit/GTMGeometryUtils.m152
-rw-r--r--AppKit/GTMGeometryUtilsTest.m323
-rw-r--r--AppKit/GTMLinearRGBShading.h76
-rw-r--r--AppKit/GTMLinearRGBShading.m193
-rw-r--r--AppKit/GTMLinearRGBShadingTest.m94
-rw-r--r--AppKit/GTMLoginItems.h87
-rw-r--r--AppKit/GTMLoginItems.m183
-rw-r--r--AppKit/GTMLoginItemsTest.m112
-rw-r--r--AppKit/GTMNSBezierPath+CGPath.h33
-rw-r--r--AppKit/GTMNSBezierPath+CGPath.m68
-rw-r--r--AppKit/GTMNSBezierPath+CGPathTest.m78
-rw-r--r--AppKit/GTMNSBezierPath+CGPathTest.tifbin0 -> 1090 bytes
-rw-r--r--AppKit/GTMNSBezierPath+RoundRect.h45
-rw-r--r--AppKit/GTMNSBezierPath+RoundRect.m58
-rw-r--r--AppKit/GTMNSBezierPath+RoundRectTest.m93
-rw-r--r--AppKit/GTMNSBezierPath+RoundRectTest.tifbin0 -> 6982 bytes
-rw-r--r--AppKit/GTMNSBezierPath+Shading.h120
-rw-r--r--AppKit/GTMNSBezierPath+Shading.m214
-rw-r--r--AppKit/GTMNSBezierPath+ShadingTest.10.4.tifbin0 -> 12964 bytes
-rw-r--r--AppKit/GTMNSBezierPath+ShadingTest.10.5.tifbin0 -> 17982 bytes
-rw-r--r--AppKit/GTMNSBezierPath+ShadingTest.m95
-rw-r--r--AppKit/GTMNSColor+Theme.h62
-rw-r--r--AppKit/GTMNSColor+Theme.m192
-rw-r--r--AppKit/GTMNSColor+ThemeTest.m249
-rw-r--r--AppKit/GTMNSWorkspace+Theme.h39
-rw-r--r--AppKit/GTMNSWorkspace+Theme.m42
-rw-r--r--AppKit/GTMNSWorkspace+ThemeTest.m37
-rw-r--r--AppKit/GTMShading.h41
-rw-r--r--COPYING202
-rw-r--r--Foundation/GTMCalculatedRange.h101
-rw-r--r--Foundation/GTMCalculatedRange.m146
-rw-r--r--Foundation/GTMCalculatedRangeTest.m88
-rw-r--r--Foundation/GTMGarbageCollection.h43
-rw-r--r--Foundation/GTMNSData+zlib.h83
-rw-r--r--Foundation/GTMNSData+zlib.m246
-rw-r--r--Foundation/GTMNSData+zlibTest.m183
-rw-r--r--Foundation/GTMNSEnumerator+Filter.h53
-rw-r--r--Foundation/GTMNSEnumerator+Filter.m167
-rw-r--r--Foundation/GTMNSEnumerator+FilterTest.m68
-rw-r--r--Foundation/GTMNSString+HTML.h66
-rw-r--r--Foundation/GTMNSString+HTML.m514
-rw-r--r--Foundation/GTMNSString+HTMLTest.m240
-rw-r--r--Foundation/GTMNSString+XML.h39
-rw-r--r--Foundation/GTMNSString+XML.m162
-rw-r--r--Foundation/GTMNSString+XMLTest.m44
-rw-r--r--Foundation/GTMObjectSingleton.h69
-rw-r--r--Foundation/GTMSystemVersion.h45
-rw-r--r--Foundation/GTMSystemVersion.m104
-rw-r--r--Foundation/GTMSystemVersionTest.m47
-rw-r--r--GTM-Info.plist20
-rw-r--r--GTM.xcodeproj/project.pbxproj787
-rw-r--r--GTM_Prefix.pch19
-rw-r--r--ReleaseNotes.txt5
-rw-r--r--UnitTest-Info.plist20
-rw-r--r--UnitTesting/GTMNSObject+UnitTesting.h534
-rw-r--r--UnitTesting/GTMNSObject+UnitTesting.m730
-rw-r--r--UnitTesting/GTMNSView+UnitTesting.h138
-rw-r--r--UnitTesting/GTMNSView+UnitTesting.m135
-rw-r--r--UnitTesting/GTMSenTestCase.h429
-rw-r--r--XcodeConfig/DebugTigerOrLater.xcconfig34
-rw-r--r--XcodeConfig/DebugUnittest.xcconfig29
-rw-r--r--XcodeConfig/LoadableBundle.xcconfig30
-rw-r--r--XcodeConfig/ReleaseTigerOrLater.xcconfig33
-rw-r--r--XcodeConfig/ReleaseUnittest.xcconfig37
-rw-r--r--XcodeConfig/SharedLibrary.xcconfig29
-rw-r--r--XcodeConfig/StaticLibrary.xcconfig30
-rw-r--r--XcodeConfig/subconfig/CodeCoverage.xcconfig25
-rw-r--r--XcodeConfig/subconfig/CodeCoverageStatic.xcconfig24
-rw-r--r--XcodeConfig/subconfig/Debug.xcconfig40
-rw-r--r--XcodeConfig/subconfig/General.xcconfig48
-rw-r--r--XcodeConfig/subconfig/Release.xcconfig38
-rw-r--r--XcodeConfig/subconfig/TigerOrLater.xcconfig29
-rw-r--r--XcodeConfig/subconfig/Unittest.xcconfig37
76 files changed, 9307 insertions, 0 deletions
diff --git a/AppKit/GTMDelegatingTableColumn.h b/AppKit/GTMDelegatingTableColumn.h
new file mode 100644
index 0000000..a90b298
--- /dev/null
+++ b/AppKit/GTMDelegatingTableColumn.h
@@ -0,0 +1,32 @@
+//
+// GTMDelegatingTableColumn.h
+//
+// Copyright 2006-2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+
+#import <Cocoa/Cocoa.h>
+
+@interface GTMDelegatingTableColumn : NSTableColumn {
+ @private
+ IBOutlet id delegate_;
+}
+- (void)setDelegate:(id)delegate;
+- (id)delegate;
+- (id)dataCellForRow:(int)row;
+@end
+
+@interface NSObject (GTMDelegatingTableColumnDelegate)
+- (id)tableColumn:(NSTableColumn*)column dataCellForRow:(int)row;
+@end
diff --git a/AppKit/GTMDelegatingTableColumn.m b/AppKit/GTMDelegatingTableColumn.m
new file mode 100644
index 0000000..d84e2e7
--- /dev/null
+++ b/AppKit/GTMDelegatingTableColumn.m
@@ -0,0 +1,39 @@
+//
+// GTMDelegatingTableColumn.m
+//
+// Copyright 2006-2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+
+#import "GTMDelegatingTableColumn.h"
+
+@implementation GTMDelegatingTableColumn
+- (void)setDelegate:(id)delegate {
+ delegate_ = delegate;
+}
+
+- (id)delegate {
+ return delegate_;
+}
+
+- (id)dataCellForRow:(int)row {
+ id dataCell = nil;
+ if (delegate_ && [delegate_ respondsToSelector:@selector(tableColumn:dataCellForRow:)]) {
+ dataCell = [delegate_ tableColumn:self dataCellForRow:row];
+ } else {
+ dataCell = [super dataCellForRow:row];
+ }
+ return dataCell;
+}
+@end
diff --git a/AppKit/GTMGeometryUtils.h b/AppKit/GTMGeometryUtils.h
new file mode 100644
index 0000000..1dd274f
--- /dev/null
+++ b/AppKit/GTMGeometryUtils.h
@@ -0,0 +1,560 @@
+//
+// GTMGeometryUtils.h
+//
+// Utilities for geometrical utilities such as conversions
+// between different types.
+//
+// Copyright 2006-2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+
+#include <Carbon/Carbon.h>
+#include <Cocoa/Cocoa.h>
+
+
+#pragma mark Miscellaneous
+
+/// Calculate the distance between two points.
+//
+// Args:
+// pt1 first point
+// pt2 second point
+//
+// Returns:
+// Distance
+CG_INLINE float GTMDistanceBetweenPoints(NSPoint pt1, NSPoint pt2) {
+ float dX = pt1.x - pt2.x;
+ float dY = pt1.y - pt2.y;
+ return sqrtf(dX * dX + dY * dY);
+}
+
+/// Returns the height of the main display (the one with the menu bar).
+//
+/// The value updates itself automatically whenever the user
+/// repositions their monitors, or changes resolutions etc.
+//
+// Returns:
+// height of the main display area.
+float GTMGetMainDisplayHeight(void);
+
+#pragma mark -
+#pragma mark Point Conversion
+
+/// Quickly convert from a global HIPoint to a global NSPoint.
+//
+/// HIPoints are relative to 0,0 in upper left;
+/// NSPoints are relative to 0,0 in lower left
+//
+// Args:
+// inPoint: HIPoint to convert
+//
+// Returns:
+// Converted NSPoint
+CG_INLINE NSPoint GTMGlobalHIPointToNSPoint(HIPoint inPoint) {
+ return NSMakePoint(inPoint.x, GTMGetMainDisplayHeight() - inPoint.y);
+}
+
+/// Quickly convert from a global NSPoint to a global HIPoint.
+//
+/// HIPoints are relative to 0,0 in upper left;
+/// NSPoints are relative to 0,0 in lower left
+//
+// Args:
+// inPoint: NSPoint to convert
+//
+// Returns:
+// Converted HIPoint
+CG_INLINE HIPoint GTMGlobalNSPointToHIPoint(NSPoint inPoint) {
+ return CGPointMake(inPoint.x, GTMGetMainDisplayHeight() - inPoint.y);
+}
+
+/// Quickly convert from a CGPoint to a NSPoint.
+//
+/// CGPoints are relative to 0,0 in lower left;
+/// NSPoints are relative to 0,0 in lower left
+//
+// Args:
+// inPoint: CGPoint to convert
+//
+// Returns:
+// Converted NSPoint
+CG_INLINE NSPoint GTMCGPointToNSPoint(CGPoint inPoint) {
+ return NSMakePoint(inPoint.x, inPoint.y);
+}
+
+/// Quickly convert from a NSPoint to a CGPoint.
+//
+/// CGPoints are relative to 0,0 in lower left;
+/// NSPoints are relative to 0,0 in lower left
+//
+// Args:
+// inPoint: NSPoint to convert
+//
+// Returns:
+// Converted CGPoint
+CG_INLINE CGPoint GTMNSPointToCGPoint(NSPoint inPoint) {
+ return CGPointMake(inPoint.x, inPoint.y);
+}
+
+/// Quickly convert from a global HIPoint to a global CGPoint.
+//
+/// HIPoints are relative to 0,0 in upper left;
+/// CGPoints are relative to 0,0 in lower left
+//
+// Args:
+// inPoint: NSPoint to convert
+//
+// Returns:
+// Converted CGPoint
+CG_INLINE HIPoint GTMGlobalCGPointToHIPoint(CGPoint inPoint) {
+ return GTMGlobalNSPointToHIPoint(GTMCGPointToNSPoint(inPoint));
+}
+
+/// Quickly convert from a global CGPoint to a global HIPoint.
+//
+/// HIPoints are relative to 0,0 in upper left;
+/// CGPoints are relative to 0,0 in lower left
+//
+// Args:
+// inPoint: CGPoint to convert
+//
+// Returns:
+// Converted NSPoint
+CG_INLINE CGPoint GTMGlobalHIPointToCGPoint(HIPoint inPoint) {
+ return GTMNSPointToCGPoint(GTMGlobalHIPointToNSPoint(inPoint));
+}
+
+#pragma mark -
+#pragma mark Rect Conversion
+
+/// Convert from a global NSRect to a global HIRect.
+//
+/// HIRect are relative to 0,0 in upper left;
+/// NSRect are relative to 0,0 in lower left
+//
+// Args:
+// inRect: NSRect to convert
+//
+// Returns:
+// Converted HIRect
+HIRect GTMGlobalNSRectToHIRect(NSRect inRect);
+
+/// Convert from a rect to a HIRect.
+//
+/// HIRect are relative to 0,0 in upper left;
+/// Rect are relative to 0,0 in upper left
+//
+// Args:
+// inRect: Rect to convert
+//
+// Returns:
+// Converted HIRect
+HIRect GTMRectToHIRect(Rect inRect);
+
+/// Convert from a global HIRect to a global NSRect.
+//
+/// NSRect are relative to 0,0 in lower left;
+/// HIRect are relative to 0,0 in upper left
+//
+// Args:
+// inRect: HIRect to convert
+//
+// Returns:
+// Converted NSRect
+NSRect GTMGlobalHIRectToNSRect(HIRect inRect);
+
+
+/// Convert from a HIRect to a Rect.
+//
+/// Rect are relative to 0,0 in upper left;
+/// HIRect are relative to 0,0 in upper left
+//
+// Args:
+// inRect: HIRect to convert
+//
+// Returns:
+// Converted Rect
+Rect GTMHIRectToRect(HIRect inRect);
+
+/// Convert from a global Rect to a global NSRect.
+//
+/// NSRect are relative to 0,0 in lower left;
+/// Rect are relative to 0,0 in upper left
+//
+// Args:
+// inRect: Rect to convert
+//
+// Returns:
+// Converted NSRect
+CG_INLINE NSRect GTMGlobalRectToNSRect(Rect inRect) {
+ return GTMGlobalHIRectToNSRect(GTMRectToHIRect(inRect));
+}
+
+/// Convert from a CGRect to a NSRect.
+//
+/// NSRect are relative to 0,0 in lower left;
+/// CGRect are relative to 0,0 in lower left
+//
+// Args:
+// inRect: CGRect to convert
+//
+// Returns:
+// Converted NSRect
+CG_INLINE NSRect GTMCGRectToNSRect(CGRect inRect) {
+ return NSMakeRect(inRect.origin.x,inRect.origin.y,inRect.size.width,inRect.size.height);
+}
+
+/// Convert from a NSRect to a CGRect.
+//
+/// NSRect are relative to 0,0 in lower left;
+/// CGRect are relative to 0,0 in lower left
+//
+// Args:
+// inRect: NSRect to convert
+//
+// Returns:
+// Converted CGRect
+CG_INLINE CGRect GTMNSRectToCGRect(NSRect inRect) {
+ return CGRectMake(inRect.origin.x,inRect.origin.y,inRect.size.width,inRect.size.height);
+}
+
+/// Convert from a global HIRect to a global CGRect.
+//
+/// HIRect are relative to 0,0 in upper left;
+/// CGRect are relative to 0,0 in lower left
+//
+// Args:
+// inRect: HIRect to convert
+//
+// Returns:
+// Converted CGRect
+CG_INLINE CGRect GTMGlobalHIRectToCGRect(HIRect inRect) {
+ return GTMNSRectToCGRect(GTMGlobalHIRectToNSRect(inRect));
+}
+
+/// Convert from a global Rect to a global CGRect.
+//
+/// Rect are relative to 0,0 in upper left;
+/// CGRect are relative to 0,0 in lower left
+//
+// Args:
+// inRect: Rect to convert
+//
+// Returns:
+// Converted CGRect
+CG_INLINE CGRect GTMGlobalRectToCGRect(Rect inRect) {
+ return GTMNSRectToCGRect(GTMGlobalRectToNSRect(inRect));
+}
+
+/// Convert from a global NSRect to a global Rect.
+//
+/// Rect are relative to 0,0 in upper left;
+/// NSRect are relative to 0,0 in lower left
+//
+// Args:
+// inRect: NSRect to convert
+//
+// Returns:
+// Converted Rect
+CG_INLINE Rect GTMGlobalNSRectToRect(NSRect inRect) {
+ return GTMHIRectToRect(GTMGlobalNSRectToHIRect(inRect));
+}
+
+/// Convert from a global CGRect to a global HIRect.
+//
+/// HIRect are relative to 0,0 in upper left;
+/// CGRect are relative to 0,0 in lower left
+//
+// Args:
+// inRect: CGRect to convert
+//
+// Returns:
+// Converted HIRect
+CG_INLINE HIRect GTMGlobalCGRectToHIRect(CGRect inRect) {
+ return GTMGlobalNSRectToHIRect(GTMCGRectToNSRect(inRect));
+}
+
+/// Convert from a global CGRect to a global Rect.
+//
+/// Rect are relative to 0,0 in upper left;
+/// CGRect are relative to 0,0 in lower left
+//
+// Args:
+// inRect: CGRect to convert
+//
+// Returns:
+// Converted Rect
+CG_INLINE Rect GTMGlobalCGRectToRect(CGRect inRect) {
+ return GTMHIRectToRect(GTMGlobalCGRectToHIRect(inRect));
+}
+
+#pragma mark -
+#pragma mark Size Conversion
+
+/// Convert from a CGSize to an NSSize.
+//
+// Args:
+// inSize: CGSize to convert
+//
+// Returns:
+// Converted NSSize
+CG_INLINE NSSize GTMCGSizeToNSSize(CGSize inSize) {
+ return NSMakeSize(inSize.width, inSize.height);
+}
+
+/// Convert from a NSSize to a CGSize.
+//
+// Args:
+// inSize: NSSize to convert
+//
+// Returns:
+// Converted CGSize
+CG_INLINE CGSize GTMNSSizeToCGSize(NSSize inSize) {
+ return CGSizeMake(inSize.width, inSize.height);
+}
+
+#pragma mark -
+#pragma mark Point On Rect
+
+/// Return middle of left side of rectangle
+//
+// Args:
+// rect - rectangle
+//
+// Returns:
+// point located in the middle of left side of rect
+CG_INLINE NSPoint GTMNSMidLeft(NSRect rect) {
+ return NSMakePoint(NSMinX(rect), NSMidY(rect));
+}
+
+/// Return middle of right side of rectangle
+//
+// Args:
+// rect - rectangle
+//
+// Returns:
+// point located in the middle of right side of rect
+CG_INLINE NSPoint GTMNSMidRight(NSRect rect) {
+ return NSMakePoint(NSMaxX(rect), NSMidY(rect));
+}
+
+/// Return middle of top side of rectangle
+//
+// Args:
+// rect - rectangle
+//
+// Returns:
+// point located in the middle of top side of rect
+CG_INLINE NSPoint GTMNSMidTop(NSRect rect) {
+ return NSMakePoint(NSMidX(rect), NSMaxY(rect));
+}
+
+/// Return middle of bottom side of rectangle
+//
+// Args:
+// rect - rectangle
+//
+// Returns:
+// point located in the middle of bottom side of rect
+CG_INLINE NSPoint GTMNSMidBottom(NSRect rect) {
+ return NSMakePoint(NSMidX(rect), NSMinY(rect));
+}
+
+/// Return center of rectangle
+//
+// Args:
+// rect - rectangle
+//
+// Returns:
+// point located in the center of rect
+CG_INLINE NSPoint GTMNSCenter(NSRect rect) {
+ return NSMakePoint(NSMidX(rect), NSMidY(rect));
+}
+
+/// Return middle of left side of rectangle
+//
+// Args:
+// rect - rectangle
+//
+// Returns:
+// point located in the middle of left side of rect
+CG_INLINE CGPoint GTMCGMidLeft(CGRect rect) {
+ return CGPointMake(CGRectGetMinX(rect), CGRectGetMidY(rect));
+}
+
+/// Return middle of right side of rectangle
+//
+// Args:
+// rect - rectangle
+//
+// Returns:
+// point located in the middle of right side of rect
+CG_INLINE CGPoint GTMCGMidRight(CGRect rect) {
+ return CGPointMake(CGRectGetMaxX(rect), CGRectGetMidY(rect));
+}
+
+/// Return middle of top side of rectangle
+//
+// Args:
+// rect - rectangle
+//
+// Returns:
+// point located in the middle of top side of rect
+CG_INLINE CGPoint GTMCGMidTop(CGRect rect) {
+ return CGPointMake(CGRectGetMidX(rect), CGRectGetMaxY(rect));
+}
+
+/// Return middle of bottom side of rectangle
+//
+// Args:
+// rect - rectangle
+//
+// Returns:
+// point located in the middle of bottom side of rect
+CG_INLINE CGPoint GTMCGMidBottom(CGRect rect) {
+ return CGPointMake(CGRectGetMidX(rect), CGRectGetMinY(rect));
+}
+
+/// Return center of rectangle
+//
+// Args:
+// rect - rectangle
+//
+// Returns:
+// point located in the center of rect
+CG_INLINE CGPoint GTMCGCenter(CGRect rect) {
+ return CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect));
+}
+
+#pragma mark -
+#pragma mark Rect-Size Conversion
+
+/// Return size of rectangle
+//
+// Args:
+// rect - rectangle
+//
+// Returns:
+// size of rectangle
+CG_INLINE NSSize GTMNSRectSize(NSRect rect) {
+ return NSMakeSize(NSWidth(rect), NSHeight(rect));
+}
+
+/// Return size of rectangle
+//
+// Args:
+// rect - rectangle
+//
+// Returns:
+// size of rectangle
+CG_INLINE CGSize GTMCGRectSize(CGRect rect) {
+ return CGSizeMake(CGRectGetWidth(rect), CGRectGetHeight(rect));
+}
+
+/// Return rectangle of size
+//
+// Args:
+// size - size
+//
+// Returns:
+// rectangle of size (origin 0,0)
+CG_INLINE NSRect GTMNSRectOfSize(NSSize size) {
+ return NSMakeRect(0.0f, 0.0f, size.width, size.height);
+}
+
+/// Return rectangle of size
+//
+// Args:
+// size - size
+//
+// Returns:
+// rectangle of size (origin 0,0)
+CG_INLINE CGRect GTMCGRectOfSize(CGSize size) {
+ return CGRectMake(0.0f, 0.0f, size.width, size.height);
+}
+
+#pragma mark -
+#pragma mark Rect Scaling and Alignment
+
+/// Scales an NSRect
+//
+// Args:
+// inRect: Rect to scale
+// xScale: fraction to scale (1.0 is 100%)
+// yScale: fraction to scale (1.0 is 100%)
+//
+// Returns:
+// Converted Rect
+CG_INLINE NSRect GTMNSRectScale(NSRect inRect, float xScale, float yScale) {
+ return NSMakeRect(inRect.origin.x, inRect.origin.y,
+ inRect.size.width * xScale, inRect.size.height * yScale);
+}
+
+/// Scales an CGRect
+//
+// Args:
+// inRect: Rect to scale
+// xScale: fraction to scale (1.0 is 100%)
+// yScale: fraction to scale (1.0 is 100%)
+//
+// Returns:
+// Converted Rect
+CG_INLINE CGRect GTMCGRectScale(CGRect inRect, float xScale, float yScale) {
+ return CGRectMake(inRect.origin.x, inRect.origin.y,
+ inRect.size.width * xScale, inRect.size.height * yScale);
+}
+
+/// Align rectangles
+//
+// Args:
+// alignee - rect to be aligned
+// aligner - rect to be aligned from
+NSRect GTMAlignRectangles(NSRect alignee, NSRect aligner,
+ NSImageAlignment alignment);
+
+/// Align rectangles
+//
+// Args:
+// alignee - rect to be aligned
+// aligner - rect to be aligned from
+// alignment - way to align the rectangles
+CG_INLINE CGRect GTMCGAlignRectangles(CGRect alignee, CGRect aligner,
+ NSImageAlignment alignment) {
+ return GTMNSRectToCGRect(GTMAlignRectangles(GTMCGRectToNSRect(alignee),
+ GTMCGRectToNSRect(aligner),
+ alignment));
+}
+
+/// Scale rectangle
+//
+// Args:
+// scalee - rect to be scaled
+// size - size to scale to
+// scaling - way to scale the rectangle
+NSRect GTMScaleRectangleToSize(NSRect scalee, NSSize size,
+ NSImageScaling scaling);
+
+/// Scale rectangle
+//
+// Args:
+// scalee - rect to be scaled
+// size - size to scale to
+// scaling - way to scale the rectangle
+CG_INLINE CGRect GTMCGScaleRectangleToSize(CGRect scalee, CGSize size,
+ NSImageScaling scaling) {
+ return GTMNSRectToCGRect(GTMScaleRectangleToSize(GTMCGRectToNSRect(scalee),
+ GTMCGSizeToNSSize(size),
+ scaling));
+}
+
diff --git a/AppKit/GTMGeometryUtils.m b/AppKit/GTMGeometryUtils.m
new file mode 100644
index 0000000..efc543e
--- /dev/null
+++ b/AppKit/GTMGeometryUtils.m
@@ -0,0 +1,152 @@
+//
+// GTMGeometryUtils.m
+//
+// Copyright 2006-2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+
+#import "GTMGeometryUtils.h"
+
+float GTMGetMainDisplayHeight(void) {
+ float height = 0;
+ NSArray *screens = [NSScreen screens];
+ // We may have a headless machine without any screens. In this case we
+ // return 0.
+ if ([screens count] > 0) {
+ height = NSHeight([(NSScreen*)[screens objectAtIndex: 0] frame]);
+ }
+ return height;
+}
+
+
+// Rect conversion routines.
+HIRect GTMGlobalNSRectToHIRect(NSRect inRect) {
+ HIRect theRect;
+ theRect.origin = GTMGlobalNSPointToHIPoint(inRect.origin);
+ theRect.origin.y -= inRect.size.height;
+ theRect.size = CGSizeMake(inRect.size.width, inRect.size.height);
+ return theRect;
+}
+
+
+HIRect GTMRectToHIRect(Rect inRect) {
+ HIRect theRect;
+ theRect.origin = CGPointMake(inRect.left,inRect.top);
+ theRect.size = CGSizeMake(inRect.right - inRect.left, inRect.bottom - inRect.top);
+ return theRect;
+}
+
+
+NSRect GTMGlobalHIRectToNSRect(HIRect inRect) {
+ NSRect theRect;
+ theRect.origin = GTMGlobalHIPointToNSPoint(inRect.origin);
+ theRect.origin.y -= inRect.size.height;
+ theRect.size = NSMakeSize(inRect.size.width, inRect.size.height);
+ return theRect;
+}
+
+
+Rect GTMHIRectToRect(HIRect inRect) {
+ Rect theRect;
+ theRect.left = inRect.origin.x;
+ theRect.right = ceilf(inRect.origin.x + inRect.size.width);
+ theRect.top = inRect.origin.y;
+ theRect.bottom = ceilf(inRect.origin.y + inRect.size.height);
+ return theRect;
+}
+
+/// Align rectangles
+//
+// Args:
+// alignee - rect to be aligned
+// aligner - rect to be aligned to
+// alignment - alignment to be applied to alignee based on aligner
+
+NSRect GTMAlignRectangles(NSRect alignee, NSRect aligner, NSImageAlignment alignment) {
+ switch (alignment) {
+ case NSImageAlignTop:
+ alignee.origin.x = aligner.origin.x + (NSWidth(aligner) * .5f - NSWidth(alignee) * .5f);
+ alignee.origin.y = aligner.origin.y + NSHeight(aligner) - NSHeight(alignee);
+ break;
+
+ case NSImageAlignTopLeft:
+ alignee.origin.x = aligner.origin.x;
+ alignee.origin.y = aligner.origin.y + NSHeight(aligner) - NSHeight(alignee);
+ break;
+
+ case NSImageAlignTopRight:
+ alignee.origin.x = aligner.origin.x + NSWidth(aligner) - NSWidth(alignee);
+ alignee.origin.y = aligner.origin.y + NSHeight(aligner) - NSHeight(alignee);
+ break;
+
+ case NSImageAlignLeft:
+ alignee.origin.x = aligner.origin.x;
+ alignee.origin.y = aligner.origin.y + (NSHeight(aligner) * .5f - NSHeight(alignee) * .5f);
+ break;
+
+ case NSImageAlignBottomLeft:
+ alignee.origin.x = aligner.origin.x;
+ alignee.origin.y = aligner.origin.y;
+ break;
+
+ case NSImageAlignBottom:
+ alignee.origin.x = aligner.origin.x + (NSWidth(aligner) * .5f - NSWidth(alignee) * .5f);
+ alignee.origin.y = aligner.origin.y;
+ break;
+
+ case NSImageAlignBottomRight:
+ alignee.origin.x = aligner.origin.x + NSWidth(aligner) - NSWidth(alignee);
+ alignee.origin.y = aligner.origin.y;
+ break;
+
+ case NSImageAlignRight:
+ alignee.origin.x = aligner.origin.x + NSWidth(aligner) - NSWidth(alignee);
+ alignee.origin.y = aligner.origin.y + (NSHeight(aligner) * .5f - NSHeight(alignee) * .5f);
+ break;
+
+ default:
+ case NSImageAlignCenter:
+ alignee.origin.x = aligner.origin.x + (NSWidth(aligner) * .5f - NSWidth(alignee) * .5f);
+ alignee.origin.y = aligner.origin.y + (NSHeight(aligner) * .5f - NSHeight(alignee) * .5f);
+ break;
+ }
+ return alignee;
+}
+
+NSRect GTMScaleRectangleToSize(NSRect scalee, NSSize size, NSImageScaling scaling) {
+ switch (scaling) {
+ case NSScaleProportionally: {
+ float height = NSHeight(scalee);
+ float width = NSWidth(scalee);
+ if (isnormal(height) && isnormal(width) &&
+ (height > size.height || width > size.width)) {
+ float horiz = size.width / width;
+ float vert = size.height / height;
+ float newScale = horiz < vert ? horiz : vert;
+ scalee = GTMNSRectScale(scalee, newScale, newScale);
+ }
+ break;
+ }
+
+ case NSScaleToFit:
+ scalee.size = size;
+ break;
+
+ case NSScaleNone:
+ default:
+ // Do nothing
+ break;
+ }
+ return scalee;
+}
diff --git a/AppKit/GTMGeometryUtilsTest.m b/AppKit/GTMGeometryUtilsTest.m
new file mode 100644
index 0000000..0fad449
--- /dev/null
+++ b/AppKit/GTMGeometryUtilsTest.m
@@ -0,0 +1,323 @@
+//
+// GTMGeometryUtilsTest.m
+//
+// Copyright 2006-2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+
+#import <SenTestingKit/SenTestingKit.h>
+#import "GTMGeometryUtils.h"
+
+@interface GTMGeometryUtilsTest : SenTestCase
+@end
+
+@implementation GTMGeometryUtilsTest
+
+- (void)testGTMGetMainDisplayHeight {
+ STAssertTrue(CGRectGetHeight(CGDisplayBounds(CGMainDisplayID())) == GTMGetMainDisplayHeight(), nil);
+}
+
+
+- (void)testGTMGlobalHIPointToNSPoint {
+ HIPoint hiPoint = CGPointMake(12.5,14.5);
+ NSPoint nsPoint = GTMGlobalHIPointToNSPoint(hiPoint);
+ STAssertTrue(nsPoint.x == hiPoint.x &&
+ nsPoint.y == GTMGetMainDisplayHeight() - hiPoint.y, nil);
+}
+
+
+- (void)testGTMGlobalNSPointToHIPoint {
+ NSPoint nsPoint = NSMakePoint(12.5,14.5);
+ HIPoint hiPoint = GTMGlobalNSPointToHIPoint(nsPoint);
+ STAssertTrue(nsPoint.x == hiPoint.x &&
+ nsPoint.y == GTMGetMainDisplayHeight() - hiPoint.y, nil);
+}
+
+
+- (void)testGTMGlobalCGPointToNSPoint {
+ CGPoint cgPoint = CGPointMake(15.1,6.2);
+ NSPoint nsPoint = GTMCGPointToNSPoint(cgPoint);
+ STAssertTrue(CGPointEqualToPoint(*(CGPoint*)&nsPoint, cgPoint), nil);
+
+}
+
+
+- (void)testGTMGlobalNSPointToCGPoint {
+ NSPoint nsPoint = NSMakePoint(10.2,1.5);
+ CGPoint cgPoint = GTMNSPointToCGPoint(nsPoint);
+ STAssertTrue(CGPointEqualToPoint(cgPoint, *(CGPoint*)&nsPoint), nil);
+}
+
+
+- (void)testGTMGlobalCGPointToHIPoint {
+ CGPoint cgPoint = CGPointMake(12.5,14.5);
+ HIPoint hiPoint = GTMGlobalCGPointToHIPoint(cgPoint);
+ STAssertTrue(cgPoint.x == hiPoint.x &&
+ cgPoint.y == GTMGetMainDisplayHeight() - hiPoint.y, nil);
+}
+
+
+- (void)testGTMGlobalHIPointToCGPoint {
+ HIPoint hiPoint = CGPointMake(12.5,14.5);
+ CGPoint cgPoint = GTMGlobalHIPointToCGPoint(hiPoint);
+ STAssertTrue(cgPoint.x == hiPoint.x &&
+ cgPoint.y == GTMGetMainDisplayHeight() - hiPoint.y, nil);
+}
+
+
+- (void)testGTMGlobalNSRectToHIRect {
+ NSRect nsRect = NSMakeRect(40,16,17,18);
+ HIRect hiRect = GTMGlobalNSRectToHIRect(nsRect);
+ STAssertTrue(nsRect.origin.x == hiRect.origin.x &&
+ nsRect.origin.y == GTMGetMainDisplayHeight() - hiRect.origin.y - hiRect.size.height &&
+ nsRect.size.height == hiRect.size.height &&
+ nsRect.size.width == hiRect.size.width, nil);
+}
+
+
+- (void)testGTMGlobalCGRectToHIRect {
+ CGRect cgRect = CGRectMake(40,16,17,19.0);
+ HIRect hiRect = GTMGlobalCGRectToHIRect(cgRect);
+ STAssertTrue(cgRect.origin.x == hiRect.origin.x &&
+ cgRect.origin.y == GTMGetMainDisplayHeight() - hiRect.origin.y - hiRect.size.height &&
+ cgRect.size.height == hiRect.size.height &&
+ cgRect.size.width == hiRect.size.width, nil);
+}
+
+
+- (void)testGTMGlobalHIRectToNSRect {
+ HIRect hiRect = CGRectMake(40.2,16.3,17.2,18.9);
+ NSRect nsRect = GTMGlobalHIRectToNSRect(hiRect);
+ STAssertTrue(nsRect.origin.x == hiRect.origin.x &&
+ nsRect.origin.y == GTMGetMainDisplayHeight() - hiRect.origin.y - hiRect.size.height &&
+ nsRect.size.height == hiRect.size.height &&
+ nsRect.size.width == hiRect.size.width, nil);
+}
+
+
+- (void)testGTMGlobalCGRectToNSRect {
+ CGRect cgRect = CGRectMake(1.5,2.4,10.6,11.7);
+ NSRect nsRect = GTMCGRectToNSRect(cgRect);
+ STAssertTrue(CGRectEqualToRect(cgRect, *(CGRect*)&nsRect), nil);
+}
+
+
+- (void)testGTMGlobalNSRectToCGRect {
+ NSRect nsRect = NSMakeRect(4.6,3.2,22.1,45.0);
+ CGRect cgRect = GTMNSRectToCGRect(nsRect);
+ STAssertTrue(CGRectEqualToRect(cgRect, *(CGRect*)&nsRect), nil);
+}
+
+
+- (void)testGTMGlobalHIRectToCGRect {
+ HIRect hiRect = CGRectMake(40.2,16.3,17.2,18.9);
+ CGRect cgRect = GTMGlobalHIRectToCGRect(hiRect);
+ STAssertTrue(cgRect.origin.x == hiRect.origin.x &&
+ cgRect.origin.y == GTMGetMainDisplayHeight() - hiRect.origin.y - hiRect.size.height &&
+ cgRect.size.height == hiRect.size.height &&
+ cgRect.size.width == hiRect.size.width, nil);
+}
+
+
+- (void)testGTMGlobalRectToNSRect {
+ Rect rect = { 10,50,40,60 };
+ NSRect nsRect = GTMGlobalRectToNSRect(rect);
+ HIRect hiRect1 = GTMRectToHIRect(rect);
+ HIRect hiRect2 = GTMGlobalNSRectToHIRect(nsRect);
+ STAssertTrue(CGRectEqualToRect(hiRect1,hiRect2), nil);
+}
+
+
+- (void)testGTMGlobalNSRectToRect {
+ NSRect nsRect = NSMakeRect(1.5,2.4,10.6,11.7);
+ HIRect hiRect = GTMGlobalNSRectToHIRect(nsRect);
+ Rect rect1 = GTMGlobalNSRectToRect(nsRect);
+ Rect rect2 = GTMHIRectToRect(hiRect);
+ STAssertTrue(rect1.left == rect2.left &&
+ rect1.right == rect2.right &&
+ rect1.top == rect2.top &&
+ rect1.bottom == rect2.bottom, nil);
+}
+
+
+- (void)testGTMGlobalRectToHIRect {
+ Rect rect = { 10,20,30,40 };
+ HIRect hiRect = GTMRectToHIRect(rect);
+ STAssertTrue(CGRectEqualToRect(hiRect, CGRectMake(20,10,20,20)), nil);
+}
+
+
+- (void)testGTMGlobalHIRectToRect {
+ HIRect hiRect = CGRectMake(1.5,2.4,10.6,11.7);
+ Rect rect = GTMHIRectToRect(hiRect);
+ STAssertTrue(rect.left == 1 &&
+ rect.right == 13 &&
+ rect.top == 2 &&
+ rect.bottom == 15, nil);
+}
+
+
+- (void)testGTMGlobalCGRectToRect {
+ CGRect cgRect = CGRectMake(1.5,2.4,10.6,11.7);
+ HIRect hiRect = GTMGlobalCGRectToHIRect(cgRect);
+ Rect rect1 = GTMGlobalCGRectToRect(cgRect);
+ Rect rect2 = GTMHIRectToRect(hiRect);
+ STAssertTrue(rect1.left == rect2.left &&
+ rect1.right == rect2.right &&
+ rect1.top == rect2.top &&
+ rect1.bottom == rect2.bottom, nil);
+}
+
+
+- (void)testGTMGlobalRectToCGRect {
+ Rect rect = { 10,50,40,60 };
+ CGRect nsRect = GTMGlobalRectToCGRect(rect);
+ HIRect hiRect1 = GTMRectToHIRect(rect);
+ HIRect hiRect2 = GTMGlobalCGRectToHIRect(nsRect);
+ STAssertTrue(CGRectEqualToRect(hiRect1,hiRect2), nil);
+}
+
+
+- (void)testGTMCGSizeToNSSize {
+ CGSize cgSize = {5,6};
+ NSSize nsSize = GTMCGSizeToNSSize(cgSize);
+ STAssertTrue(CGSizeEqualToSize(cgSize, *(CGSize*)&nsSize), nil);
+}
+
+
+- (void)testGTMNSSizeToCGSize {
+ NSSize nsSize = {22,15};
+ CGSize cgSize = GTMNSSizeToCGSize(nsSize);
+ STAssertTrue(CGSizeEqualToSize(cgSize, *(CGSize*)&nsSize), nil);
+}
+
+- (void)testGTMDistanceBetweenPoints {
+ NSPoint pt1 = NSMakePoint(0, 0);
+ NSPoint pt2 = NSMakePoint(3, 4);
+ STAssertEquals(GTMDistanceBetweenPoints(pt1, pt2), 5.0f, nil);
+ STAssertEquals(GTMDistanceBetweenPoints(pt2, pt1), 5.0f, nil);
+ pt1 = NSMakePoint(1, 1);
+ pt2 = NSMakePoint(1, 1);
+ STAssertEquals(GTMDistanceBetweenPoints(pt1, pt2), 0.0f, nil);
+}
+
+- (void)testGTMAlignRectangles {
+ typedef struct {
+ NSPoint expectedOrigin;
+ NSImageAlignment alignment;
+ } TestData;
+
+ TestData data[] = {
+ { {1,2}, NSImageAlignTop },
+ { {0,2}, NSImageAlignTopLeft },
+ { {2,2}, NSImageAlignTopRight },
+ { {0,1}, NSImageAlignLeft },
+ { {1,0}, NSImageAlignBottom },
+ { {0,0}, NSImageAlignBottomLeft },
+ { {2,0}, NSImageAlignBottomRight },
+ { {2,1}, NSImageAlignRight },
+ { {1,1}, NSImageAlignCenter },
+ };
+
+ NSRect rect1 = NSMakeRect(0, 0, 4, 4);
+ NSRect rect2 = NSMakeRect(0, 0, 2, 2);
+
+ for (int i = 0; i < sizeof(data) / sizeof(TestData); i++) {
+ NSRect expectedRect;
+ expectedRect.origin = data[i].expectedOrigin;
+ expectedRect.size = NSMakeSize(2, 2);
+ NSRect outRect = GTMAlignRectangles(rect2, rect1, data[i].alignment);
+ STAssertEquals(outRect, expectedRect, nil);
+ }
+}
+
+- (void)testGTMPointsOnRect {
+ NSRect rect = NSMakeRect(0, 0, 2, 2);
+ CGRect cgRect = GTMNSRectToCGRect(rect);
+
+ NSPoint point = GTMNSMidLeft(rect);
+ CGPoint cgPoint = GTMCGMidLeft(cgRect);
+ STAssertEquals(point.x, cgPoint.x, nil);
+ STAssertEquals(point.y, cgPoint.y, nil);
+ STAssertEqualsWithAccuracy(point.y, 1.0f, 0.01f, nil);
+ STAssertEqualsWithAccuracy(point.x, 0.0f, 0.01f, nil);
+
+ point = GTMNSMidRight(rect);
+ cgPoint = GTMCGMidRight(cgRect);
+ STAssertEquals(point.x, cgPoint.x, nil);
+ STAssertEquals(point.y, cgPoint.y, nil);
+ STAssertEqualsWithAccuracy(point.y, 1.0f, 0.01f, nil);
+ STAssertEqualsWithAccuracy(point.x, 2.0f, 0.01f, nil);
+
+ point = GTMNSMidTop(rect);
+ cgPoint = GTMCGMidTop(cgRect);
+ STAssertEquals(point.x, cgPoint.x, nil);
+ STAssertEquals(point.y, cgPoint.y, nil);
+ STAssertEqualsWithAccuracy(point.y, 2.0f, 0.01f, nil);
+ STAssertEqualsWithAccuracy(point.x, 1.0f, 0.01f, nil);
+
+ point = GTMNSMidBottom(rect);
+ cgPoint = GTMCGMidBottom(cgRect);
+ STAssertEquals(point.x, cgPoint.x, nil);
+ STAssertEquals(point.y, cgPoint.y, nil);
+ STAssertEqualsWithAccuracy(point.y, 0.0f, 0.01f, nil);
+ STAssertEqualsWithAccuracy(point.x, 1.0f, 0.01f, nil);
+}
+
+- (void)testGTMRectScaling {
+ NSRect rect = NSMakeRect(1.0f, 2.0f, 5.0f, 10.0f);
+ NSRect rect2 = NSMakeRect(1.0f, 2.0f, 1.0f, 12.0f);
+ STAssertEquals(GTMNSRectScale(rect, 0.2f, 1.2f),
+ rect2, nil);
+ STAssertEquals(GTMCGRectScale(GTMNSRectToCGRect(rect), 0.2f, 1.2f),
+ GTMNSRectToCGRect(rect2), nil);
+}
+
+- (void)testGTMScaleRectangleToSize {
+ NSRect rect = NSMakeRect(0.0f, 0.0f, 10.0f, 10.0f);
+ typedef struct {
+ NSSize size_;
+ NSSize newSize_;
+ } Test;
+ Test tests[] = {
+ { { 5.0, 10.0 }, { 5.0, 5.0 } },
+ { { 10.0, 5.0 }, { 5.0, 5.0 } },
+ { { 10.0, 10.0 }, { 10.0, 10.0 } },
+ { { 11.0, 11.0, }, { 10.0, 10.0 } },
+ { { 5.0, 2.0 }, { 2.0, 2.0 } },
+ { { 2.0, 5.0 }, { 2.0, 2.0 } },
+ { { 2.0, 2.0 }, { 2.0, 2.0 } },
+ { { 0.0, 10.0 }, { 0.0, 0.0 } }
+ };
+
+ for (size_t i = 0; i < sizeof(tests) / sizeof(Test); ++i) {
+ NSRect result = GTMScaleRectangleToSize(rect, tests[i].size_,
+ NSScaleProportionally);
+ STAssertEquals(result, GTMNSRectOfSize(tests[i].newSize_), @"failed on test %z", i);
+ }
+
+ NSRect result = GTMScaleRectangleToSize(NSZeroRect, tests[0].size_,
+ NSScaleProportionally);
+ STAssertEquals(result, NSZeroRect, nil);
+
+ result = GTMScaleRectangleToSize(rect, tests[0].size_,
+ NSScaleToFit);
+ STAssertEquals(result, GTMNSRectOfSize(tests[0].size_), nil);
+
+ result = GTMScaleRectangleToSize(rect, tests[0].size_,
+ NSScaleNone);
+ STAssertEquals(result, rect, nil);
+
+}
+@end
diff --git a/AppKit/GTMLinearRGBShading.h b/AppKit/GTMLinearRGBShading.h
new file mode 100644
index 0000000..f6b6405
--- /dev/null
+++ b/AppKit/GTMLinearRGBShading.h
@@ -0,0 +1,76 @@
+//
+// GTMLinearRGBShading.h
+//
+// Copyright 2006-2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+
+#import <Cocoa/Cocoa.h>
+#import "GTMShading.h"
+#import "GTMCalculatedRange.h"
+
+/// A shading that does returns smooth linear values for RGB.
+//
+/// Thus if you create a shading from 0.0->red to 1.0->blue you will get
+/// \verbatim
+/// - 0.5->purple
+/// - 0.75->eggplant
+/// - 0.25->magenta
+/// \endverbatim
+
+@interface GTMLinearRGBShading : GTMCalculatedRange <GTMShading> {
+@private
+ CGFunctionRef function_; // function used to calculated shading (STRONG)
+ CGColorSpaceRef colorSpace_; // colorspace used for shading (STRONG)
+ BOOL isCalibrated_; // are we using calibrated or device RGB.
+ float colorValue[4]; // the RGBA color values
+}
+
+/// Generate a shading with color |begin| at position 0.0 and color |end| at 1.0.
+//
+// Args:
+// begin: color at beginning of range
+// end: color at end of range
+// colorSpaceName: name of colorspace to draw into must be either
+// NSCalibratedRGBColorSpace or NSDeviceRGBColorSpace
+//
+// Returns:
+// a GTMLinearRGBShading
++ (id)shadingFromColor:(NSColor *)begin toColor:(NSColor *)end
+ fromSpaceNamed:(NSString*)colorSpaceName;
+
+/// Generate a shading with a collection of colors at various positions.
+//
+// Args:
+// colors: a C style array containg the colors we are adding
+// colorSpaceName: name of colorspace to draw into must be either
+// NSCalibratedRGBColorSpace or NSDeviceRGBColorSpace
+// positions: a C style array containg the positions we want to
+// add the colors at
+// numberOfColors: how many colors/positions we are adding
+//
+// Returns:
+// a GTMLinearRGBShading
++ (id)shadingWithColors:(NSColor **)colors
+ fromSpaceNamed:(NSString*)colorSpaceName
+ atPositions:(float *)positions
+ count:(unsigned int)numberOfColors;
+
+/// Designated initializer
+// Args:
+// colorSpaceName - name of the colorspace to use must be either
+// NSCalibratedRGBColorSpace or NSDeviceRGBColorSpace
+- (id)initWithColorSpaceName:(NSString*)colorSpaceName;
+
+@end
diff --git a/AppKit/GTMLinearRGBShading.m b/AppKit/GTMLinearRGBShading.m
new file mode 100644
index 0000000..ab5bda6
--- /dev/null
+++ b/AppKit/GTMLinearRGBShading.m
@@ -0,0 +1,193 @@
+//
+// GTMLinearRGBShading.m
+//
+// Copyright 2006-2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+
+#import "GTMLinearRGBShading.h"
+
+// Carbon callback function required for CoreGraphics
+static void cShadeFunction(void *info, const float *inPos, float *outVals);
+
+@implementation GTMLinearRGBShading
++ (id)shadingFromColor:(NSColor *)begin toColor:(NSColor *)end
+ fromSpaceNamed:(NSString*)colorSpaceName {
+ NSColor *theColors[] = { begin, end };
+ float thePositions[] = { 0.0f, 1.0f };
+ return [[self class] shadingWithColors:theColors
+ fromSpaceNamed:colorSpaceName
+ atPositions:thePositions
+ count:(sizeof(thePositions)/sizeof(float))];
+}
+
++ (id)shadingWithColors:(NSColor **)colors fromSpaceNamed:(NSString*)colorSpaceName
+ atPositions:(float *)positions count:(unsigned)count {
+
+ GTMLinearRGBShading *theShading = [[[[self class] alloc] initWithColorSpaceName:colorSpaceName] autorelease];
+ for (unsigned int i = 0; i < count; ++i) {
+ [theShading insertStop:colors[i] atPosition:positions[i]];
+ }
+ return theShading;
+}
+
+- (id)initWithColorSpaceName:(NSString*)colorSpaceName {
+ if ((self = [super init])) {
+ if ([colorSpaceName isEqualToString:NSDeviceRGBColorSpace]) {
+ isCalibrated_ = NO;
+ } else if ([colorSpaceName isEqualToString:NSCalibratedRGBColorSpace]) {
+ isCalibrated_ = YES;
+ }
+ else {
+ [self dealloc];
+ self = nil;
+ }
+ }
+ return self;
+}
+
+- (void) dealloc {
+ if (nil != function_) {
+ CGFunctionRelease(function_);
+ }
+ if (nil != colorSpace_) {
+ CGColorSpaceRelease(colorSpace_);
+ }
+ [super dealloc];
+}
+
+
+- (void)insertStop:(id)item atPosition:(float)position {
+ NSString *colorSpaceName = isCalibrated_ ? NSCalibratedRGBColorSpace : NSDeviceRGBColorSpace;
+ NSColor *tempColor = [item colorUsingColorSpaceName: colorSpaceName];
+ if (nil != tempColor) {
+ [super insertStop:tempColor atPosition:position];
+ }
+}
+
+// Calculate a linear value based on our stops
+- (id)valueAtPosition:(float)position {
+ unsigned int index = 0;
+ unsigned int colorCount = [self stopCount];
+ float stop1Position = 0.0f;
+ NSColor *stop1Color = [self stopAtIndex:index position:&stop1Position];
+ index += 1;
+ float stop2Position = 0.0f;
+ NSColor *stop2Color = nil;
+ NSColor *theColor = nil;
+ if (colorCount > 1) {
+ stop2Color = [self stopAtIndex:index position:&stop2Position];
+ index += 1;
+ } else {
+ // if we only have one value, that's what we return
+ stop2Position = stop1Position;
+ stop2Color = stop1Color;
+ }
+
+ while (index < colorCount && stop2Position < position) {
+ stop1Color = stop2Color;
+ stop1Position = stop2Position;
+ stop2Color = [self stopAtIndex:index position:&stop2Position];
+ index += 1;
+ }
+
+ if (position <= stop1Position) {
+ // if we are less than our lowest position, return our first color
+ theColor = stop1Color;
+ [stop1Color getRed:&colorValue[0] green:&colorValue[1]
+ blue:&colorValue[2] alpha:&colorValue[3]];
+ } else if (position >= stop2Position) {
+ // likewise if we are greater than our highest position, return the last color
+ [stop2Color getRed:&colorValue[0] green:&colorValue[1]
+ blue:&colorValue[2] alpha:&colorValue[3]];
+ } else {
+ // otherwise interpolate between the two
+ position = (position - stop1Position) / (stop2Position - stop1Position);
+ float red1, red2, green1, green2, blue1, blue2, alpha1, alpha2;
+ [stop1Color getRed:&red1 green:&green1 blue:&blue1 alpha:&alpha1];
+ [stop2Color getRed:&red2 green:&green2 blue:&blue2 alpha:&alpha2];
+
+ colorValue[0] = (red2 - red1) * position + red1;
+ colorValue[1] = (green2 - green1) * position + green1;
+ colorValue[2] = (blue2 - blue1) * position + blue1;
+ colorValue[3] = (alpha2 - alpha1) * position + alpha1;
+ }
+
+ // Yes, I am casting a float[] to an id to pass it by the compiler. This
+ // significantly improves performance though as I avoid creating an NSColor
+ // for every scanline which later has to be cleaned up in an autorelease pool
+ // somewhere. Causes guardmalloc to run significantly faster.
+ return (id)colorValue;
+}
+
+//
+// switch from C to obj-C. The callback to a shader is a c function
+// but we want to call our objective c object to do all the
+// calculations for us. We have passed our function our
+// GTMLinearRGBShading as an obj-c object in the |info| so
+// we just turn around and ask it to calculate our value based
+// on |inPos| and then stick the results back in |outVals|
+//
+// Args:
+// info: is the GTMLinearRGBShading as an
+// obj-C object.
+// inPos: the position to calculate values for. This is a pointer to
+// a single float value
+// outVals: where we store our return values. Since we are calculating
+// an RGBA color, this is a pointer to an array of four float values
+// ranging from 0.0f to 1.0f
+//
+//
+static void cShadeFunction(void *info, const float *inPos, float *outVals) {
+ id object = (id)info;
+ float *colorValue = (float*)[object valueAtPosition:*inPos];
+ outVals[0] = colorValue[0];
+ outVals[1] = colorValue[1];
+ outVals[2] = colorValue[2];
+ outVals[3] = colorValue[3];
+}
+
+- (CGFunctionRef) shadeFunction {
+ // lazily create the function as necessary
+ if (nil == function_) {
+ // We have to go to carbon here, and create the CGFunction. Note that this
+ // diposed if necessary in the dealloc call.
+ const CGFunctionCallbacks shadeFunctionCallbacks = { 0, &cShadeFunction, NULL };
+
+ // TODO: (dmaclach): this code assumes that we have a range from 0.0f to 1.0f
+ // which may not be true according to the stops that the user has given us.
+ // In general you have stops at 0.0 and 1.0, so this will do for right now
+ // but may be an issue in the future.
+ const float inRange[2] = { 0.0f, 1.0f };
+ const float outRange[8] = { 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f };
+ function_ = CGFunctionCreate(self,
+ sizeof(inRange) / (sizeof(float) * 2), inRange,
+ sizeof(outRange) / (sizeof(float) * 2), outRange,
+ &shadeFunctionCallbacks);
+ }
+ return function_;
+}
+
+- (CGColorSpaceRef)colorSpace {
+ // lazily create the colorspace as necessary
+ if (nil == colorSpace_) {
+ if (isCalibrated_) {
+ colorSpace_ = CGColorSpaceCreateWithName(kCGColorSpaceUserRGB);
+ } else {
+ colorSpace_ = CGColorSpaceCreateDeviceRGB();
+ }
+ }
+ return colorSpace_;
+}
+@end
diff --git a/AppKit/GTMLinearRGBShadingTest.m b/AppKit/GTMLinearRGBShadingTest.m
new file mode 100644
index 0000000..6407e71
--- /dev/null
+++ b/AppKit/GTMLinearRGBShadingTest.m
@@ -0,0 +1,94 @@
+//
+// GTMLinearRGBShadingTest.m
+//
+// Copyright 2006-2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+
+#import <SenTestingKit/SenTestingKit.h>
+#import "GTMLinearRGBShading.h"
+#import "GTMNSColor+Theme.h"
+
+
+@interface GTMLinearRGBShadingTest : SenTestCase
+@end
+
+@implementation GTMLinearRGBShadingTest
+- (void)testShadingFrom {
+ // Create a shading from red to blue, and check if 50% is purple
+ NSColor *red = [NSColor gtm_deviceRedColor];
+ NSColor *blue = [NSColor gtm_deviceBlueColor];
+ NSColor *purple = [NSColor gtm_devicePurpleColor];
+ GTMLinearRGBShading *theShading =
+ [GTMLinearRGBShading shadingFromColor:red
+ toColor:blue
+ fromSpaceNamed:NSDeviceRGBColorSpace];
+ STAssertNotNil(theShading,nil);
+ STAssertTrue([theShading stopCount] == 2, nil);
+ float *theColor = (float*)[theShading valueAtPosition: 0.5];
+ STAssertTrue(theColor[0] == [purple redComponent] &&
+ theColor[1] == [purple greenComponent] &&
+ theColor[2] == [purple blueComponent] &&
+ theColor[3] == [purple alphaComponent], nil);
+}
+
+- (void)testShadingWith {
+ // Create a shading with kColorCount colors and make sure all the values are there.
+ const unsigned int kColorCount = 100;
+ NSColor *theColors[kColorCount];
+ float thePositions[kColorCount];
+ const float kColorIncrement = 1.0f / kColorCount;
+ for (unsigned int i = 0; i < kColorCount; i++) {
+ thePositions[i] = kColorIncrement * i;
+ theColors[i] = [NSColor colorWithDeviceRed:kColorIncrement * i
+ green:kColorIncrement * i
+ blue:kColorIncrement * i
+ alpha:kColorIncrement * i];
+ }
+ GTMLinearRGBShading *theShading =
+ [GTMLinearRGBShading shadingWithColors:theColors
+ fromSpaceNamed:NSDeviceRGBColorSpace
+ atPositions:thePositions
+ count:kColorCount];
+ for (unsigned int i = 0; i < kColorCount; i++) {
+ float *theColor = (float*)[theShading valueAtPosition: kColorIncrement * i];
+ STAssertTrue(theColor[0] == kColorIncrement * i &&
+ theColor[1] == kColorIncrement * i &&
+ theColor[2] == kColorIncrement * i &&
+ theColor[3] == kColorIncrement * i, nil);
+ }
+}
+
+- (void)testShadeFunction {
+ GTMLinearRGBShading *theShading =
+ [GTMLinearRGBShading shadingWithColors:nil
+ fromSpaceNamed:NSDeviceRGBColorSpace
+ atPositions:nil
+ count:0];
+ CGFunctionRef theFunction = [theShading shadeFunction];
+ STAssertTrue(nil != theFunction, nil);
+ STAssertTrue(CFGetTypeID(theFunction) == CGFunctionGetTypeID(), nil);
+}
+
+- (void)testColorSpace {
+ GTMLinearRGBShading *theShading =
+ [GTMLinearRGBShading shadingWithColors:nil
+ fromSpaceNamed:NSDeviceRGBColorSpace
+ atPositions:nil
+ count:0];
+ CGColorSpaceRef theColorSpace = [theShading colorSpace];
+ STAssertTrue(nil != theColorSpace, nil);
+ STAssertTrue(CFGetTypeID(theColorSpace) == CGColorSpaceGetTypeID(), nil);
+}
+@end
diff --git a/AppKit/GTMLoginItems.h b/AppKit/GTMLoginItems.h
new file mode 100644
index 0000000..1531bd0
--- /dev/null
+++ b/AppKit/GTMLoginItems.h
@@ -0,0 +1,87 @@
+//
+// GTMLoginItems.h
+//
+// Copyright 2007-2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+
+#import <Foundation/Foundation.h>
+
+/// Login items key constants, used as keys in |+loginItems|
+//
+// Item name
+extern NSString * const kGTMLoginItemsNameKey;
+// Item path
+extern NSString * const kGTMLoginItemsPathKey;
+// Hidden (NSNumber bool)
+extern NSString * const kGTMLoginItemsHiddenKey;
+
+/// GTMLoginItems
+//
+/// A helper class to manipulate the user's Login Items.
+@interface GTMLoginItems : NSObject
+
+/// Obtain a complete list of all login items.
+//
+// Returns:
+// Autoreleased array of dictionaries keyed with kGTMLoginItemsPathKey, etc.
+//
++ (NSArray *)loginItems:(NSError **)errorInfo;
+
+/// Check if the given path is in the current user's Login Items
+//
+// Args:
+// path: path to the application
+//
+// Returns:
+// YES if the path is in the Login Items
+//
++ (BOOL)pathInLoginItems:(NSString *)path;
+
+/// Check if the given name is in the current user's Login Items
+//
+// Args:
+// name: name to the application
+//
+// Returns:
+// YES if the name is in the Login Items
+//
++ (BOOL)itemWithNameInLoginItems:(NSString *)name;
+
+/// Add the given path to the current user's Login Items. Does nothing if the
+/// path is already there.
+//
+// Args:
+// path: path to add
+// hide: Set to YES to have the item launch hidden
+//
++ (void)addPathToLoginItems:(NSString *)path hide:(BOOL)hide;
+
+/// Remove the given path from the current user's Login Items. Does nothing if
+/// the path is not there.
+//
+// Args:
+// path: the path to remove
+//
++ (void)removePathFromLoginItems:(NSString *)path;
+
+/// Remove the given item name from the current user's Login Items. Does nothing
+/// if no item with that name is present.
+//
+// Args:
+// name: name of the item to remove
+//
++ (void)removeItemWithNameFromLoginItems:(NSString *)name;
+
+@end
diff --git a/AppKit/GTMLoginItems.m b/AppKit/GTMLoginItems.m
new file mode 100644
index 0000000..d3197c0
--- /dev/null
+++ b/AppKit/GTMLoginItems.m
@@ -0,0 +1,183 @@
+//
+// GTMLoginItems.m
+// Based on AELoginItems from DTS.
+//
+// Copyright 2007-2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+
+#import "GTMLoginItems.h"
+
+#include <Carbon/Carbon.h>
+
+// Exposed constants
+NSString * const kGTMLoginItemsNameKey = @"Name";
+NSString * const kGTMLoginItemsPathKey = @"Path";
+NSString * const kGTMLoginItemsHiddenKey = @"Hide";
+
+@interface GTMLoginItems (PrivateMethods)
++ (int)indexOfLoginItemWithValue:(id)value
+ forKey:(NSString *)key
+ loginItems:(NSArray *)items;
++ (BOOL)compileAndRunScript:(NSString *)script
+ withError:(NSError **)errorInfo;
+@end
+
+@implementation GTMLoginItems (PrivateMethods)
+
++ (int)indexOfLoginItemWithValue:(id)value
+ forKey:(NSString *)key
+ loginItems:(NSArray *)items {
+ if (!value || !key || !items) return NSNotFound;
+ NSDictionary *item = nil;
+ NSEnumerator *itemsEnum = [items objectEnumerator];
+ int found = -1;
+ while ((item = [itemsEnum nextObject])) {
+ ++found;
+ id itemValue = [item objectForKey:key];
+ if (itemValue && [itemValue isEqual:value]) {
+ return found;
+ }
+ }
+ return NSNotFound;
+}
+
++ (BOOL)compileAndRunScript:(NSString *)script
+ withError:(NSError **)errorInfo {
+ if ([script length] == 0) {
+ if (errorInfo)
+ *errorInfo = [NSError errorWithDomain:@"GTMLoginItems" code:-90 userInfo:nil];
+ return NO;
+ }
+ NSAppleScript *query = [[[NSAppleScript alloc] initWithSource:script] autorelease];
+ NSDictionary *errDict = nil;
+ if ( ![query compileAndReturnError:&errDict]) {
+ if (errorInfo)
+ *errorInfo = [NSError errorWithDomain:@"GTMLoginItems" code:-91 userInfo:errDict];
+ return NO;
+ }
+ NSAppleEventDescriptor *scriptResult = [query executeAndReturnError:&errDict];
+ if (!scriptResult) {
+ if (*errorInfo)
+ *errorInfo = [NSError errorWithDomain:@"GTMLoginItems" code:-92 userInfo:errDict];
+ return NO;
+ }
+ // we don't process the result
+ return YES;
+}
+
+@end
+
+@implementation GTMLoginItems
+
++ (NSArray*)loginItems:(NSError **)errorInfo {
+ NSDictionary *errDict = nil;
+ // get the script compiled and saved off
+ static NSAppleScript *query = nil;
+ if (!query) {
+ NSString *querySource = @"tell application \"System Events\" to get properties of login items";
+ query = [[NSAppleScript alloc] initWithSource:querySource];
+ if ( ![query compileAndReturnError:&errDict]) {
+ if (errorInfo)
+ *errorInfo = [NSError errorWithDomain:@"GTMLoginItems" code:-1 userInfo:errDict];
+ [query release];
+ query = nil;
+ return nil;
+ }
+ }
+ // run the script
+ NSAppleEventDescriptor *scriptResult = [query executeAndReturnError:&errDict];
+ if (!scriptResult) {
+ if (*errorInfo)
+ *errorInfo = [NSError errorWithDomain:@"GTMLoginItems" code:-2 userInfo:errDict];
+ return nil;
+ }
+ // build our results
+ NSMutableArray *result = [NSMutableArray array];
+ int count = [scriptResult numberOfItems];
+ for (int i = 0; i < count; ++i) {
+ NSAppleEventDescriptor *aeItem = [scriptResult descriptorAtIndex:i+1];
+ NSAppleEventDescriptor *hidn = [aeItem descriptorForKeyword:kAEHidden];
+ NSAppleEventDescriptor *nam = [aeItem descriptorForKeyword:pName];
+ NSAppleEventDescriptor *ppth = [aeItem descriptorForKeyword:'ppth'];
+ NSMutableDictionary *item = [NSMutableDictionary dictionary];
+ if (hidn && [hidn booleanValue]) {
+ [item setObject:[NSNumber numberWithBool:YES] forKey:kGTMLoginItemsHiddenKey];
+ }
+ if (nam) {
+ NSString *name = [nam stringValue];
+ if (name) {
+ [item setObject:name forKey:kGTMLoginItemsNameKey];
+ }
+ }
+ if (ppth) {
+ NSString *path = [ppth stringValue];
+ if (path) {
+ [item setObject:path forKey:kGTMLoginItemsPathKey];
+ }
+ }
+ [result addObject:item];
+ }
+
+ return result;
+}
+
++ (BOOL)pathInLoginItems:(NSString *)path {
+ NSArray *loginItems = [self loginItems:nil];
+ int index = [self indexOfLoginItemWithValue:path
+ forKey:kGTMLoginItemsPathKey
+ loginItems:loginItems];
+ return (index != NSNotFound) ? YES : NO;
+}
+
++ (BOOL)itemWithNameInLoginItems:(NSString *)name {
+ NSArray *loginItems = [self loginItems:nil];
+ int index = [self indexOfLoginItemWithValue:name
+ forKey:kGTMLoginItemsNameKey
+ loginItems:loginItems];
+ return (index != NSNotFound) ? YES : NO;
+}
+
++ (void)addPathToLoginItems:(NSString*)path hide:(BOOL)hide {
+ if (!path) return;
+ // make sure it isn't already there
+ if ([self pathInLoginItems:path]) return;
+ // now append it
+ NSString *scriptSource =
+ [NSString stringWithFormat:
+ @"tell application \"System Events\" to make new login item with properties { path:\"%s\", hidden:%s } at end",
+ [path UTF8String],
+ (hide ? "yes" : "no")];
+ [self compileAndRunScript:scriptSource withError:nil];
+}
+
++ (void)removePathFromLoginItems:(NSString*)path {
+ if ([path length] == 0) return;
+ NSString *scriptSource =
+ [NSString stringWithFormat:
+ @"tell application \"System Events\" to delete (login items whose path is \"%s\")",
+ [path UTF8String]];
+ [self compileAndRunScript:scriptSource withError:nil];
+}
+
++ (void)removeItemWithNameFromLoginItems:(NSString *)name {
+ if ([name length] == 0) return;
+ NSString *scriptSource =
+ [NSString stringWithFormat:
+ @"tell application \"System Events\" to delete (login items whose name is \"%s\")",
+ [name UTF8String]];
+ [self compileAndRunScript:scriptSource withError:nil];
+}
+
+@end
diff --git a/AppKit/GTMLoginItemsTest.m b/AppKit/GTMLoginItemsTest.m
new file mode 100644
index 0000000..05a93a1
--- /dev/null
+++ b/AppKit/GTMLoginItemsTest.m
@@ -0,0 +1,112 @@
+//
+// GTMLoginItemsTest.m
+//
+// Copyright 2006-2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+
+#import <SenTestingKit/SenTestingKit.h>
+#import "GTMSenTestCase.h"
+#import "GTMLoginItems.h"
+
+ // we don't really run this test because if someone had it in some automated
+ // tests, then if something did fail, it could leave things in the login items
+ // on the computer which could be a nasty surprise.
+#define TESTS_ENABLED 0
+
+
+@interface GTMLoginItemsTest : SenTestCase
+@end
+
+#if TESTS_ENABLED
+
+static BOOL ItemsListHasPath(NSArray *items, NSString *path) {
+ NSDictionary *item = nil;
+ NSEnumerator *itemsEnum = [items objectEnumerator];
+ while ((item = [itemsEnum nextObject])) {
+ NSString *itemPath = [item objectForKey:kGTMLoginItemsPathKey];
+ if (itemPath && [itemPath isEqual:path]) {
+ return YES;
+ }
+ }
+ return NO;
+}
+
+#endif // TESTS_ENABLED
+
+@implementation GTMLoginItemsTest
+
+- (void)testGTMLoginItems {
+
+#if TESTS_ENABLED
+
+ NSError *error = nil;
+ NSString *textEditPath = @"/Applications/TextEdit.app";
+ NSString *textEditName = @"TextEdit";
+
+ // fetch the starting values
+ NSArray *initialItems = [GTMLoginItems loginItems:&error];
+ STAssertNotNil(initialItems, @"shouldn't be nil (%@)", error);
+ STAssertFalse(ItemsListHasPath(initialItems, textEditPath),
+ @"textedit shouldn't be in list to start for test (%@)", initialItems);
+
+ // add textedit
+ [GTMLoginItems addPathToLoginItems:textEditPath hide:NO];
+ NSArray *curItems = [GTMLoginItems loginItems:nil];
+ STAssertNotEqualObjects(initialItems, curItems, nil);
+
+ // check by path
+ STAssertTrue([GTMLoginItems pathInLoginItems:textEditPath], nil);
+
+ // check by name
+ STAssertTrue([GTMLoginItems itemWithNameInLoginItems:textEditName], nil);
+
+ // remove it by path
+ [GTMLoginItems removePathFromLoginItems:textEditPath];
+ curItems = [GTMLoginItems loginItems:nil];
+ STAssertEqualObjects(initialItems, curItems, nil);
+
+ // check by path
+ STAssertFalse([GTMLoginItems pathInLoginItems:textEditPath], nil);
+
+ // check by name
+ STAssertFalse([GTMLoginItems itemWithNameInLoginItems:textEditName], nil);
+
+ // add textedit
+ [GTMLoginItems addPathToLoginItems:textEditPath hide:NO];
+ curItems = [GTMLoginItems loginItems:nil];
+ STAssertNotEqualObjects(initialItems, curItems, nil);
+
+ // check by path
+ STAssertTrue([GTMLoginItems pathInLoginItems:textEditPath], nil);
+
+ // check by name
+ STAssertTrue([GTMLoginItems itemWithNameInLoginItems:textEditName], nil);
+
+ // remove it by name
+ [GTMLoginItems removeItemWithNameFromLoginItems:textEditName];
+ curItems = [GTMLoginItems loginItems:nil];
+ STAssertEqualObjects(initialItems, curItems, nil);
+
+ // check by path
+ STAssertFalse([GTMLoginItems pathInLoginItems:textEditPath], nil);
+
+ // check by name
+ STAssertFalse([GTMLoginItems itemWithNameInLoginItems:textEditName], nil);
+
+#endif // TESTS_ENABLED
+
+}
+
+@end
diff --git a/AppKit/GTMNSBezierPath+CGPath.h b/AppKit/GTMNSBezierPath+CGPath.h
new file mode 100644
index 0000000..3349142
--- /dev/null
+++ b/AppKit/GTMNSBezierPath+CGPath.h
@@ -0,0 +1,33 @@
+//
+// GTMNSBezierPath+CGPath.h
+//
+// Copyright 2006-2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+
+#import <Cocoa/Cocoa.h>
+
+/// Category for extracting a CGPathRef from a NSBezierPath
+@interface NSBezierPath (GTMBezierPathCGPathAdditions)
+
+/// Extract a CGPathRef from a NSBezierPath.
+//
+// Args:
+//
+// Returns:
+// Converted CGPathRef. Must be released by client (CGPathRelease).
+// nil if failure.
+- (CGPathRef)gtm_createCGPath;
+
+@end
diff --git a/AppKit/GTMNSBezierPath+CGPath.m b/AppKit/GTMNSBezierPath+CGPath.m
new file mode 100644
index 0000000..97464fc
--- /dev/null
+++ b/AppKit/GTMNSBezierPath+CGPath.m
@@ -0,0 +1,68 @@
+//
+// GTMNSBezierPath+CGPath.h
+//
+// Category for extracting a CGPathRef from a NSBezierPath
+//
+// Copyright 2006-2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+#import "GTMNSBezierPath+CGPath.h"
+
+@implementation NSBezierPath (GTMBezierPathCGPathAdditions)
+
+// Extract a CGPathRef from a NSBezierPath.
+//
+// Args:
+//
+// Returns:
+// Converted CGPathRef. Must be released by client (CGPathRelease).
+// nil if failure.
+- (CGPathRef)gtm_createCGPath {
+ CGMutablePathRef thePath = CGPathCreateMutable();
+ if (!thePath) return nil;
+
+ unsigned int elementCount = [self elementCount];
+
+ // The maximum number of points is 3 for a NSCurveToBezierPathElement.
+ // (controlPoint1, controlPoint2, and endPoint)
+ NSPoint controlPoints[3];
+
+ for (unsigned int i = 0; i < elementCount; i++) {
+ switch ([self elementAtIndex:i associatedPoints:controlPoints]) {
+ case NSMoveToBezierPathElement:
+ CGPathMoveToPoint(thePath, &CGAffineTransformIdentity,
+ controlPoints[0].x, controlPoints[0].y);
+ break;
+ case NSLineToBezierPathElement:
+ CGPathAddLineToPoint(thePath, &CGAffineTransformIdentity,
+ controlPoints[0].x, controlPoints[0].y);
+ break;
+ case NSCurveToBezierPathElement:
+ CGPathAddCurveToPoint(thePath, &CGAffineTransformIdentity,
+ controlPoints[0].x, controlPoints[0].y,
+ controlPoints[1].x, controlPoints[1].y,
+ controlPoints[2].x, controlPoints[2].y);
+ break;
+ case NSClosePathBezierPathElement:
+ CGPathCloseSubpath(thePath);
+ break;
+ default:
+ NSLog(@"Unknown element at [NSBezierPath (GTMBezierPathCGPathAdditions) cgPath]");
+ break;
+ };
+ }
+ return thePath;
+}
+
+@end
diff --git a/AppKit/GTMNSBezierPath+CGPathTest.m b/AppKit/GTMNSBezierPath+CGPathTest.m
new file mode 100644
index 0000000..c8506d7
--- /dev/null
+++ b/AppKit/GTMNSBezierPath+CGPathTest.m
@@ -0,0 +1,78 @@
+//
+// GTMNSBezierPath+CGPathTest.m
+//
+// Copyright 2006-2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+
+#import <Cocoa/Cocoa.h>
+
+#import <SenTestingKit/SenTestingKit.h>
+#import "GTMNSBezierPath+CGPath.h"
+#import "GTMNSView+UnitTesting.h"
+
+@interface GTMNSBezierPath_CGPathTest : SenTestCase<GTMUnitTestViewDrawer>
+@end
+
+@implementation GTMNSBezierPath_CGPathTest
+
+- (void)testCreateCGPath {
+ GTMAssertDrawingEqualToFile(self, NSMakeSize(100, 100), @"GTMNSBezierPath+CGPathTest", nil, nil);
+}
+
+
+// Draws all of our tests so that we can compare this to our stored TIFF file.
+- (void)unitTestViewDrawRect:(NSRect)rect contextInfo:(void*)contextInfo{
+ NSBezierPath *thePath = [NSBezierPath bezierPath];
+ NSPoint theStart = NSMakePoint(20.0f, 20.0f);
+
+ // Test moveto/lineto
+ [thePath moveToPoint: theStart];
+ for (unsigned int i = 0; i < 10; ++i) {
+ NSPoint theNewPoint = NSMakePoint(i * 5, i * 10);
+ [thePath lineToPoint: theNewPoint];
+ theNewPoint = NSMakePoint(i * 2, i * 6);
+ [thePath moveToPoint: theNewPoint];
+ }
+
+ // Test moveto/curveto
+ for (unsigned int i = 0; i < 10; ++i) {
+ NSPoint startPoint = NSMakePoint(5.0f, 50.0f);
+ NSPoint endPoint = NSMakePoint(55.0f, 50.0f);
+ NSPoint controlPoint1 = NSMakePoint(17.5f, 50.0f + 5.0f * i);
+ NSPoint controlPoint2 = NSMakePoint(42.5f, 50.0f - 5.0f * i);
+ [thePath moveToPoint:startPoint];
+ [thePath curveToPoint:endPoint controlPoint1:controlPoint1 controlPoint2:controlPoint2];
+ }
+ // test close
+ [thePath closePath];
+
+ CGPathRef cgPath = [thePath gtm_createCGPath];
+ if (nil == cgPath) {
+ @throw [NSException failureInFile:[NSString stringWithCString:__FILE__]
+ atLine:__LINE__
+ withDescription:@"Nil CGPath"];
+ }
+ CGContextRef cgContext = [[NSGraphicsContext currentContext] graphicsPort];
+ if (nil == cgContext) {
+ @throw [NSException failureInFile:[NSString stringWithCString:__FILE__]
+ atLine:__LINE__
+ withDescription:@"Nil CGContext"];
+ }
+ CGContextAddPath(cgContext, cgPath);
+ CGContextStrokePath(cgContext);
+ CGPathRelease(cgPath);
+}
+
+@end
diff --git a/AppKit/GTMNSBezierPath+CGPathTest.tif b/AppKit/GTMNSBezierPath+CGPathTest.tif
new file mode 100644
index 0000000..a7fee9b
--- /dev/null
+++ b/AppKit/GTMNSBezierPath+CGPathTest.tif
Binary files 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 <Cocoa/Cocoa.h>
+
+/// Category for adding utility functions for creating round rectangles.
+@interface NSBezierPath (GMBezierPathRoundRectAdditions)
+
+/// Inscribe a round rectangle inside of rectangle |rect| with a corner radius of |radius|
+//
+// Args:
+// rect: outer rectangle to inscribe into
+// radius: radius of the corners. |radius| is clamped internally
+// to be no larger than the smaller of half |rect|'s width or height
+//
+// Returns:
+// Auto released NSBezierPath
++ (NSBezierPath *)gtm_bezierPathWithRoundRect:(NSRect)rect cornerRadius:(float)radius;
+
+/// Adds a path which is a round rectangle inscribed inside of rectangle |rect| with a corner radius of |radius|
+//
+// Args:
+// rect: outer rectangle to inscribe into
+// radius: radius of the corners. |radius| is clamped internally
+// to be no larger than the smaller of half |rect|'s width or height
+- (void)gtm_appendBezierPathWithRoundRect:(NSRect)rect cornerRadius:(float)radius;
+@end
diff --git a/AppKit/GTMNSBezierPath+RoundRect.m b/AppKit/GTMNSBezierPath+RoundRect.m
new file mode 100644
index 0000000..4b98dc9
--- /dev/null
+++ b/AppKit/GTMNSBezierPath+RoundRect.m
@@ -0,0 +1,58 @@
+//
+// GTMNSBezierPath+RoundRect.h
+//
+// Category for adding utility functions for creating
+// round rectangles.
+//
+// Copyright 2006-2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+
+#import "GTMNSBezierPath+RoundRect.h"
+
+@implementation NSBezierPath (GTMBezierPathRoundRectAdditions)
+
+
++ (NSBezierPath *)gtm_bezierPathWithRoundRect:(NSRect)rect cornerRadius:(float)radius {
+ NSBezierPath *bezier = [NSBezierPath bezierPath];
+ [bezier gtm_appendBezierPathWithRoundRect:rect cornerRadius:radius];
+ return bezier;
+}
+
+
+- (void)gtm_appendBezierPathWithRoundRect:(NSRect)rect cornerRadius:(float)radius {
+ if (!NSIsEmptyRect(rect)) {
+ if (radius > 0.0) {
+ // Clamp radius to be no larger than half the rect's width or height.
+ radius = MIN(radius, 0.5 * MIN(rect.size.width, rect.size.height));
+
+ NSPoint topLeft = NSMakePoint(NSMinX(rect), NSMaxY(rect));
+ NSPoint topRight = NSMakePoint(NSMaxX(rect), NSMaxY(rect));
+ NSPoint bottomRight = NSMakePoint(NSMaxX(rect), NSMinY(rect));
+
+ [self moveToPoint:NSMakePoint(NSMidX(rect), NSMaxY(rect))];
+ [self appendBezierPathWithArcFromPoint:topLeft toPoint:rect.origin radius:radius];
+ [self appendBezierPathWithArcFromPoint:rect.origin toPoint:bottomRight radius:radius];
+ [self appendBezierPathWithArcFromPoint:bottomRight toPoint:topRight radius:radius];
+ [self appendBezierPathWithArcFromPoint:topRight toPoint:topLeft radius:radius];
+ [self closePath];
+ } else {
+ // When radius <= 0.0, use plain rectangle.
+ [self appendBezierPathWithRect:rect];
+ }
+ }
+}
+
+
+@end
diff --git a/AppKit/GTMNSBezierPath+RoundRectTest.m b/AppKit/GTMNSBezierPath+RoundRectTest.m
new file mode 100644
index 0000000..61bd2dd
--- /dev/null
+++ b/AppKit/GTMNSBezierPath+RoundRectTest.m
@@ -0,0 +1,93 @@
+//
+// NSBezierPath+RoundRectTest.m
+//
+// Copyright 2006-2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+
+#import <Cocoa/Cocoa.h>
+
+#import <SenTestingKit/SenTestingKit.h>
+#import "GTMNSBezierPath+RoundRect.h"
+#import "GTMNSView+UnitTesting.h"
+
+@interface GTMNSBezierPath_RoundRectTest : SenTestCase<GTMUnitTestViewDrawer>
+@end
+
+@implementation GTMNSBezierPath_RoundRectTest
+
+- (void)testRoundRects {
+ GTMAssertDrawingEqualToFile(self, NSMakeSize(330, 430), @"GTMNSBezierPath+RoundRectTest", nil, nil);
+}
+
+
+// Draws all of our tests so that we can compare this to our stored TIFF file.
+- (void)unitTestViewDrawRect:(NSRect)rect contextInfo:(void*)contextInfo{
+ NSRect theRects[] = {
+ NSMakeRect(0.0f, 10.0f, 0.0f, 0.0f), //Empty Rect test
+ NSMakeRect(50.0f, 10.0f, 30.0f, 30.0f), //Square Test
+ NSMakeRect(100.0f, 10.0f, 1.0f, 2.0f), //Small Test
+ NSMakeRect(120.0f, 10.0f, 15.0f, 20.0f), //Medium Test
+ NSMakeRect(140.0f, 10.0f, 150.0f, 30.0f) //Large Test
+ };
+ const unsigned int theRectCount = sizeof(theRects) / sizeof(NSRect);
+
+ // Line Width Tests
+ float theLineWidths[] = { 0.5f, 50.0f, 2.0f };
+ const unsigned int theLineWidthCount = sizeof(theLineWidths) / sizeof(float);
+ unsigned int i,j;
+
+ for (i = 0; i < theLineWidthCount; ++i) {
+ for (j = 0; j < theRectCount; ++j) {
+ NSBezierPath *roundRect = [NSBezierPath gtm_bezierPathWithRoundRect:theRects[j] cornerRadius:20.0f];
+ [roundRect setLineWidth: theLineWidths[i]];
+ [roundRect stroke];
+ float newWidth = 35.0f;
+ if (i < theLineWidthCount - 1) newWidth += theLineWidths[i + 1] + theLineWidths[i];
+ theRects[j].origin.y += newWidth;
+ }
+ }
+
+ // Fill test
+ NSColor *theColors[] = {
+ [NSColor colorWithDeviceRed:1.0f green:0.0f blue:0.0f alpha:1.0f],
+ [NSColor colorWithDeviceRed:0.2f green:0.4f blue:0.6f alpha:0.4f]
+ };
+ const unsigned int theColorCount = sizeof(theColors)/sizeof(NSColor);
+
+ for (i = 0; i < theColorCount; ++i) {
+ for (j = 0; j < theRectCount; ++j) {
+ NSBezierPath *roundRect = [NSBezierPath gtm_bezierPathWithRoundRect:theRects[j] cornerRadius:10.0f];
+ [theColors[i] setFill];
+ [roundRect fill];
+ theRects[j].origin.y += 35.0f;
+ }
+ }
+
+ // Flatness test
+ float theFlatness[] = {0.0f, 0.1f, 1.0f, 10.0f};
+ const unsigned int theFlatnessCount = sizeof(theFlatness)/sizeof(float);
+
+ for (i = 0; i < theFlatnessCount; i++) {
+ for (j = 0; j < theRectCount; ++j) {
+ NSBezierPath *roundRect = [NSBezierPath gtm_bezierPathWithRoundRect:theRects[j] cornerRadius:6.0f];
+ [roundRect setFlatness:theFlatness[i]];
+ [roundRect stroke];
+ theRects[j].origin.y += 35.0f;
+ }
+ }
+}
+
+
+@end
diff --git a/AppKit/GTMNSBezierPath+RoundRectTest.tif b/AppKit/GTMNSBezierPath+RoundRectTest.tif
new file mode 100644
index 0000000..7ce0d45
--- /dev/null
+++ b/AppKit/GTMNSBezierPath+RoundRectTest.tif
Binary files 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 <Cocoa/Cocoa.h>
+
+@protocol GTMShading;
+
+//
+/// Category for radial and axial stroke and fill functions for NSBezierPaths
+//
+@interface NSBezierPath (GTMBezierPathShadingAdditions)
+
+/// Stroke the path axially with a color blend defined by |shading|.
+//
+/// The fill will extend from |fromPoint| to |toPoint| and will extend
+/// indefinitely perpendicular to the axis of the line defined by the
+/// two points. You can extend beyond the |fromPoint|/|toPoint by setting
+/// |extendingStart|/|extendingEnd| respectively.
+//
+// Args:
+// fromPoint: point to start the shading at
+// toPoint: point to end the shading at
+// extendingStart: should we extend the shading before |fromPoint| using
+// the first color in our shading?
+// extendingEnd: should we extend the shading after |toPoint| using the
+// last color in our shading?
+// shading: the shading to use to take our colors from.
+//
+- (void)gtm_strokeAxiallyFrom:(NSPoint)fromPoint to:(NSPoint)toPoint
+ extendingStart:(BOOL)extendingStart extendingEnd:(BOOL)extendingEnd
+ shading:(id<GTMShading>)shading;
+
+/// Stroke the path radially with a color blend defined by |shading|.
+//
+/// The fill will extend from the circle with center |fromPoint|
+/// and radius |fromRadius| to the circle with center |toPoint|
+/// with radius |toRadius|.
+/// You can extend beyond the |fromPoint|/|toPoint| by setting
+/// |extendingStart|/|extendingEnd| respectively.
+//
+// Args:
+// fromPoint: center of the circle to start the shading at
+// fromRadius: radius of the circle to start the shading at
+// toPoint: center of the circle to to end the shading at
+// toRadius: raidus of the circle to end the shading at
+// extendingStart: should we extend the shading before |fromPoint| using
+// the first color in our shading?
+// extendingEnd: should we extend the shading after |toPoint| using the
+// last color in our shading?
+// shading: the shading to use to take our colors from.
+//
+- (void)gtm_strokeRadiallyFrom:(NSPoint)fromPoint fromRadius:(float)fromRadius
+ to:(NSPoint)toPoint toRadius:(float)toRadius
+ extendingStart:(BOOL)extendingStart extendingEnd:(BOOL)extendingEnd
+ shading:(id<GTMShading>)shading;
+
+/// Fill the path radially with a color blend defined by |shading|.
+//
+/// The fill will extend from the circle with center |fromPoint|
+/// and radius |fromRadius| to the circle with center |toPoint|
+/// with radius |toRadius|.
+/// You can extend beyond the |fromPoint|/|toPoint by setting
+/// |extendingStart|/|extendingEnd| respectively.
+//
+// Args:
+// fromPoint: center of the circle to start the shading at
+// fromRadius: radius of the circle to start the shading at
+// toPoint: center of the circle to to end the shading at
+// toRadius: radius of the circle to end the shading at
+// extendingStart: should we extend the shading before |fromPoint| using
+// the first color in our shading?
+// extendingEnd: should we extend the shading after |toPoint| using the
+// last color in our shading?
+// shading: the shading to use to take our colors from.
+//
+- (void)gtm_fillAxiallyFrom:(NSPoint)fromPoint to:(NSPoint)toPoint
+ extendingStart:(BOOL)extendingStart extendingEnd:(BOOL)extendingEnd
+ shading:(id<GTMShading>)shading;
+
+/// Fill the path radially with a color blend defined by |shading|.
+//
+/// The fill will extend from the circle with center |fromPoint|
+/// and radius |fromRadius| to the circle with center |toPoint|
+/// with radius |toRadius|.
+/// You can extend beyond the |fromPoint|/|toPoint by setting
+/// |extendingStart|/|extendingEnd| respectively.
+//
+// Args:
+// fromPoint: center of the circle to start the shading at
+// fromRadius: radius of the circle to start the shading at
+// toPoint: center of the circle to to end the shading at
+// toRadius: radius of the circle to end the shading at
+// extendingStart: should we extend the shading before |fromPoint| using
+// the first color in our shading?
+// extendingEnd: should we extend the shading after |toPoint| using the
+// last color in our shading?
+// shading: the shading to use to take our colors from.
+//
+- (void)gtm_fillRadiallyFrom:(NSPoint)fromPoint fromRadius:(float)fromRadius
+ to:(NSPoint)toPoint toRadius:(float)toRadius
+ extendingStart:(BOOL)extendingStart extendingEnd:(BOOL)extendingEnd
+ shading:(id<GTMShading>)shading;
+@end
diff --git a/AppKit/GTMNSBezierPath+Shading.m b/AppKit/GTMNSBezierPath+Shading.m
new file mode 100644
index 0000000..f5935ab
--- /dev/null
+++ b/AppKit/GTMNSBezierPath+Shading.m
@@ -0,0 +1,214 @@
+//
+// GTMNSBezierPath+Shading.m
+//
+// Category for radial and axial stroke and fill functions for NSBezierPaths
+//
+// Copyright 2006-2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+
+#import "GTMNSBezierPath+Shading.h"
+#import "GTMNSBezierPath+CGPath.h"
+#import "GTMShading.h"
+#import "GTMGeometryUtils.h"
+
+@interface NSBezierPath (GTMBezierPathShadingAdditionsPrivate)
+// Fills a CGPathRef either axially or radially with the given shading.
+//
+// Args:
+// path: path to fill
+// axially: if YES fill axially, otherwise fill radially
+// asStroke: if YES, clip to the stroke of the path, otherwise
+// clip to the fill
+// from: where to shade from
+// fromRadius: in a radial fill, the radius of the from circle
+// to: where to shade to
+// toRadius: in a radial fill, the radius of the to circle
+// extendingStart: if true, extend the fill with the first color of the shade
+// beyond |from| away from |to|
+// extendingEnd: if true, extend the fill with the last color of the shade
+// beyond |to| away from |from|
+// shading: the shading to use for the fill
+//
+- (void)gtm_fillCGPath:(CGPathRef)path
+ axially:(BOOL)axially
+ asStroke:(BOOL)asStroke
+ from:(NSPoint)fromPoint fromRadius:(float)fromRadius
+ to:(NSPoint)toPoint toRadius:(float)toRadius
+ extendingStart:(BOOL)extendingStart extendingEnd:(BOOL)extendingEnd
+ shading:(id<GTMShading>)shading;
+
+// Returns the point which is the projection of a line from point |pointA|
+// to |pointB| by length
+//
+// Args:
+// pointA: first point
+// pointB: second point
+// length: distance to project beyond |pointB| which is in line with
+// |pointA| and |pointB|
+//
+// Returns:
+// the projected point
+- (NSPoint)gtm_projectLineFrom:(NSPoint)pointA
+ to:(NSPoint)pointB
+ by:(float)length;
+@end
+
+
+@implementation NSBezierPath (GTMBezierPathAdditionsPrivate)
+
+- (void)gtm_fillCGPath:(CGPathRef)path
+ axially:(BOOL)axially asStroke:(BOOL)asStroke
+ from:(NSPoint)fromPoint fromRadius:(float)fromRadius
+ to:(NSPoint)toPoint toRadius:(float)toRadius
+ extendingStart:(BOOL)extendingStart extendingEnd:(BOOL)extendingEnd
+ shading:(id<GTMShading>)shading {
+ CGFunctionRef shadingFunction = [shading shadeFunction];
+ if (nil != shadingFunction) {
+ CGContextRef currentContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
+ if (nil != currentContext) {
+ CGContextSaveGState(currentContext);
+ float lineWidth = [self lineWidth];
+ CGContextSetLineWidth(currentContext,lineWidth);
+ if (asStroke) {
+ // if we are using the stroke, we offset the from and to points
+ // by half the stroke width away from the center of the stroke.
+ // Otherwise we tend to end up with fills that only cover half of the
+ // because users set the start and end points based on the center
+ // of the stroke.
+ float halfWidth = lineWidth * 0.5f;
+ fromPoint = [self gtm_projectLineFrom:toPoint to:fromPoint by:halfWidth];
+ toPoint = [self gtm_projectLineFrom:fromPoint to:toPoint by:-halfWidth];
+ }
+ CGColorSpaceRef colorspace = [shading colorSpace];
+ if (nil != colorspace) {
+ CGPoint toCGPoint = GTMNSPointToCGPoint(toPoint);
+ CGPoint fromCGPoint = GTMNSPointToCGPoint(fromPoint);
+ CGShadingRef myCGShading;
+ if(axially) {
+ myCGShading = CGShadingCreateAxial(colorspace, fromCGPoint,
+ toCGPoint, shadingFunction,
+ extendingStart == YES,
+ extendingEnd == YES);
+ }
+ else {
+ myCGShading = CGShadingCreateRadial(colorspace, fromCGPoint, fromRadius,
+ toCGPoint, toRadius, shadingFunction,
+ extendingStart == YES,
+ extendingEnd == YES);
+ }
+
+ if (nil != myCGShading) {
+ CGContextAddPath(currentContext,path);
+ if(asStroke) {
+ CGContextReplacePathWithStrokedPath(currentContext);
+ }
+ CGContextClip(currentContext);
+ CGContextDrawShading(currentContext, myCGShading);
+ CGShadingRelease(myCGShading);
+ }
+ }
+ CGContextRestoreGState(currentContext);
+ }
+ }
+}
+
+
+- (NSPoint)gtm_projectLineFrom:(NSPoint)pointA
+ to:(NSPoint)pointB
+ by:(float)length {
+ NSPoint newPoint = pointB;
+ float x = (pointB.x - pointA.x);
+ float y = (pointB.y - pointA.y);
+ if (x == 0.0f) {
+ newPoint.y += length;
+ } else if (y == 0.0f) {
+ newPoint.x += length;
+ } else {
+ float angle = atanf(y / x);
+ newPoint.x += sinf(angle) * length;
+ newPoint.y += cosf(angle) * length;
+ }
+ return newPoint;
+}
+
+@end
+
+
+@implementation NSBezierPath (GTMBezierPathShadingAdditions)
+
+//METHOD_CHECK(NSBezierPath, gtm_createCGPath);
+
+- (void)gtm_strokeAxiallyFrom:(NSPoint)fromPoint to:(NSPoint)toPoint
+ extendingStart:(BOOL)extendingStart extendingEnd:(BOOL)extendingEnd
+ shading:(id<GTMShading>)shading {
+ CGPathRef thePath = [self gtm_createCGPath];
+ if (nil != thePath) {
+ [self gtm_fillCGPath:thePath axially:YES asStroke:YES
+ from:fromPoint fromRadius:0.0f
+ to:toPoint toRadius:0.0f
+ extendingStart:extendingStart extendingEnd:extendingEnd
+ shading:shading];
+ CGPathRelease(thePath);
+ }
+}
+
+
+- (void)gtm_strokeRadiallyFrom:(NSPoint)fromPoint fromRadius:(float)fromRadius
+ to:(NSPoint)toPoint toRadius:(float)toRadius
+ extendingStart:(BOOL)extendingStart extendingEnd:(BOOL)extendingEnd
+ shading:(id<GTMShading>)shading {
+ CGPathRef thePath = [self gtm_createCGPath];
+ if (nil != thePath) {
+ [self gtm_fillCGPath:thePath axially:NO asStroke:YES
+ from:fromPoint fromRadius:fromRadius
+ to:toPoint toRadius:toRadius
+ extendingStart:extendingStart extendingEnd:extendingEnd
+ shading:shading];
+ CGPathRelease(thePath);
+ }
+}
+
+
+- (void)gtm_fillAxiallyFrom:(NSPoint)fromPoint to:(NSPoint)toPoint
+ extendingStart:(BOOL)extendingStart extendingEnd:(BOOL)extendingEnd
+ shading:(id<GTMShading>)shading {
+ CGPathRef thePath = [self gtm_createCGPath];
+ if (nil != thePath) {
+ [self gtm_fillCGPath:thePath axially:YES asStroke:NO
+ from:fromPoint fromRadius:0.0f
+ to:toPoint toRadius:0.0f
+ extendingStart:extendingStart extendingEnd:extendingEnd
+ shading:shading];
+ CGPathRelease(thePath);
+ }
+}
+
+
+- (void)gtm_fillRadiallyFrom:(NSPoint)fromPoint fromRadius:(float)fromRadius
+ to:(NSPoint)toPoint toRadius:(float)toRadius
+ extendingStart:(BOOL)extendingStart extendingEnd:(BOOL)extendingEnd
+ shading:(id<GTMShading>)shading {
+ CGPathRef thePath = [self gtm_createCGPath];
+ if (nil != thePath) {
+ [self gtm_fillCGPath:thePath axially:NO asStroke:NO
+ from:fromPoint fromRadius:fromRadius
+ to:toPoint toRadius:toRadius
+ extendingStart:extendingStart extendingEnd:extendingEnd
+ shading:shading];
+ CGPathRelease(thePath);
+ }
+}
+
+@end
diff --git a/AppKit/GTMNSBezierPath+ShadingTest.10.4.tif b/AppKit/GTMNSBezierPath+ShadingTest.10.4.tif
new file mode 100644
index 0000000..44a09b5
--- /dev/null
+++ b/AppKit/GTMNSBezierPath+ShadingTest.10.4.tif
Binary files differ
diff --git a/AppKit/GTMNSBezierPath+ShadingTest.10.5.tif b/AppKit/GTMNSBezierPath+ShadingTest.10.5.tif
new file mode 100644
index 0000000..6d4570a
--- /dev/null
+++ b/AppKit/GTMNSBezierPath+ShadingTest.10.5.tif
Binary files 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 <Cocoa/Cocoa.h>
+
+#import <SenTestingKit/SenTestingKit.h>
+
+#import "GTMLinearRGBShading.h"
+#import "GTMNSView+UnitTesting.h"
+#import "GTMNSBezierPath+Shading.h"
+#import "GTMNSColor+Theme.h"
+
+@interface GTMNSBezierPath_ShadingTest : SenTestCase<GTMUnitTestViewDrawer>
+@end
+
+@implementation GTMNSBezierPath_ShadingTest
+
+- (void)testShadings {
+ GTMAssertDrawingEqualToFile(self, NSMakeSize(200, 200), @"GTMNSBezierPath+ShadingTest", nil, nil);
+}
+
+
+- (void)unitTestViewDrawRect:(NSRect)rect contextInfo:(void*)contextInfo{
+
+ NSColor *theColorArray[] = { [NSColor gtm_deviceBlueColor],
+ [NSColor gtm_deviceRedColor], [NSColor gtm_deviceYellowColor],
+ [NSColor gtm_deviceBlueColor], [NSColor gtm_deviceGreenColor],
+ [NSColor gtm_deviceRedColor] };
+ float theFloatArray[] = { 0.0f, 0.2f, 0.4f, 0.6f, 0.8f, 1.00f };
+
+ GTMLinearRGBShading *shading =
+ [GTMLinearRGBShading shadingWithColors:theColorArray
+ fromSpaceNamed:NSDeviceRGBColorSpace
+ atPositions:theFloatArray
+ count:sizeof(theFloatArray)/sizeof(float)];
+ NSBezierPath *shadedPath;
+
+ // axialStrokeRect test
+ NSRect axialStrokeRect = NSMakeRect(10.0f, 10.0f, 90.0f, 90.0f);
+ shadedPath = [NSBezierPath bezierPathWithRect:axialStrokeRect];
+ [shadedPath setLineWidth: 10.0f];
+ NSPoint startPoint = NSMakePoint(axialStrokeRect.origin.x + 20.0f,
+ axialStrokeRect.origin.y + 20.0f);
+ NSPoint endPoint = NSMakePoint(axialStrokeRect.origin.x + axialStrokeRect.size.width - 20.0f,
+ axialStrokeRect.origin.y + axialStrokeRect.size.height - 20.0f);
+ [shadedPath gtm_strokeAxiallyFrom:startPoint to:endPoint extendingStart:YES extendingEnd:YES shading:shading];
+
+ // axial fill
+ NSRect axialFillRect = NSMakeRect(10.0f, 110.0f, 90.0f, 90.0f);
+ shadedPath = [NSBezierPath bezierPathWithRect:axialFillRect];
+ startPoint = NSMakePoint(axialFillRect.origin.x + 20.0f,
+ axialFillRect.origin.y + 20.0f);
+ endPoint = NSMakePoint(axialFillRect.origin.x + axialFillRect.size.width - 20.0f,
+ axialFillRect.origin.y + axialFillRect.size.height - 20.0f);
+ [shadedPath gtm_fillAxiallyFrom:startPoint to:endPoint extendingStart:YES extendingEnd:YES shading:shading];
+
+ // radial stroke
+ NSRect radialStrokeRect = NSMakeRect(110.0f, 110.0f, 90.0f, 90.0f);
+ shadedPath = [NSBezierPath bezierPathWithRect:radialStrokeRect];
+ startPoint = NSMakePoint(radialStrokeRect.origin.x + 20.0f,
+ radialStrokeRect.origin.y + 20.0f);
+ endPoint = NSMakePoint(radialStrokeRect.origin.x + radialStrokeRect.size.width - 20.0f,
+ radialStrokeRect.origin.y + radialStrokeRect.size.height - 20.0f);
+ [shadedPath gtm_strokeRadiallyFrom:startPoint fromRadius:60.0f
+ to:endPoint toRadius:20.0f
+ extendingStart:YES extendingEnd:YES shading:shading];
+
+ // radial fill
+ NSRect radialFillRect = NSMakeRect(110.0f, 10.0f, 90.0f, 90.0f);
+ shadedPath = [NSBezierPath bezierPathWithRect:radialFillRect];
+ startPoint = NSMakePoint(radialFillRect.origin.x + 20.0f,
+ radialFillRect.origin.y + 20.0f);
+ endPoint = NSMakePoint(radialFillRect.origin.x + radialFillRect.size.width - 20.0f,
+ radialFillRect.origin.y + radialFillRect.size.height - 20.0f);
+ [shadedPath gtm_fillRadiallyFrom:startPoint fromRadius:10.0f
+ to:endPoint toRadius:20.0f
+ extendingStart:YES extendingEnd:YES shading:shading];
+}
+
+@end
diff --git a/AppKit/GTMNSColor+Theme.h b/AppKit/GTMNSColor+Theme.h
new file mode 100644
index 0000000..ef121eb
--- /dev/null
+++ b/AppKit/GTMNSColor+Theme.h
@@ -0,0 +1,62 @@
+//
+// GTMNSColor+Theme.m
+//
+// Category for working with Themes and NSColor
+//
+// Copyright 2006-2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+
+#import <Cocoa/Cocoa.h>
+#import <Carbon/Carbon.h>
+
+/// Category for working with Themes and NSColor
+@interface NSColor (GTMColorThemeAdditions)
+
+/// Create up an NSColor based on a Theme Text Color.
+/// Colors will be in the DeviceRGB color space
++ (id)gtm_colorWithThemeTextColor:(ThemeTextColor)textColor;
+
+/// Create up an NSColor based on a Theme Brush
+/// Colors will be in the DeviceRGB color space
++ (id)gtm_colorWithThemeBrush:(ThemeBrush)brush;
+
+/// Device colors for drawing UI elements and such
++ (NSColor *)gtm_deviceBlackColor; /* 0.0 white */
++ (NSColor *)gtm_deviceDarkGrayColor; /* 0.333 white */
++ (NSColor *)gtm_deviceLightGrayColor; /* 0.667 white */
++ (NSColor *)gtm_deviceWhiteColor; /* 1.0 white */
++ (NSColor *)gtm_deviceGrayColor; /* 0.5 white */
++ (NSColor *)gtm_deviceRedColor; /* 1.0, 0.0, 0.0 RGB */
++ (NSColor *)gtm_deviceGreenColor; /* 0.0, 1.0, 0.0 RGB */
++ (NSColor *)gtm_deviceBlueColor; /* 0.0, 0.0, 1.0 RGB */
++ (NSColor *)gtm_deviceCyanColor; /* 0.0, 1.0, 1.0 RGB */
++ (NSColor *)gtm_deviceYellowColor; /* 1.0, 1.0, 0.0 RGB */
++ (NSColor *)gtm_deviceMagentaColor; /* 1.0, 0.0, 1.0 RGB */
++ (NSColor *)gtm_deviceOrangeColor; /* 1.0, 0.5, 0.0 RGB */
++ (NSColor *)gtm_devicePurpleColor; /* 0.5, 0.0, 0.5 RGB */
++ (NSColor *)gtm_deviceBrownColor; /* 0.6, 0.4, 0.2 RGB */
++ (NSColor *)gtm_deviceClearColor; /* 0.0 white, 0.0 alpha */
+
+/// default colorWithAlphaComponent has a bug where it doesn't maintain colorspace
+// Radar 5047862 [NSColor colorWithAlphaComponent] does NOT maintain colorspace.
+- (NSColor *)gtm_safeColorWithAlphaComponent:(float)alpha;
+
+// gives us a color for any color space.
+// Should never return nil, but will if there is an error (out of memory?)
+// converting a color to a different colorspace.
+// Radar 5608378 Would like theme brush value for the menu item highlight color
+// Radar 5608444 colorUsingColorSpaceName doesn't work for NSDeviceRGBColorSpace and SystemColors
+- (NSColor *)gtm_safeColorUsingColorSpaceName:(NSString *)colorSpaceName;
+@end
diff --git a/AppKit/GTMNSColor+Theme.m b/AppKit/GTMNSColor+Theme.m
new file mode 100644
index 0000000..b3437e7
--- /dev/null
+++ b/AppKit/GTMNSColor+Theme.m
@@ -0,0 +1,192 @@
+//
+// GTMNSColor+Theme.m
+//
+// Category for working with Themes and NSColor
+//
+// Copyright 2006-2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+
+#import "GTMNSColor+Theme.h"
+
+@implementation NSColor (GTMColorThemeAdditions)
+
+/// Create up an NSColor based on a Theme Text Color
+/// Colors will be in the device color space
++ (id)gtm_colorWithThemeTextColor:(ThemeTextColor)textColor {
+ NSColor *nsTextColor = nil;
+ RGBColor rgbTextColor;
+ OSStatus status = GetThemeTextColor(textColor, 32, true, &rgbTextColor);
+ if (status == noErr) {
+ float red = rgbTextColor.red / 65535.0f;
+ float green = rgbTextColor.green / 65535.0f;
+ float blue = rgbTextColor.blue / 65535.0f;
+ nsTextColor = [NSColor colorWithDeviceRed:red
+ green:green
+ blue:blue
+ alpha:1.0f];
+ } else {
+#ifdef DEBUG
+ NSLog(@"Unable to create color for textcolor %d", textColor);
+#endif
+ }
+ return nsTextColor;
+}
+
+/// Create up an NSColor based on a Theme Brush
+/// Colors will be in the DeviceRGB color space
++ (id)gtm_colorWithThemeBrush:(ThemeBrush)brush {
+ NSColor *nsBrushColor = nil;
+ RGBColor rgbBrushColor;
+ OSStatus status = GetThemeBrushAsColor(brush, 32, true, &rgbBrushColor);
+ if (status == noErr) {
+ nsBrushColor = [NSColor colorWithDeviceRed:rgbBrushColor.red / 65535.0f
+ green:rgbBrushColor.green / 65535.0f
+ blue:rgbBrushColor.blue / 65535.0f
+ alpha:1.0f];
+ } else {
+#ifdef DEBUG
+ NSLog(@"Unable to create color for brushcolor %d", brush);
+#endif
+ }
+ return nsBrushColor;
+}
+
++ (NSColor *)gtm_deviceBlackColor { /* 0.0f white */
+ return [NSColor colorWithDeviceWhite:0.0f alpha:1.0f];
+}
+
++ (NSColor *)gtm_deviceDarkGrayColor { /* 0.333 white */
+ return [NSColor colorWithDeviceWhite:1.0f/3.0f alpha:1.0f];
+}
+
++ (NSColor *)gtm_deviceLightGrayColor { /* 0.667 white */
+ return [NSColor colorWithDeviceWhite:2.0f/3.0f alpha:1.0f];
+}
+
++ (NSColor *)gtm_deviceWhiteColor { /* 1.0f white */
+ return [NSColor colorWithDeviceWhite:1.0f alpha:1.0f];
+}
+
++ (NSColor *)gtm_deviceGrayColor { /* 0.5 white */
+ return [NSColor colorWithDeviceWhite:0.5f alpha:1.0f];
+}
+
++ (NSColor *)gtm_deviceRedColor { /* 1.0f, 0.0f, 0.0f RGB */
+ return [NSColor colorWithDeviceRed:1.0f green:0.0f blue: 0.0f alpha:1.0f];
+}
+
++ (NSColor *)gtm_deviceGreenColor { /* 0.0f, 1.0f, 0.0f RGB */
+return [NSColor colorWithDeviceRed:0.0f green:1.0f blue: 0.0f alpha:1.0f];
+}
+
++ (NSColor *)gtm_deviceBlueColor { /* 0.0f, 0.0f, 1.0f RGB */
+return [NSColor colorWithDeviceRed:0.0f green:0.0f blue: 1.0f alpha:1.0f];
+}
+
++ (NSColor *)gtm_deviceCyanColor { /* 0.0f, 1.0f, 1.0f RGB */
+return [NSColor colorWithDeviceRed:0.0f green:1.0f blue: 1.0f alpha:1.0f];
+}
+
++ (NSColor *)gtm_deviceYellowColor { /* 1.0f, 1.0f, 0.0f RGB */
+return [NSColor colorWithDeviceRed:1.0f green:1.0f blue: 0.0f alpha:1.0f];
+}
+
++ (NSColor *)gtm_deviceMagentaColor { /* 1.0f, 0.0f, 1.0f RGB */
+return [NSColor colorWithDeviceRed:1.0f green:0.0f blue: 1.0f alpha:1.0f];
+}
+
++ (NSColor *)gtm_deviceOrangeColor { /* 1.0f, 0.5, 0.0f RGB */
+return [NSColor colorWithDeviceRed:1.0f green:0.5f blue: 0.0f alpha:1.0f];
+}
+
++ (NSColor *)gtm_devicePurpleColor { /* 0.5, 0.0f, 0.5 RGB */
+return [NSColor colorWithDeviceRed:0.5f green:0.0f blue: 0.5f alpha:1.0f];
+}
+
++ (NSColor *)gtm_deviceBrownColor { /* 0.6, 0.4, 0.2 RGB */
+return [NSColor colorWithDeviceRed:0.6f green:0.4f blue: 0.2f alpha:1.0f];
+}
+
++ (NSColor *)gtm_deviceClearColor { /* 0.0f white, 0.0f alpha */
+ return [NSColor colorWithDeviceWhite:0.0f alpha:0.0f];
+}
+
+- (NSColor *)gtm_safeColorWithAlphaComponent:(float)alpha {
+ // This mess is here because of
+ // Radar 5047862 [NSColor colorWithAlphaComponent] does NOT maintain colorspace.
+ // As of 10.4.8, colorWithAlphaComponent will return an NSCalibratedRGBColor
+ // instead of a NSDeviceRGBColor when you call colorWithAlphaComponent on
+ // a NSDeviceRGBColor even though the docs say "Creates and returns an NSColor
+ // object that has the same color space and component values as the receiver"
+ // We must use exceptions in case somebody attempts to put a pattern through
+ // here. The assumption being made is that alpha is the last color component
+ // which it is for all current cases.
+ NSColor *newColor = nil;
+
+#if (MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4)
+ @try {
+ int componentCount = [self numberOfComponents];
+ float *components = (float*)calloc(componentCount, sizeof(float));
+ if (!components) {
+ NSException *exception = [NSException exceptionWithName:NSMallocException
+ reason:@"Unable to malloc components"
+ userInfo:nil];
+ @throw exception;
+ }
+ [self getComponents:components];
+ components[componentCount - 1] = alpha;
+ newColor = [NSColor colorWithColorSpace:[self colorSpace]
+ components:components count:componentCount];
+ free(components);
+ }
+ @catch (NSException *ex) {
+ // Probably passed us a pattern. I'm not even sure how Apple deals with
+ // changing the alpha of a pattern color.
+ newColor = [self colorWithAlphaComponent:alpha];
+ }
+#else
+ // Radar 5047862 is fixed in Leopard.
+ newColor = [self colorWithAlphaComponent:alpha];
+#endif
+ return newColor;
+}
+
+- (NSColor *)gtm_safeColorUsingColorSpaceName:(NSString *)colorSpaceName {
+ NSColor *outColor = [self colorUsingColorSpaceName:colorSpaceName];
+ if (!outColor) {
+ NSBitmapImageRep *rep = [[[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
+ pixelsWide:1
+ pixelsHigh:1
+ bitsPerSample:8
+ samplesPerPixel:4
+ hasAlpha:YES
+ isPlanar:NO
+ colorSpaceName:colorSpaceName
+ bytesPerRow:0
+ bitsPerPixel:0] autorelease];
+ NSGraphicsContext *context = [NSGraphicsContext graphicsContextWithBitmapImageRep:rep];
+ if (context) {
+ NSRect rect = NSMakeRect(0, 0, 1, 1);
+ [NSGraphicsContext saveGraphicsState];
+ [NSGraphicsContext setCurrentContext:context];
+ [self setFill];
+ NSRectFill(rect);
+ [NSGraphicsContext restoreGraphicsState];
+ outColor = [rep colorAtX:0 y:0];
+ }
+ }
+ return outColor;
+}
+@end
diff --git a/AppKit/GTMNSColor+ThemeTest.m b/AppKit/GTMNSColor+ThemeTest.m
new file mode 100644
index 0000000..d3c9db1
--- /dev/null
+++ b/AppKit/GTMNSColor+ThemeTest.m
@@ -0,0 +1,249 @@
+//
+// NSColor+ThemeTest.m
+//
+// Copyright 2006-2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+
+#import <SenTestingKit/SenTestingKit.h>
+#import "GTMNSColor+Theme.h"
+#import "GTMNSWorkspace+Theme.h"
+#import "GTMSystemVersion.h"
+
+
+@interface GTMNSColor_ThemeTest : SenTestCase
+@end
+
+@implementation GTMNSColor_ThemeTest
+
+//METHOD_CHECK(NSWorkspace, themeAppearance);
+
+// utility function for giving random floats between 0.0f and 1.0f
+static float Randomf() {
+ float val = random();
+ return val / INT32_MAX;
+}
+
+- (void)testColorWithThemeTextColor {
+ float colorValues[][4] = {
+ { 1.000000, 1.000000, 1.000000, 1.000000 },
+ { 0.000000, 0.000000, 0.000000, 1.000000 },
+ { 0.000000, 0.000000, 0.000000, 0.000000 },
+ { 0.000000, 0.000000, 0.000000, 1.000000 },
+ { 0.499992, 0.499992, 0.499992, 1.000000 },
+ { 0.000000, 0.000000, 0.000000, 1.000000 },
+ { 0.499992, 0.499992, 0.499992, 1.000000 },
+ { 0.000000, 0.000000, 0.000000, 1.000000 },
+ { 0.499992, 0.499992, 0.499992, 1.000000 },
+ { 0.000000, 0.000000, 0.000000, 1.000000 },
+ { 0.499992, 0.499992, 0.499992, 1.000000 },
+ { 0.000000, 0.000000, 0.000000, 1.000000 },
+ { 0.499992, 0.499992, 0.499992, 1.000000 },
+ { 0.000000, 0.000000, 0.000000, 1.000000 },
+ { 0.000000, 0.000000, 0.000000, 1.000000 },
+ { 0.499992, 0.499992, 0.499992, 1.000000 },
+ { 0.000000, 0.000000, 0.000000, 1.000000 },
+ { 0.000000, 0.000000, 0.000000, 1.000000 },
+ { 0.499992, 0.499992, 0.499992, 1.000000 },
+ { 0.000000, 0.000000, 0.000000, 1.000000 },
+ { 0.000000, 0.000000, 0.000000, 1.000000 },
+ { 0.499992, 0.499992, 0.499992, 1.000000 },
+ { 0.000000, 0.000000, 0.000000, 1.000000 },
+ { 0.000000, 0.000000, 0.000000, 1.000000 },
+ { 0.000000, 0.000000, 0.000000, 1.000000 },
+ { 0.000000, 0.000000, 0.000000, 1.000000 },
+ { 0.449989, 0.449989, 0.449989, 1.000000 },
+ { 0.000000, 0.000000, 0.000000, 1.000000 },
+ { 0.449989, 0.449989, 0.449989, 1.000000 },
+ { 0.000000, 0.000000, 0.000000, 1.000000 },
+ { 0.499992, 0.499992, 0.499992, 1.000000 },
+ { 0.000000, 0.000000, 0.000000, 1.000000 },
+ { 0.499992, 0.499992, 0.499992, 1.000000 },
+ { 0.000000, 0.000000, 0.000000, 1.000000 },
+ { 1.000000, 1.000000, 1.000000, 1.000000 },
+ { 0.599985, 0.599985, 0.599985, 1.000000 },
+ { 0.000000, 0.000000, 0.000000, 1.000000 },
+ { 1.000000, 1.000000, 1.000000, 1.000000 },
+ { 0.599985, 0.599985, 0.599985, 1.000000 },
+ { 0.000000, 0.000000, 0.000000, 1.000000 },
+ { 0.499992, 0.499992, 0.499992, 1.000000 },
+ { 0.000000, 0.000000, 0.000000, 1.000000 },
+ { 0.000000, 0.000000, 0.000000, 1.000000 },
+ { 0.000000, 0.000000, 0.000000, 1.000000 },
+ { 0.499992, 0.499992, 0.499992, 1.000000 },
+ { 0.499992, 0.499992, 0.499992, 1.000000 },
+ { 0.000000, 0.000000, 0.000000, 1.000000 },
+ { 0.000000, 0.000000, 0.000000, 1.000000 },
+ { 0.499992, 0.499992, 0.499992, 1.000000 },
+ { 0.000000, 0.000000, 0.000000, 1.000000 }
+ };
+
+ if ([GTMSystemVersion isLeopardOrGreater]) {
+ // kThemeTextColorRootMenuDisabled changed to white in Leopard.
+ colorValues[35][0] = 1.0;
+ colorValues[35][1] = 1.0;
+ colorValues[35][2] = 1.0;
+ }
+ for(int i = kThemeTextColorWhite; i < kThemeTextColorSystemDetail; i++) {
+ if (i == 0) continue;
+ NSColor *textColor = [NSColor gtm_colorWithThemeTextColor:i];
+ float nsComponents[5];
+ [textColor getComponents: nsComponents];
+ for(int j = 0; j < 4; j++) {
+ STAssertEqualsWithAccuracy(nsComponents[j], colorValues[i + 2][j], 0.000001,
+ @"Theme Text Color %d is wrong", i);
+ STAssertEqualObjects([textColor colorSpaceName], NSDeviceRGBColorSpace,
+ @"Color space must be DeviceRGB");
+ }
+ }
+}
+
+- (void)testColorWithThemeBrushColor {
+ float colorValues[][4] = {
+ { 1.000000, 1.000000, 1.000000, 1.000000 },
+ { 0.000000, 0.000000, 0.000000, 1.000000 },
+ { 0.000000, 0.000000, 0.000000, 0.000000 },
+ { 1.000000, 1.000000, 1.000000, 1.000000 },
+ { 1.000000, 1.000000, 1.000000, 1.000000 },
+ { 1.000000, 1.000000, 1.000000, 1.000000 },
+ { 1.000000, 1.000000, 1.000000, 1.000000 },
+ { 1.000000, 1.000000, 1.000000, 1.000000 },
+ { 1.000000, 1.000000, 1.000000, 1.000000 },
+ { 1.000000, 1.000000, 1.000000, 1.000000 },
+ { 1.000000, 1.000000, 1.000000, 1.000000 },
+ { 1.000000, 1.000000, 1.000000, 1.000000 },
+ { 1.000000, 1.000000, 1.000000, 1.000000 },
+ { 0.980011, 0.990005, 0.990005, 1.000000 },
+ { 1.000000, 1.000000, 1.000000, 1.000000 },
+ { 0.000000, 0.000000, 0.000000, 1.000000 },
+ { 0.709789, 0.835294, 1.000000, 1.000000 },
+ { 1.000000, 1.000000, 1.000000, 1.000000 },
+ { 1.000000, 1.000000, 1.000000, 1.000000 },
+ { 1.000000, 1.000000, 1.000000, 1.000000 },
+ { 1.000000, 1.000000, 1.000000, 1.000000 },
+ { 0.492195, 0.675792, 0.847669, 1.000000 },
+ { 0.000000, 0.000000, 0.000000, 1.000000 },
+ { 0.000000, 0.000000, 0.000000, 1.000000 },
+ { 0.500008, 0.500008, 0.500008, 1.000000 },
+ { 1.000000, 1.000000, 1.000000, 1.000000 },
+ { 0.600000, 0.600000, 0.600000, 1.000000 },
+ { 1.000000, 1.000000, 1.000000, 1.000000 },
+ { 1.000000, 1.000000, 1.000000, 1.000000 },
+ { 0.510002, 0.510002, 0.510002, 1.000000 },
+ { 0.760006, 0.760006, 0.760006, 1.000000 },
+ { 0.940002, 0.940002, 0.940002, 1.000000 },
+ { 0.980011, 0.980011, 0.980011, 1.000000 },
+ { 0.670008, 0.670008, 0.670008, 1.000000 },
+ { 0.860014, 0.860014, 0.860014, 1.000000 },
+ { 0.880003, 0.880003, 0.880003, 1.000000 },
+ { 0.880003, 0.880003, 0.880003, 1.000000 },
+ { 0.990005, 0.990005, 0.990005, 1.000000 },
+ { 0.900008, 0.900008, 0.900008, 1.000000 },
+ { 0.930007, 0.930007, 0.930007, 1.000000 },
+ { 0.930007, 0.930007, 0.930007, 1.000000 },
+ { 0.990005, 0.990005, 0.990005, 1.000000 },
+ { 0.540002, 0.540002, 0.540002, 1.000000 },
+ { 0.590005, 0.590005, 0.590005, 1.000000 },
+ { 0.590005, 0.590005, 0.590005, 1.000000 },
+ { 0.730007, 0.730007, 0.730007, 1.000000 },
+ { 0.640009, 0.640009, 0.640009, 1.000000 },
+ { 0.640009, 0.640009, 0.640009, 1.000000 },
+ { 0.820005, 0.820005, 0.820005, 1.000000 },
+ { 0.820005, 0.820005, 0.820005, 1.000000 },
+ { 1.000000, 1.000000, 1.000000, 1.000000 },
+ { 1.000000, 1.000000, 1.000000, 1.000000 },
+ { 1.000000, 1.000000, 1.000000, 1.000000 },
+ { 1.000000, 1.000000, 1.000000, 1.000000 },
+ { 1.000000, 1.000000, 1.000000, 1.000000 },
+ { 1.000000, 1.000000, 1.000000, 1.000000 },
+ { 1.000000, 1.000000, 1.000000, 1.000000 },
+ { 1.000000, 1.000000, 1.000000, 1.000000 },
+ { 1.000000, 1.000000, 1.000000, 1.000000 },
+ { 0.925490, 0.952895, 0.996094, 1.000000 }
+ };
+
+ NSString *theme = [[NSWorkspace sharedWorkspace] gtm_themeAppearance];
+ if ([theme isEqualToString:(NSString*)kThemeAppearanceAquaGraphite]) {
+ // These are the only two brushes that change with an appearance change
+ colorValues[21][0] = 0.605478;
+ colorValues[21][1] = 0.667979;
+ colorValues[21][2] = 0.738293;
+ colorValues[59][0] = 0.941192;
+ colorValues[59][1] = 0.941192;
+ colorValues[59][2] = 0.941192;
+ }
+ for(int i = kThemeBrushWhite; i < kThemeBrushListViewColumnDivider; i++) {
+ // Brush "14" is the selection, so it will change depending on the system
+ // There is no brush 0.
+ if (i == 0 || i == 14) continue;
+ NSColor *brushColor = [NSColor gtm_colorWithThemeBrush:i];
+ float nsComponents[5];
+ [brushColor getComponents: nsComponents];
+ for(int j = 0; j < 4; j++) {
+ STAssertEqualsWithAccuracy(nsComponents[j], colorValues[i + 2][j], 0.000001,
+ @"Theme Text Brush %d is wrong", i + 2);
+ STAssertEqualObjects([brushColor colorSpaceName], NSDeviceRGBColorSpace,
+ @"Color space must be DeviceRGB");
+ }
+ }
+}
+
+- (void)testSafeColorWithAlphaComponent {
+ NSColorSpace *testSpace[6];
+ testSpace[0] = [NSColorSpace genericRGBColorSpace];
+ testSpace[1] = [NSColorSpace genericGrayColorSpace];
+ testSpace[2] = [NSColorSpace genericCMYKColorSpace];
+ testSpace[3] = [NSColorSpace deviceRGBColorSpace];
+ testSpace[4] = [NSColorSpace deviceGrayColorSpace];
+ testSpace[5] = [NSColorSpace deviceCMYKColorSpace];
+
+ float comp[5];
+ for (int i = 0; i < sizeof(comp) / sizeof(float); ++i) {
+ comp[i] = Randomf();
+ }
+
+ float alpha = Randomf();
+ for (int i = 0; i < sizeof(testSpace) / sizeof(NSColorSpace*); ++i) {
+ int componentCount = [testSpace[i] numberOfColorComponents];
+ NSColor *color = [NSColor colorWithColorSpace:testSpace[i]
+ components:comp
+ count:componentCount + 1];
+ NSColor *alphaColor = [color gtm_safeColorWithAlphaComponent:alpha];
+
+ float alphaSwap = comp[componentCount];
+ comp[componentCount] = alpha;
+ NSColor *testColor = [NSColor colorWithColorSpace:testSpace[i]
+ components:comp
+ count:componentCount + 1];
+ comp[componentCount] = alphaSwap;
+ STAssertEqualObjects(alphaColor,
+ testColor,
+ @"Compare failed with components: %f %f %f %f %f %f alpha: %f",
+ testSpace[0], testSpace[1], testSpace[2],
+ testSpace[3], testSpace[4], testSpace[5],
+ alpha);
+ }
+}
+
+- (void)testSafeColorUsingColorSpaceName {
+
+ NSColor *brushColor = [[NSColor selectedMenuItemColor] colorUsingColorSpaceName:NSDeviceRGBColorSpace];
+ STAssertNil(brushColor, @"This doesn't work on Tiger or Leopard");
+
+ brushColor = [[NSColor selectedMenuItemColor] gtm_safeColorUsingColorSpaceName:NSDeviceRGBColorSpace];
+ STAssertNotNil(brushColor, nil);
+}
+
+
+@end
diff --git a/AppKit/GTMNSWorkspace+Theme.h b/AppKit/GTMNSWorkspace+Theme.h
new file mode 100644
index 0000000..364d623
--- /dev/null
+++ b/AppKit/GTMNSWorkspace+Theme.h
@@ -0,0 +1,39 @@
+//
+// GTMNSWorkspace+ScreenSaver.h
+//
+// Copyright 2007-2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+
+#import <Cocoa/Cocoa.h>
+#import <Carbon/Carbon.h>
+
+enum {
+ // This means that we had a failure getting the style
+ kThemeScrollBarArrowsInvalid = -1,
+ // This is the "missing" scroll bar arrow style that some people use
+ kThemeScrollBarArrowsDouble = 2
+};
+
+/// Category for interacting with the screen saver
+@interface NSWorkspace (GTMWorkspaceThemeAddition)
+
+// returns one of the kThemeAppearance... constants as an autoreleased NSString
+// tells you whether we are running under blue or graphite
+- (NSString*)gtm_themeAppearance;
+
+// Returns how the user has their scroll bars configured.
+- (ThemeScrollBarArrowStyle)gtm_themeScrollBarArrowStyle;
+@end
+
diff --git a/AppKit/GTMNSWorkspace+Theme.m b/AppKit/GTMNSWorkspace+Theme.m
new file mode 100644
index 0000000..86b9935
--- /dev/null
+++ b/AppKit/GTMNSWorkspace+Theme.m
@@ -0,0 +1,42 @@
+//
+// GTMNSWorkspace+Theme.m
+//
+// Copyright 2007-2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+
+#import "GTMNSWorkspace+Theme.h"
+#import "GTMGarbageCollection.h"
+
+@implementation NSWorkspace (GTMWorkspaceThemeAddition)
+
+- (NSString*)gtm_themeAppearance {
+ CFStringRef cfTheme;
+ NSString *nsTheme = nil;
+ OSStatus theStatus = CopyThemeIdentifier(&cfTheme);
+ if (theStatus == noErr) {
+ nsTheme = [GTMNSMakeCollectable(cfTheme) autorelease];
+ }
+ return nsTheme;
+}
+
+- (ThemeScrollBarArrowStyle)gtm_themeScrollBarArrowStyle {
+ ThemeScrollBarArrowStyle style = kThemeScrollBarArrowsInvalid;
+ OSStatus theStatus = GetThemeScrollBarArrowStyle(&style);
+ if (theStatus != noErr) {
+ style = kThemeScrollBarArrowsInvalid;
+ }
+ return style;
+}
+@end
diff --git a/AppKit/GTMNSWorkspace+ThemeTest.m b/AppKit/GTMNSWorkspace+ThemeTest.m
new file mode 100644
index 0000000..213de22
--- /dev/null
+++ b/AppKit/GTMNSWorkspace+ThemeTest.m
@@ -0,0 +1,37 @@
+//
+// NSWorkspace+ThemeTest.m
+//
+// Copyright 2007-2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+
+#import <SenTestingKit/SenTestingKit.h>
+#import "GTMNSWorkspace+Theme.h"
+
+@interface GTMNSWorkspace_ThemeTest : SenTestCase
+@end
+
+@implementation GTMNSWorkspace_ThemeTest
+
+- (void)testThemeAppearance {
+ NSString *theme = [[NSWorkspace sharedWorkspace] gtm_themeAppearance];
+ STAssertNotNil(theme, nil);
+ STAssertTrue([theme hasPrefix:(NSString*)kThemeAppearanceAqua], nil);
+}
+
+- (void)testThemeScrollBarArrowStyle {
+ ThemeScrollBarArrowStyle style = [[NSWorkspace sharedWorkspace] gtm_themeScrollBarArrowStyle];
+ STAssertTrue(style <= kThemeScrollBarArrowsDouble, nil);
+}
+@end
diff --git a/AppKit/GTMShading.h b/AppKit/GTMShading.h
new file mode 100644
index 0000000..27e163e
--- /dev/null
+++ b/AppKit/GTMShading.h
@@ -0,0 +1,41 @@
+//
+// GTMShading.h
+//
+// A protocol for an object that can be used as a shader.
+//
+// Copyright 2006-2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+
+/// \cond Protocols
+
+@protocol GTMShading
+// Returns the shadefunction for using in a shader.
+// This shadefunction shoud never be released. It is owned by the implementor
+// of the GTMShading protocol.
+//
+// Returns:
+// a shading function.
+- (CGFunctionRef)shadeFunction;
+
+// Returns the colorSpace for using in a shader.
+// This colorSpace shoud never be released. It is owned by the implementor
+// of the GTMShading protocol.
+//
+// Returns:
+// a color space.
+- (CGColorSpaceRef)colorSpace;
+@end
+
+/// \endcond
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 <Cocoa/Cocoa.h>
+
+/// 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 <SenTestingKit/SenTestingKit.h>
+#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 <Foundation/Foundation.h>
+
+// 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 <Foundation/Foundation.h>
+
+/// 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 <zlib.h>
+
+#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 <stdlib.h> // for randiom/srandomdev
+#import <zlib.h>
+
+#import <SenTestingKit/SenTestingKit.h>
+
+@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 <Foundation/Foundation.h>
+
+/// 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. <stdint.h>
+ 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. <stdint.h>
+ 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 <SenTestingKit/SenTestingKit.h>
+#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 <Foundation/Foundation.h>
+
+/// Utilities for NSStrings containing HTML
+@interface NSString (GTMNSStringHTMLAdditions)
+
+/// Get a string where internal characters that need escaping for HTML are escaped
+//
+/// For example, '&' become '&amp;'. 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 '&amp;'
+/// 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, '&amp;' becomes '&'
+/// Handles &#32; and &#x32; 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
+ { @"&quot;", 34 },
+ { @"&amp;", 38 },
+ { @"&apos;", 39 },
+ { @"&lt;", 60 },
+ { @"&gt;", 62 },
+
+ // A.2.1. Latin-1 characters
+ { @"&nbsp;", 160 },
+ { @"&iexcl;", 161 },
+ { @"&cent;", 162 },
+ { @"&pound;", 163 },
+ { @"&curren;", 164 },
+ { @"&yen;", 165 },
+ { @"&brvbar;", 166 },
+ { @"&sect;", 167 },
+ { @"&uml;", 168 },
+ { @"&copy;", 169 },
+ { @"&ordf;", 170 },
+ { @"&laquo;", 171 },
+ { @"&not;", 172 },
+ { @"&shy;", 173 },
+ { @"&reg;", 174 },
+ { @"&macr;", 175 },
+ { @"&deg;", 176 },
+ { @"&plusmn;", 177 },
+ { @"&sup2;", 178 },
+ { @"&sup3;", 179 },
+ { @"&acute;", 180 },
+ { @"&micro;", 181 },
+ { @"&para;", 182 },
+ { @"&middot;", 183 },
+ { @"&cedil;", 184 },
+ { @"&sup1;", 185 },
+ { @"&ordm;", 186 },
+ { @"&raquo;", 187 },
+ { @"&frac14;", 188 },
+ { @"&frac12;", 189 },
+ { @"&frac34;", 190 },
+ { @"&iquest;", 191 },
+ { @"&Agrave;", 192 },
+ { @"&Aacute;", 193 },
+ { @"&Acirc;", 194 },
+ { @"&Atilde;", 195 },
+ { @"&Auml;", 196 },
+ { @"&Aring;", 197 },
+ { @"&AElig;", 198 },
+ { @"&Ccedil;", 199 },
+ { @"&Egrave;", 200 },
+ { @"&Eacute;", 201 },
+ { @"&Ecirc;", 202 },
+ { @"&Euml;", 203 },
+ { @"&Igrave;", 204 },
+ { @"&Iacute;", 205 },
+ { @"&Icirc;", 206 },
+ { @"&Iuml;", 207 },
+ { @"&ETH;", 208 },
+ { @"&Ntilde;", 209 },
+ { @"&Ograve;", 210 },
+ { @"&Oacute;", 211 },
+ { @"&Ocirc;", 212 },
+ { @"&Otilde;", 213 },
+ { @"&Ouml;", 214 },
+ { @"&times;", 215 },
+ { @"&Oslash;", 216 },
+ { @"&Ugrave;", 217 },
+ { @"&Uacute;", 218 },
+ { @"&Ucirc;", 219 },
+ { @"&Uuml;", 220 },
+ { @"&Yacute;", 221 },
+ { @"&THORN;", 222 },
+ { @"&szlig;", 223 },
+ { @"&agrave;", 224 },
+ { @"&aacute;", 225 },
+ { @"&acirc;", 226 },
+ { @"&atilde;", 227 },
+ { @"&auml;", 228 },
+ { @"&aring;", 229 },
+ { @"&aelig;", 230 },
+ { @"&ccedil;", 231 },
+ { @"&egrave;", 232 },
+ { @"&eacute;", 233 },
+ { @"&ecirc;", 234 },
+ { @"&euml;", 235 },
+ { @"&igrave;", 236 },
+ { @"&iacute;", 237 },
+ { @"&icirc;", 238 },
+ { @"&iuml;", 239 },
+ { @"&eth;", 240 },
+ { @"&ntilde;", 241 },
+ { @"&ograve;", 242 },
+ { @"&oacute;", 243 },
+ { @"&ocirc;", 244 },
+ { @"&otilde;", 245 },
+ { @"&ouml;", 246 },
+ { @"&divide;", 247 },
+ { @"&oslash;", 248 },
+ { @"&ugrave;", 249 },
+ { @"&uacute;", 250 },
+ { @"&ucirc;", 251 },
+ { @"&uuml;", 252 },
+ { @"&yacute;", 253 },
+ { @"&thorn;", 254 },
+ { @"&yuml;", 255 },
+
+ // A.2.2. Special characters cont'd
+ { @"&OElig;", 338 },
+ { @"&oelig;", 339 },
+ { @"&Scaron;", 352 },
+ { @"&scaron;", 353 },
+ { @"&Yuml;", 376 },
+
+ // A.2.3. Symbols
+ { @"&fnof;", 402 },
+
+ // A.2.2. Special characters cont'd
+ { @"&circ;", 710 },
+ { @"&tilde;", 732 },
+
+ // A.2.3. Symbols cont'd
+ { @"&Alpha;", 913 },
+ { @"&Beta;", 914 },
+ { @"&Gamma;", 915 },
+ { @"&Delta;", 916 },
+ { @"&Epsilon;", 917 },
+ { @"&Zeta;", 918 },
+ { @"&Eta;", 919 },
+ { @"&Theta;", 920 },
+ { @"&Iota;", 921 },
+ { @"&Kappa;", 922 },
+ { @"&Lambda;", 923 },
+ { @"&Mu;", 924 },
+ { @"&Nu;", 925 },
+ { @"&Xi;", 926 },
+ { @"&Omicron;", 927 },
+ { @"&Pi;", 928 },
+ { @"&Rho;", 929 },
+ { @"&Sigma;", 931 },
+ { @"&Tau;", 932 },
+ { @"&Upsilon;", 933 },
+ { @"&Phi;", 934 },
+ { @"&Chi;", 935 },
+ { @"&Psi;", 936 },
+ { @"&Omega;", 937 },
+ { @"&alpha;", 945 },
+ { @"&beta;", 946 },
+ { @"&gamma;", 947 },
+ { @"&delta;", 948 },
+ { @"&epsilon;", 949 },
+ { @"&zeta;", 950 },
+ { @"&eta;", 951 },
+ { @"&theta;", 952 },
+ { @"&iota;", 953 },
+ { @"&kappa;", 954 },
+ { @"&lambda;", 955 },
+ { @"&mu;", 956 },
+ { @"&nu;", 957 },
+ { @"&xi;", 958 },
+ { @"&omicron;", 959 },
+ { @"&pi;", 960 },
+ { @"&rho;", 961 },
+ { @"&sigmaf;", 962 },
+ { @"&sigma;", 963 },
+ { @"&tau;", 964 },
+ { @"&upsilon;", 965 },
+ { @"&phi;", 966 },
+ { @"&chi;", 967 },
+ { @"&psi;", 968 },
+ { @"&omega;", 969 },
+ { @"&thetasym;", 977 },
+ { @"&upsih;", 978 },
+ { @"&piv;", 982 },
+
+ // A.2.2. Special characters cont'd
+ { @"&ensp;", 8194 },
+ { @"&emsp;", 8195 },
+ { @"&thinsp;", 8201 },
+ { @"&zwnj;", 8204 },
+ { @"&zwj;", 8205 },
+ { @"&lrm;", 8206 },
+ { @"&rlm;", 8207 },
+ { @"&ndash;", 8211 },
+ { @"&mdash;", 8212 },
+ { @"&lsquo;", 8216 },
+ { @"&rsquo;", 8217 },
+ { @"&sbquo;", 8218 },
+ { @"&ldquo;", 8220 },
+ { @"&rdquo;", 8221 },
+ { @"&bdquo;", 8222 },
+ { @"&dagger;", 8224 },
+ { @"&Dagger;", 8225 },
+ // A.2.3. Symbols cont'd
+ { @"&bull;", 8226 },
+ { @"&hellip;", 8230 },
+
+ // A.2.2. Special characters cont'd
+ { @"&permil;", 8240 },
+
+ // A.2.3. Symbols cont'd
+ { @"&prime;", 8242 },
+ { @"&Prime;", 8243 },
+
+ // A.2.2. Special characters cont'd
+ { @"&lsaquo;", 8249 },
+ { @"&rsaquo;", 8250 },
+
+ // A.2.3. Symbols cont'd
+ { @"&oline;", 8254 },
+ { @"&frasl;", 8260 },
+
+ // A.2.2. Special characters cont'd
+ { @"&euro;", 8364 },
+
+ // A.2.3. Symbols cont'd
+ { @"&image;", 8465 },
+ { @"&weierp;", 8472 },
+ { @"&real;", 8476 },
+ { @"&trade;", 8482 },
+ { @"&alefsym;", 8501 },
+ { @"&larr;", 8592 },
+ { @"&uarr;", 8593 },
+ { @"&rarr;", 8594 },
+ { @"&darr;", 8595 },
+ { @"&harr;", 8596 },
+ { @"&crarr;", 8629 },
+ { @"&lArr;", 8656 },
+ { @"&uArr;", 8657 },
+ { @"&rArr;", 8658 },
+ { @"&dArr;", 8659 },
+ { @"&hArr;", 8660 },
+ { @"&forall;", 8704 },
+ { @"&part;", 8706 },
+ { @"&exist;", 8707 },
+ { @"&empty;", 8709 },
+ { @"&nabla;", 8711 },
+ { @"&isin;", 8712 },
+ { @"&notin;", 8713 },
+ { @"&ni;", 8715 },
+ { @"&prod;", 8719 },
+ { @"&sum;", 8721 },
+ { @"&minus;", 8722 },
+ { @"&lowast;", 8727 },
+ { @"&radic;", 8730 },
+ { @"&prop;", 8733 },
+ { @"&infin;", 8734 },
+ { @"&ang;", 8736 },
+ { @"&and;", 8743 },
+ { @"&or;", 8744 },
+ { @"&cap;", 8745 },
+ { @"&cup;", 8746 },
+ { @"&int;", 8747 },
+ { @"&there4;", 8756 },
+ { @"&sim;", 8764 },
+ { @"&cong;", 8773 },
+ { @"&asymp;", 8776 },
+ { @"&ne;", 8800 },
+ { @"&equiv;", 8801 },
+ { @"&le;", 8804 },
+ { @"&ge;", 8805 },
+ { @"&sub;", 8834 },
+ { @"&sup;", 8835 },
+ { @"&nsub;", 8836 },
+ { @"&sube;", 8838 },
+ { @"&supe;", 8839 },
+ { @"&oplus;", 8853 },
+ { @"&otimes;", 8855 },
+ { @"&perp;", 8869 },
+ { @"&sdot;", 8901 },
+ { @"&lceil;", 8968 },
+ { @"&rceil;", 8969 },
+ { @"&lfloor;", 8970 },
+ { @"&rfloor;", 8971 },
+ { @"&lang;", 9001 },
+ { @"&rang;", 9002 },
+ { @"&loz;", 9674 },
+ { @"&spades;", 9824 },
+ { @"&clubs;", 9827 },
+ { @"&hearts;", 9829 },
+ { @"&diams;", 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
+ { @"&quot;", 34 },
+ { @"&amp;", 38 },
+ { @"&apos;", 39 },
+ { @"&lt;", 60 },
+ { @"&gt;", 62 },
+
+ // Latin Extended-A
+ { @"&OElig;", 338 },
+ { @"&oelig;", 339 },
+ { @"&Scaron;", 352 },
+ { @"&scaron;", 353 },
+ { @"&Yuml;", 376 },
+
+ // Spacing Modifier Letters
+ { @"&circ;", 710 },
+ { @"&tilde;", 732 },
+
+ // General Punctuation
+ { @"&ensp;", 8194 },
+ { @"&emsp;", 8195 },
+ { @"&thinsp;", 8201 },
+ { @"&zwnj;", 8204 },
+ { @"&zwj;", 8205 },
+ { @"&lrm;", 8206 },
+ { @"&rlm;", 8207 },
+ { @"&ndash;", 8211 },
+ { @"&mdash;", 8212 },
+ { @"&lsquo;", 8216 },
+ { @"&rsquo;", 8217 },
+ { @"&sbquo;", 8218 },
+ { @"&ldquo;", 8220 },
+ { @"&rdquo;", 8221 },
+ { @"&bdquo;", 8222 },
+ { @"&dagger;", 8224 },
+ { @"&Dagger;", 8225 },
+ { @"&permil;", 8240 },
+ { @"&lsaquo;", 8249 },
+ { @"&rsaquo;", 8250 },
+ { @"&euro;", 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 (&lt;) and less than 11 (&thetasym;)
+ if (length > 3 && length < 11) {
+ if ([escapeString characterAtIndex:1] == '#') {
+ unichar char2 = [escapeString characterAtIndex:2];
+ if (char2 == 'x' || char2 == 'X') {
+ // Hex escape squences &#xa3;
+ 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 &#123;
+ 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 <SenTestingKit/SenTestingKit.h>
+#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 =
+ @"&quot;&amp;&apos;&lt;&gt;&OElig;&oelig;&Scaron;&scaron;&Yuml;"
+ "&circ;&tilde;&ensp;&emsp;&thinsp;&zwnj;&zwj;&lrm;&rlm;&ndash;"
+ "&mdash;&lsquo;&rsquo;&sbquo;&ldquo;&rdquo;&bdquo;&dagger;&Dagger;"
+ "&permil;&lsaquo;&rsaquo;&euro;";
+
+ STAssertEqualObjects([string1 gtm_stringByEscapingForHTML],
+ string2,
+ @"HTML escaping failed");
+
+ STAssertEqualObjects([@"<this & that>" gtm_stringByEscapingForHTML],
+ @"&lt;this &amp; that&gt;",
+ @"HTML escaping failed");
+ NSString *string = [NSString stringWithUTF8String:"パン・&ド・カンパーニュ"];
+ NSString *escapeStr = [NSString stringWithUTF8String:"パン・&amp;ド・カンパーニュ"];
+ STAssertEqualObjects([string gtm_stringByEscapingForHTML],
+ escapeStr,
+ @"HTML escaping failed");
+
+ string = [NSString stringWithUTF8String:"abcا1ب<تdef&"];
+ STAssertEqualObjects([string gtm_stringByEscapingForHTML],
+ [NSString stringWithUTF8String:"abcا1ب&lt;تdef&amp;"],
+ @"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 =
+ @"&quot;&amp;&apos;&lt;&gt;&nbsp;&iexcl;&cent;&pound;&curren;&yen;"
+ "&brvbar;&sect;&uml;&copy;&ordf;&laquo;&not;&shy;&reg;&macr;&deg;"
+ "&plusmn;&sup2;&sup3;&acute;&micro;&para;&middot;&cedil;&sup1;"
+ "&ordm;&raquo;&frac14;&frac12;&frac34;&iquest;&Agrave;&Aacute;"
+ "&Acirc;&Atilde;&Auml;&Aring;&AElig;&Ccedil;&Egrave;&Eacute;"
+ "&Ecirc;&Euml;&Igrave;&Iacute;&Icirc;&Iuml;&ETH;&Ntilde;&Ograve;"
+ "&Oacute;&Ocirc;&Otilde;&Ouml;&times;&Oslash;&Ugrave;&Uacute;"
+ "&Ucirc;&Uuml;&Yacute;&THORN;&szlig;&agrave;&aacute;&acirc;&atilde;"
+ "&auml;&aring;&aelig;&ccedil;&egrave;&eacute;&ecirc;&euml;&igrave;"
+ "&iacute;&icirc;&iuml;&eth;&ntilde;&ograve;&oacute;&ocirc;&otilde;"
+ "&ouml;&divide;&oslash;&ugrave;&uacute;&ucirc;&uuml;&yacute;&thorn;"
+ "&yuml;&OElig;&oelig;&Scaron;&scaron;&Yuml;&fnof;&circ;&tilde;"
+ "&Alpha;&Beta;&Gamma;&Delta;&Epsilon;&Zeta;&Eta;&Theta;&Iota;"
+ "&Kappa;&Lambda;&Mu;&Nu;&Xi;&Omicron;&Pi;&Rho;&Sigma;&Tau;"
+ "&Upsilon;&Phi;&Chi;&Psi;&Omega;&alpha;&beta;&gamma;&delta;"
+ "&epsilon;&zeta;&eta;&theta;&iota;&kappa;&lambda;&mu;&nu;&xi;"
+ "&omicron;&pi;&rho;&sigmaf;&sigma;&tau;&upsilon;&phi;&chi;&psi;"
+ "&omega;&thetasym;&upsih;&piv;&ensp;&emsp;&thinsp;&zwnj;&zwj;"
+ "&lrm;&rlm;&ndash;&mdash;&lsquo;&rsquo;&sbquo;&ldquo;&rdquo;"
+ "&bdquo;&dagger;&Dagger;&bull;&hellip;&permil;&prime;&Prime;"
+ "&lsaquo;&rsaquo;&oline;&frasl;&euro;&weierp;&image;&real;&trade;"
+ "&alefsym;&larr;&uarr;&rarr;&darr;&harr;&crarr;&lArr;&uArr;&rArr;"
+ "&dArr;&hArr;&forall;&part;&exist;&empty;&nabla;&isin;&notin;&ni;"
+ "&prod;&sum;&minus;&lowast;&radic;&prop;&infin;&ang;&and;&or;"
+ "&cap;&cup;&int;&there4;&sim;&cong;&asymp;&ne;&equiv;&le;&ge;"
+ "&sub;&sup;&nsub;&sube;&supe;&oplus;&otimes;&perp;&sdot;&lceil;"
+ "&rceil;&lfloor;&rfloor;&lang;&rang;&loz;&spades;&clubs;&hearts;"
+ "&diams;";
+
+ STAssertEqualObjects([string1 gtm_stringByEscapingForAsciiHTML],
+ string2,
+ @"HTML escaping failed");
+
+ STAssertEqualObjects([@"<this & that>" gtm_stringByEscapingForAsciiHTML],
+ @"&lt;this &amp; that&gt;",
+ @"HTML escaping failed");
+ NSString *string = [NSString stringWithUTF8String:"パン・ド・カンパーニュ"];
+ STAssertEqualObjects([string gtm_stringByEscapingForAsciiHTML],
+ @"&#12497;&#12531;&#12539;&#12489;&#12539;&#12459;"
+ "&#12531;&#12497;&#12540;&#12491;&#12517;",
+ @"HTML escaping failed");
+
+ // Mix in some right - to left
+ string = [NSString stringWithUTF8String:"abcا1ب<تdef&"];
+ STAssertEqualObjects([string gtm_stringByEscapingForAsciiHTML],
+ @"abc&#1575;1&#1576;&lt;&#1578;def&amp;",
+ @"HTML escaping failed");
+} // stringByEscapingAsciiHTML
+
+- (void)testStringByUnescapingHTML {
+ NSString *string1 =
+ @"&quot;&amp;&apos;&lt;&gt;&nbsp;&iexcl;&cent;&pound;&curren;&yen;"
+ "&brvbar;&sect;&uml;&copy;&ordf;&laquo;&not;&shy;&reg;&macr;&deg;"
+ "&plusmn;&sup2;&sup3;&acute;&micro;&para;&middot;&cedil;&sup1;"
+ "&ordm;&raquo;&frac14;&frac12;&frac34;&iquest;&Agrave;&Aacute;"
+ "&Acirc;&Atilde;&Auml;&Aring;&AElig;&Ccedil;&Egrave;&Eacute;"
+ "&Ecirc;&Euml;&Igrave;&Iacute;&Icirc;&Iuml;&ETH;&Ntilde;&Ograve;"
+ "&Oacute;&Ocirc;&Otilde;&Ouml;&times;&Oslash;&Ugrave;&Uacute;"
+ "&Ucirc;&Uuml;&Yacute;&THORN;&szlig;&agrave;&aacute;&acirc;&atilde;"
+ "&auml;&aring;&aelig;&ccedil;&egrave;&eacute;&ecirc;&euml;&igrave;"
+ "&iacute;&icirc;&iuml;&eth;&ntilde;&ograve;&oacute;&ocirc;&otilde;"
+ "&ouml;&divide;&oslash;&ugrave;&uacute;&ucirc;&uuml;&yacute;&thorn;"
+ "&yuml;&OElig;&oelig;&Scaron;&scaron;&Yuml;&fnof;&circ;&tilde;"
+ "&Alpha;&Beta;&Gamma;&Delta;&Epsilon;&Zeta;&Eta;&Theta;&Iota;"
+ "&Kappa;&Lambda;&Mu;&Nu;&Xi;&Omicron;&Pi;&Rho;&Sigma;&Tau;"
+ "&Upsilon;&Phi;&Chi;&Psi;&Omega;&alpha;&beta;&gamma;&delta;"
+ "&epsilon;&zeta;&eta;&theta;&iota;&kappa;&lambda;&mu;&nu;&xi;"
+ "&omicron;&pi;&rho;&sigmaf;&sigma;&tau;&upsilon;&phi;&chi;&psi;"
+ "&omega;&thetasym;&upsih;&piv;&ensp;&emsp;&thinsp;&zwnj;&zwj;"
+ "&lrm;&rlm;&ndash;&mdash;&lsquo;&rsquo;&sbquo;&ldquo;&rdquo;"
+ "&bdquo;&dagger;&Dagger;&bull;&hellip;&permil;&prime;&Prime;"
+ "&lsaquo;&rsaquo;&oline;&frasl;&euro;&weierp;&image;&real;&trade;"
+ "&alefsym;&larr;&uarr;&rarr;&darr;&harr;&crarr;&lArr;&uArr;&rArr;"
+ "&dArr;&hArr;&forall;&part;&exist;&empty;&nabla;&isin;&notin;&ni;"
+ "&prod;&sum;&minus;&lowast;&radic;&prop;&infin;&ang;&and;&or;"
+ "&cap;&cup;&int;&there4;&sim;&cong;&asymp;&ne;&equiv;&le;&ge;"
+ "&sub;&sup;&nsub;&sube;&supe;&oplus;&otimes;&perp;&sdot;&lceil;"
+ "&rceil;&lfloor;&rfloor;&lang;&rang;&loz;&spades;&clubs;&hearts;"
+ "&diams;";
+
+ 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([@"&#65;&#x42;&#X43;" gtm_stringByUnescapingFromHTML],
+ @"ABC", @"HTML unescaping failed");
+
+ STAssertEqualObjects([@"" gtm_stringByUnescapingFromHTML],
+ @"", @"HTML unescaping failed");
+
+ STAssertEqualObjects([@"&#65;&Bang;&#X43;" gtm_stringByUnescapingFromHTML],
+ @"A&Bang;C", @"HTML unescaping failed");
+
+ STAssertEqualObjects([@"&#65&Bang;&#X43;" gtm_stringByUnescapingFromHTML],
+ @"&#65&Bang;C", @"HTML unescaping failed");
+
+ STAssertEqualObjects([@"&#65;&Bang;&#X43" gtm_stringByUnescapingFromHTML],
+ @"A&Bang;&#X43", @"HTML unescaping failed");
+
+ STAssertEqualObjects([@"&#65A;" gtm_stringByUnescapingFromHTML],
+ @"&#65A;", @"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([@"&lt;this &amp; that&gt;" gtm_stringByUnescapingFromHTML],
+ @"<this & that>", @"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 <Foundation/Foundation.h>
+
+/// 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
+ @"&quot;",
+ @"&amp;",
+ @"&apos;",
+ @"&lt;",
+ @"&gt;",
+};
+
+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 <SenTestingKit/SenTestingKit.h>
+#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&quot;z&amp;z&apos;z&lt;z&gt;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 <Foundation/Foundation.h>
+
+/// 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 <Carbon/Carbon.h>
+#import <stdlib.h>
+
+@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 <SenTestingKit/SenTestingKit.h>
+#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 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>English</string>
+ <key>CFBundleExecutable</key>
+ <string>${EXECUTABLE_NAME}</string>
+ <key>CFBundleIdentifier</key>
+ <string>com.google.GTM</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundlePackageType</key>
+ <string>FMWK</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>CFBundleVersion</key>
+ <string>1.0</string>
+</dict>
+</plist>
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 = "<absolute>"; };
+ 0867D6A5FE840307C02AAC07 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = "<absolute>"; };
+ 1058C7B1FEA5585E11CA2CBB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = /System/Library/Frameworks/Cocoa.framework; sourceTree = "<absolute>"; };
+ 32DBCF5E0370ADEE00C91783 /* GTM_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTM_Prefix.pch; sourceTree = "<group>"; };
+ F428FEFF0D48E55E00382ED1 /* GTMNSBezierPath+CGPath.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSBezierPath+CGPath.h"; sourceTree = "<group>"; };
+ F428FF000D48E55E00382ED1 /* GTMNSBezierPath+CGPath.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSBezierPath+CGPath.m"; sourceTree = "<group>"; };
+ F428FF010D48E55E00382ED1 /* GTMNSBezierPath+CGPathTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSBezierPath+CGPathTest.m"; sourceTree = "<group>"; };
+ F428FF020D48E55E00382ED1 /* GTMNSBezierPath+CGPathTest.tif */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = "GTMNSBezierPath+CGPathTest.tif"; sourceTree = "<group>"; };
+ 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 = "<group>"; };
+ F42E089B0D199B1800D5DDE0 /* SenTestingKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SenTestingKit.framework; path = /System/Library/Frameworks/SenTestingKit.framework; sourceTree = "<absolute>"; };
+ F42E09A80D19A5E300D5DDE0 /* GTMNSWorkspace+Theme.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSWorkspace+Theme.h"; sourceTree = "<group>"; };
+ F42E09A90D19A5E300D5DDE0 /* GTMNSWorkspace+Theme.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSWorkspace+Theme.m"; sourceTree = "<group>"; };
+ F42E09AD0D19A62F00D5DDE0 /* Carbon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = /System/Library/Frameworks/Carbon.framework; sourceTree = "<absolute>"; };
+ F42E0B090D19A6FB00D5DDE0 /* GTMNSWorkspace+ThemeTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSWorkspace+ThemeTest.m"; sourceTree = "<group>"; };
+ F43DCDCB0D4796C600959A62 /* GTMLoginItems.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMLoginItems.h; sourceTree = "<group>"; };
+ F43DCDCC0D4796C600959A62 /* GTMLoginItems.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMLoginItems.m; sourceTree = "<group>"; };
+ F43DCEC60D47BEA000959A62 /* GTMLoginItemsTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMLoginItemsTest.m; sourceTree = "<group>"; };
+ F43E44770D4918B20041161F /* GTMLinearRGBShading.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMLinearRGBShading.h; sourceTree = "<group>"; };
+ F43E44780D4918B20041161F /* GTMLinearRGBShading.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMLinearRGBShading.m; sourceTree = "<group>"; };
+ F43E44790D4918B20041161F /* GTMLinearRGBShadingTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMLinearRGBShadingTest.m; sourceTree = "<group>"; };
+ F43E4C250D4E361D0041161F /* GTMNSString+XML.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSString+XML.h"; sourceTree = "<group>"; };
+ F43E4C260D4E361D0041161F /* GTMNSString+XML.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSString+XML.m"; sourceTree = "<group>"; };
+ F43E4C270D4E361D0041161F /* GTMNSString+XMLTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSString+XMLTest.m"; sourceTree = "<group>"; };
+ F43E4DD60D4E56320041161F /* GTMNSEnumerator+Filter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSEnumerator+Filter.h"; sourceTree = "<group>"; };
+ F43E4DD70D4E56320041161F /* GTMNSEnumerator+Filter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSEnumerator+Filter.m"; sourceTree = "<group>"; };
+ F43E4DD80D4E56320041161F /* GTMNSEnumerator+FilterTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSEnumerator+FilterTest.m"; sourceTree = "<group>"; };
+ F43E4E5E0D4E5EC90041161F /* GTMNSData+zlib.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSData+zlib.h"; sourceTree = "<group>"; };
+ F43E4E5F0D4E5EC90041161F /* GTMNSData+zlib.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSData+zlib.m"; sourceTree = "<group>"; };
+ F43E4E600D4E5EC90041161F /* GTMNSData+zlibTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSData+zlibTest.m"; sourceTree = "<group>"; };
+ F43E4F6C0D4E60C50041161F /* libz.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.dylib; path = /usr/lib/libz.dylib; sourceTree = "<absolute>"; };
+ F47F1C0D0D490BC000925B8F /* GTMNSBezierPath+Shading.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSBezierPath+Shading.h"; sourceTree = "<group>"; };
+ F47F1C0E0D490BC000925B8F /* GTMNSBezierPath+Shading.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSBezierPath+Shading.m"; sourceTree = "<group>"; };
+ F47F1C0F0D490BC000925B8F /* GTMNSBezierPath+ShadingTest.10.4.tif */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = "GTMNSBezierPath+ShadingTest.10.4.tif"; sourceTree = "<group>"; };
+ F47F1C100D490BC000925B8F /* GTMNSBezierPath+ShadingTest.10.5.tif */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = "GTMNSBezierPath+ShadingTest.10.5.tif"; sourceTree = "<group>"; };
+ F47F1C110D490BC000925B8F /* GTMNSBezierPath+ShadingTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSBezierPath+ShadingTest.m"; sourceTree = "<group>"; };
+ F47F1C740D490E5C00925B8F /* GTMShading.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMShading.h; sourceTree = "<group>"; };
+ F47F1CAC0D4910FD00925B8F /* GTMNSColor+Theme.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSColor+Theme.h"; sourceTree = "<group>"; };
+ F47F1CAD0D4910FD00925B8F /* GTMNSColor+Theme.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSColor+Theme.m"; sourceTree = "<group>"; };
+ F47F1CAE0D4910FD00925B8F /* GTMNSColor+ThemeTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSColor+ThemeTest.m"; sourceTree = "<group>"; };
+ F47F1D2D0D4914AD00925B8F /* GTMCalculatedRange.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMCalculatedRange.h; sourceTree = "<group>"; };
+ F47F1D2E0D4914AD00925B8F /* GTMCalculatedRange.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMCalculatedRange.m; sourceTree = "<group>"; };
+ F47F1D2F0D4914AD00925B8F /* GTMCalculatedRangeTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMCalculatedRangeTest.m; sourceTree = "<group>"; };
+ F48FE2410D197F9A009257D2 /* DebugTigerOrLater.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = DebugTigerOrLater.xcconfig; sourceTree = "<group>"; };
+ F48FE2420D197F9A009257D2 /* DebugUnittest.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = DebugUnittest.xcconfig; sourceTree = "<group>"; };
+ F48FE2430D197F9A009257D2 /* LoadableBundle.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = LoadableBundle.xcconfig; sourceTree = "<group>"; };
+ F48FE2440D197F9A009257D2 /* ReleaseTigerOrLater.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = ReleaseTigerOrLater.xcconfig; sourceTree = "<group>"; };
+ F48FE2450D197F9A009257D2 /* ReleaseUnittest.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = ReleaseUnittest.xcconfig; sourceTree = "<group>"; };
+ F48FE2460D197F9A009257D2 /* SharedLibrary.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = SharedLibrary.xcconfig; sourceTree = "<group>"; };
+ F48FE2470D197F9A009257D2 /* StaticLibrary.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = StaticLibrary.xcconfig; sourceTree = "<group>"; };
+ 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 = "<group>"; };
+ F48FE27C0D198D0E009257D2 /* GTMDelegatingTableColumn.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMDelegatingTableColumn.h; sourceTree = "<group>"; };
+ F48FE27D0D198D0E009257D2 /* GTMDelegatingTableColumn.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMDelegatingTableColumn.m; sourceTree = "<group>"; };
+ F48FE27E0D198D0E009257D2 /* GTMGeometryUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMGeometryUtils.h; sourceTree = "<group>"; };
+ F48FE27F0D198D0E009257D2 /* GTMGeometryUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMGeometryUtils.m; sourceTree = "<group>"; };
+ F48FE2800D198D0E009257D2 /* GTMGeometryUtilsTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMGeometryUtilsTest.m; sourceTree = "<group>"; };
+ F48FE2810D198D0E009257D2 /* GTMNSBezierPath+RoundRect.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSBezierPath+RoundRect.h"; sourceTree = "<group>"; };
+ F48FE2820D198D0E009257D2 /* GTMNSBezierPath+RoundRect.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSBezierPath+RoundRect.m"; sourceTree = "<group>"; };
+ F48FE2830D198D0E009257D2 /* GTMNSBezierPath+RoundRectTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSBezierPath+RoundRectTest.m"; sourceTree = "<group>"; };
+ F48FE2840D198D0E009257D2 /* GTMNSBezierPath+RoundRectTest.tif */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = "GTMNSBezierPath+RoundRectTest.tif"; sourceTree = "<group>"; };
+ F48FE28D0D198D24009257D2 /* GTMGarbageCollection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMGarbageCollection.h; sourceTree = "<group>"; };
+ F48FE28E0D198D24009257D2 /* GTMNSString+HTML.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSString+HTML.h"; sourceTree = "<group>"; };
+ F48FE28F0D198D24009257D2 /* GTMNSString+HTML.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSString+HTML.m"; sourceTree = "<group>"; };
+ F48FE2900D198D24009257D2 /* GTMNSString+HTMLTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSString+HTMLTest.m"; sourceTree = "<group>"; };
+ F48FE2910D198D24009257D2 /* GTMObjectSingleton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMObjectSingleton.h; sourceTree = "<group>"; };
+ F48FE2920D198D24009257D2 /* GTMSystemVersion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMSystemVersion.h; sourceTree = "<group>"; };
+ F48FE2930D198D24009257D2 /* GTMSystemVersion.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMSystemVersion.m; sourceTree = "<group>"; };
+ F48FE29B0D198D36009257D2 /* GTMNSObject+UnitTesting.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSObject+UnitTesting.h"; sourceTree = "<group>"; };
+ F48FE29C0D198D36009257D2 /* GTMNSObject+UnitTesting.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSObject+UnitTesting.m"; sourceTree = "<group>"; };
+ F48FE29D0D198D36009257D2 /* GTMNSView+UnitTesting.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSView+UnitTesting.h"; sourceTree = "<group>"; };
+ F48FE29E0D198D36009257D2 /* GTMNSView+UnitTesting.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSView+UnitTesting.m"; sourceTree = "<group>"; };
+ F48FE29F0D198D36009257D2 /* GTMSenTestCase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMSenTestCase.h; sourceTree = "<group>"; };
+ F48FE2E10D198E4C009257D2 /* GTMSystemVersionTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMSystemVersionTest.m; sourceTree = "<group>"; };
+/* 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 = "<group>";
+ };
+ 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 = "<group>";
+ };
+ 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 = "<group>";
+ };
+ 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 = "<group>";
+ };
+ 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 = "<group>";
+ };
+ 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 = "<group>";
+ };
+ 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 = "<group>";
+ };
+/* 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 <Cocoa/Cocoa.h>
+#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 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>English</string>
+ <key>CFBundleExecutable</key>
+ <string>${EXECUTABLE_NAME}</string>
+ <key>CFBundleIdentifier</key>
+ <string>com.google.${PRODUCT_NAME}</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundlePackageType</key>
+ <string>BNDL</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>CFBundleVersion</key>
+ <string>1.0</string>
+</dict>
+</plist>
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 <Cocoa/Cocoa.h>
+
+/// 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) <GTMUnitTestingEncoding>
+/// 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 <Carbon/Carbon.h>
+#include <mach-o/arch.h>
+
+#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<GTMUnitTestingEncoding> encoder = (id<GTMUnitTestingEncoding>)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 <Cocoa/Cocoa.h>
+#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<GTMUnitTestViewDrawer> 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) <GTMUnitTestingEncoding>
+
+/// 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<GTMUnitTestViewDrawer> 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<GTMUnitTestViewDrawer>)drawer contextInfo:(void*)contextInfo;
+@end
+
+/// \cond Protocols
+
+// Formal protocol for doing unit testing of views. See description of
+// GTMUnitTestView for details.
+@protocol GTMUnitTestViewDrawer <NSObject>
+
+// 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 <SenTestingKit/SenTestingKit.h>
+#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<GTMUnitTestViewDrawer>)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 <SenTestingKit/SenTestingKit.h>
+
+// 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