aboutsummaryrefslogtreecommitdiff
path: root/AppKit
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 /AppKit
initial drop of a few sources to start things out
Diffstat (limited to 'AppKit')
-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
31 files changed, 3317 insertions, 0 deletions
diff --git a/AppKit/GTMDelegatingTableColumn.h b/AppKit/GTMDelegatingTableColumn.h
new file mode 100644
index 0000000..a90b298
--- /dev/null
+++ b/AppKit/GTMDelegatingTableColumn.h
@@ -0,0 +1,32 @@
+//
+// GTMDelegatingTableColumn.h
+//
+// Copyright 2006-2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+
+#import <Cocoa/Cocoa.h>
+
+@interface GTMDelegatingTableColumn : NSTableColumn {
+ @private
+ IBOutlet id delegate_;
+}
+- (void)setDelegate:(id)delegate;
+- (id)delegate;
+- (id)dataCellForRow:(int)row;
+@end
+
+@interface NSObject (GTMDelegatingTableColumnDelegate)
+- (id)tableColumn:(NSTableColumn*)column dataCellForRow:(int)row;
+@end
diff --git a/AppKit/GTMDelegatingTableColumn.m b/AppKit/GTMDelegatingTableColumn.m
new file mode 100644
index 0000000..d84e2e7
--- /dev/null
+++ b/AppKit/GTMDelegatingTableColumn.m
@@ -0,0 +1,39 @@
+//
+// GTMDelegatingTableColumn.m
+//
+// Copyright 2006-2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+
+#import "GTMDelegatingTableColumn.h"
+
+@implementation GTMDelegatingTableColumn
+- (void)setDelegate:(id)delegate {
+ delegate_ = delegate;
+}
+
+- (id)delegate {
+ return delegate_;
+}
+
+- (id)dataCellForRow:(int)row {
+ id dataCell = nil;
+ if (delegate_ && [delegate_ respondsToSelector:@selector(tableColumn:dataCellForRow:)]) {
+ dataCell = [delegate_ tableColumn:self dataCellForRow:row];
+ } else {
+ dataCell = [super dataCellForRow:row];
+ }
+ return dataCell;
+}
+@end
diff --git a/AppKit/GTMGeometryUtils.h b/AppKit/GTMGeometryUtils.h
new file mode 100644
index 0000000..1dd274f
--- /dev/null
+++ b/AppKit/GTMGeometryUtils.h
@@ -0,0 +1,560 @@
+//
+// GTMGeometryUtils.h
+//
+// Utilities for geometrical utilities such as conversions
+// between different types.
+//
+// Copyright 2006-2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+
+#include <Carbon/Carbon.h>
+#include <Cocoa/Cocoa.h>
+
+
+#pragma mark Miscellaneous
+
+/// Calculate the distance between two points.
+//
+// Args:
+// pt1 first point
+// pt2 second point
+//
+// Returns:
+// Distance
+CG_INLINE float GTMDistanceBetweenPoints(NSPoint pt1, NSPoint pt2) {
+ float dX = pt1.x - pt2.x;
+ float dY = pt1.y - pt2.y;
+ return sqrtf(dX * dX + dY * dY);
+}
+
+/// Returns the height of the main display (the one with the menu bar).
+//
+/// The value updates itself automatically whenever the user
+/// repositions their monitors, or changes resolutions etc.
+//
+// Returns:
+// height of the main display area.
+float GTMGetMainDisplayHeight(void);
+
+#pragma mark -
+#pragma mark Point Conversion
+
+/// Quickly convert from a global HIPoint to a global NSPoint.
+//
+/// HIPoints are relative to 0,0 in upper left;
+/// NSPoints are relative to 0,0 in lower left
+//
+// Args:
+// inPoint: HIPoint to convert
+//
+// Returns:
+// Converted NSPoint
+CG_INLINE NSPoint GTMGlobalHIPointToNSPoint(HIPoint inPoint) {
+ return NSMakePoint(inPoint.x, GTMGetMainDisplayHeight() - inPoint.y);
+}
+
+/// Quickly convert from a global NSPoint to a global HIPoint.
+//
+/// HIPoints are relative to 0,0 in upper left;
+/// NSPoints are relative to 0,0 in lower left
+//
+// Args:
+// inPoint: NSPoint to convert
+//
+// Returns:
+// Converted HIPoint
+CG_INLINE HIPoint GTMGlobalNSPointToHIPoint(NSPoint inPoint) {
+ return CGPointMake(inPoint.x, GTMGetMainDisplayHeight() - inPoint.y);
+}
+
+/// Quickly convert from a CGPoint to a NSPoint.
+//
+/// CGPoints are relative to 0,0 in lower left;
+/// NSPoints are relative to 0,0 in lower left
+//
+// Args:
+// inPoint: CGPoint to convert
+//
+// Returns:
+// Converted NSPoint
+CG_INLINE NSPoint GTMCGPointToNSPoint(CGPoint inPoint) {
+ return NSMakePoint(inPoint.x, inPoint.y);
+}
+
+/// Quickly convert from a NSPoint to a CGPoint.
+//
+/// CGPoints are relative to 0,0 in lower left;
+/// NSPoints are relative to 0,0 in lower left
+//
+// Args:
+// inPoint: NSPoint to convert
+//
+// Returns:
+// Converted CGPoint
+CG_INLINE CGPoint GTMNSPointToCGPoint(NSPoint inPoint) {
+ return CGPointMake(inPoint.x, inPoint.y);
+}
+
+/// Quickly convert from a global HIPoint to a global CGPoint.
+//
+/// HIPoints are relative to 0,0 in upper left;
+/// CGPoints are relative to 0,0 in lower left
+//
+// Args:
+// inPoint: NSPoint to convert
+//
+// Returns:
+// Converted CGPoint
+CG_INLINE HIPoint GTMGlobalCGPointToHIPoint(CGPoint inPoint) {
+ return GTMGlobalNSPointToHIPoint(GTMCGPointToNSPoint(inPoint));
+}
+
+/// Quickly convert from a global CGPoint to a global HIPoint.
+//
+/// HIPoints are relative to 0,0 in upper left;
+/// CGPoints are relative to 0,0 in lower left
+//
+// Args:
+// inPoint: CGPoint to convert
+//
+// Returns:
+// Converted NSPoint
+CG_INLINE CGPoint GTMGlobalHIPointToCGPoint(HIPoint inPoint) {
+ return GTMNSPointToCGPoint(GTMGlobalHIPointToNSPoint(inPoint));
+}
+
+#pragma mark -
+#pragma mark Rect Conversion
+
+/// Convert from a global NSRect to a global HIRect.
+//
+/// HIRect are relative to 0,0 in upper left;
+/// NSRect are relative to 0,0 in lower left
+//
+// Args:
+// inRect: NSRect to convert
+//
+// Returns:
+// Converted HIRect
+HIRect GTMGlobalNSRectToHIRect(NSRect inRect);
+
+/// Convert from a rect to a HIRect.
+//
+/// HIRect are relative to 0,0 in upper left;
+/// Rect are relative to 0,0 in upper left
+//
+// Args:
+// inRect: Rect to convert
+//
+// Returns:
+// Converted HIRect
+HIRect GTMRectToHIRect(Rect inRect);
+
+/// Convert from a global HIRect to a global NSRect.
+//
+/// NSRect are relative to 0,0 in lower left;
+/// HIRect are relative to 0,0 in upper left
+//
+// Args:
+// inRect: HIRect to convert
+//
+// Returns:
+// Converted NSRect
+NSRect GTMGlobalHIRectToNSRect(HIRect inRect);
+
+
+/// Convert from a HIRect to a Rect.
+//
+/// Rect are relative to 0,0 in upper left;
+/// HIRect are relative to 0,0 in upper left
+//
+// Args:
+// inRect: HIRect to convert
+//
+// Returns:
+// Converted Rect
+Rect GTMHIRectToRect(HIRect inRect);
+
+/// Convert from a global Rect to a global NSRect.
+//
+/// NSRect are relative to 0,0 in lower left;
+/// Rect are relative to 0,0 in upper left
+//
+// Args:
+// inRect: Rect to convert
+//
+// Returns:
+// Converted NSRect
+CG_INLINE NSRect GTMGlobalRectToNSRect(Rect inRect) {
+ return GTMGlobalHIRectToNSRect(GTMRectToHIRect(inRect));
+}
+
+/// Convert from a CGRect to a NSRect.
+//
+/// NSRect are relative to 0,0 in lower left;
+/// CGRect are relative to 0,0 in lower left
+//
+// Args:
+// inRect: CGRect to convert
+//
+// Returns:
+// Converted NSRect
+CG_INLINE NSRect GTMCGRectToNSRect(CGRect inRect) {
+ return NSMakeRect(inRect.origin.x,inRect.origin.y,inRect.size.width,inRect.size.height);
+}
+
+/// Convert from a NSRect to a CGRect.
+//
+/// NSRect are relative to 0,0 in lower left;
+/// CGRect are relative to 0,0 in lower left
+//
+// Args:
+// inRect: NSRect to convert
+//
+// Returns:
+// Converted CGRect
+CG_INLINE CGRect GTMNSRectToCGRect(NSRect inRect) {
+ return CGRectMake(inRect.origin.x,inRect.origin.y,inRect.size.width,inRect.size.height);
+}
+
+/// Convert from a global HIRect to a global CGRect.
+//
+/// HIRect are relative to 0,0 in upper left;
+/// CGRect are relative to 0,0 in lower left
+//
+// Args:
+// inRect: HIRect to convert
+//
+// Returns:
+// Converted CGRect
+CG_INLINE CGRect GTMGlobalHIRectToCGRect(HIRect inRect) {
+ return GTMNSRectToCGRect(GTMGlobalHIRectToNSRect(inRect));
+}
+
+/// Convert from a global Rect to a global CGRect.
+//
+/// Rect are relative to 0,0 in upper left;
+/// CGRect are relative to 0,0 in lower left
+//
+// Args:
+// inRect: Rect to convert
+//
+// Returns:
+// Converted CGRect
+CG_INLINE CGRect GTMGlobalRectToCGRect(Rect inRect) {
+ return GTMNSRectToCGRect(GTMGlobalRectToNSRect(inRect));
+}
+
+/// Convert from a global NSRect to a global Rect.
+//
+/// Rect are relative to 0,0 in upper left;
+/// NSRect are relative to 0,0 in lower left
+//
+// Args:
+// inRect: NSRect to convert
+//
+// Returns:
+// Converted Rect
+CG_INLINE Rect GTMGlobalNSRectToRect(NSRect inRect) {
+ return GTMHIRectToRect(GTMGlobalNSRectToHIRect(inRect));
+}
+
+/// Convert from a global CGRect to a global HIRect.
+//
+/// HIRect are relative to 0,0 in upper left;
+/// CGRect are relative to 0,0 in lower left
+//
+// Args:
+// inRect: CGRect to convert
+//
+// Returns:
+// Converted HIRect
+CG_INLINE HIRect GTMGlobalCGRectToHIRect(CGRect inRect) {
+ return GTMGlobalNSRectToHIRect(GTMCGRectToNSRect(inRect));
+}
+
+/// Convert from a global CGRect to a global Rect.
+//
+/// Rect are relative to 0,0 in upper left;
+/// CGRect are relative to 0,0 in lower left
+//
+// Args:
+// inRect: CGRect to convert
+//
+// Returns:
+// Converted Rect
+CG_INLINE Rect GTMGlobalCGRectToRect(CGRect inRect) {
+ return GTMHIRectToRect(GTMGlobalCGRectToHIRect(inRect));
+}
+
+#pragma mark -
+#pragma mark Size Conversion
+
+/// Convert from a CGSize to an NSSize.
+//
+// Args:
+// inSize: CGSize to convert
+//
+// Returns:
+// Converted NSSize
+CG_INLINE NSSize GTMCGSizeToNSSize(CGSize inSize) {
+ return NSMakeSize(inSize.width, inSize.height);
+}
+
+/// Convert from a NSSize to a CGSize.
+//
+// Args:
+// inSize: NSSize to convert
+//
+// Returns:
+// Converted CGSize
+CG_INLINE CGSize GTMNSSizeToCGSize(NSSize inSize) {
+ return CGSizeMake(inSize.width, inSize.height);
+}
+
+#pragma mark -
+#pragma mark Point On Rect
+
+/// Return middle of left side of rectangle
+//
+// Args:
+// rect - rectangle
+//
+// Returns:
+// point located in the middle of left side of rect
+CG_INLINE NSPoint GTMNSMidLeft(NSRect rect) {
+ return NSMakePoint(NSMinX(rect), NSMidY(rect));
+}
+
+/// Return middle of right side of rectangle
+//
+// Args:
+// rect - rectangle
+//
+// Returns:
+// point located in the middle of right side of rect
+CG_INLINE NSPoint GTMNSMidRight(NSRect rect) {
+ return NSMakePoint(NSMaxX(rect), NSMidY(rect));
+}
+
+/// Return middle of top side of rectangle
+//
+// Args:
+// rect - rectangle
+//
+// Returns:
+// point located in the middle of top side of rect
+CG_INLINE NSPoint GTMNSMidTop(NSRect rect) {
+ return NSMakePoint(NSMidX(rect), NSMaxY(rect));
+}
+
+/// Return middle of bottom side of rectangle
+//
+// Args:
+// rect - rectangle
+//
+// Returns:
+// point located in the middle of bottom side of rect
+CG_INLINE NSPoint GTMNSMidBottom(NSRect rect) {
+ return NSMakePoint(NSMidX(rect), NSMinY(rect));
+}
+
+/// Return center of rectangle
+//
+// Args:
+// rect - rectangle
+//
+// Returns:
+// point located in the center of rect
+CG_INLINE NSPoint GTMNSCenter(NSRect rect) {
+ return NSMakePoint(NSMidX(rect), NSMidY(rect));
+}
+
+/// Return middle of left side of rectangle
+//
+// Args:
+// rect - rectangle
+//
+// Returns:
+// point located in the middle of left side of rect
+CG_INLINE CGPoint GTMCGMidLeft(CGRect rect) {
+ return CGPointMake(CGRectGetMinX(rect), CGRectGetMidY(rect));
+}
+
+/// Return middle of right side of rectangle
+//
+// Args:
+// rect - rectangle
+//
+// Returns:
+// point located in the middle of right side of rect
+CG_INLINE CGPoint GTMCGMidRight(CGRect rect) {
+ return CGPointMake(CGRectGetMaxX(rect), CGRectGetMidY(rect));
+}
+
+/// Return middle of top side of rectangle
+//
+// Args:
+// rect - rectangle
+//
+// Returns:
+// point located in the middle of top side of rect
+CG_INLINE CGPoint GTMCGMidTop(CGRect rect) {
+ return CGPointMake(CGRectGetMidX(rect), CGRectGetMaxY(rect));
+}
+
+/// Return middle of bottom side of rectangle
+//
+// Args:
+// rect - rectangle
+//
+// Returns:
+// point located in the middle of bottom side of rect
+CG_INLINE CGPoint GTMCGMidBottom(CGRect rect) {
+ return CGPointMake(CGRectGetMidX(rect), CGRectGetMinY(rect));
+}
+
+/// Return center of rectangle
+//
+// Args:
+// rect - rectangle
+//
+// Returns:
+// point located in the center of rect
+CG_INLINE CGPoint GTMCGCenter(CGRect rect) {
+ return CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect));
+}
+
+#pragma mark -
+#pragma mark Rect-Size Conversion
+
+/// Return size of rectangle
+//
+// Args:
+// rect - rectangle
+//
+// Returns:
+// size of rectangle
+CG_INLINE NSSize GTMNSRectSize(NSRect rect) {
+ return NSMakeSize(NSWidth(rect), NSHeight(rect));
+}
+
+/// Return size of rectangle
+//
+// Args:
+// rect - rectangle
+//
+// Returns:
+// size of rectangle
+CG_INLINE CGSize GTMCGRectSize(CGRect rect) {
+ return CGSizeMake(CGRectGetWidth(rect), CGRectGetHeight(rect));
+}
+
+/// Return rectangle of size
+//
+// Args:
+// size - size
+//
+// Returns:
+// rectangle of size (origin 0,0)
+CG_INLINE NSRect GTMNSRectOfSize(NSSize size) {
+ return NSMakeRect(0.0f, 0.0f, size.width, size.height);
+}
+
+/// Return rectangle of size
+//
+// Args:
+// size - size
+//
+// Returns:
+// rectangle of size (origin 0,0)
+CG_INLINE CGRect GTMCGRectOfSize(CGSize size) {
+ return CGRectMake(0.0f, 0.0f, size.width, size.height);
+}
+
+#pragma mark -
+#pragma mark Rect Scaling and Alignment
+
+/// Scales an NSRect
+//
+// Args:
+// inRect: Rect to scale
+// xScale: fraction to scale (1.0 is 100%)
+// yScale: fraction to scale (1.0 is 100%)
+//
+// Returns:
+// Converted Rect
+CG_INLINE NSRect GTMNSRectScale(NSRect inRect, float xScale, float yScale) {
+ return NSMakeRect(inRect.origin.x, inRect.origin.y,
+ inRect.size.width * xScale, inRect.size.height * yScale);
+}
+
+/// Scales an CGRect
+//
+// Args:
+// inRect: Rect to scale
+// xScale: fraction to scale (1.0 is 100%)
+// yScale: fraction to scale (1.0 is 100%)
+//
+// Returns:
+// Converted Rect
+CG_INLINE CGRect GTMCGRectScale(CGRect inRect, float xScale, float yScale) {
+ return CGRectMake(inRect.origin.x, inRect.origin.y,
+ inRect.size.width * xScale, inRect.size.height * yScale);
+}
+
+/// Align rectangles
+//
+// Args:
+// alignee - rect to be aligned
+// aligner - rect to be aligned from
+NSRect GTMAlignRectangles(NSRect alignee, NSRect aligner,
+ NSImageAlignment alignment);
+
+/// Align rectangles
+//
+// Args:
+// alignee - rect to be aligned
+// aligner - rect to be aligned from
+// alignment - way to align the rectangles
+CG_INLINE CGRect GTMCGAlignRectangles(CGRect alignee, CGRect aligner,
+ NSImageAlignment alignment) {
+ return GTMNSRectToCGRect(GTMAlignRectangles(GTMCGRectToNSRect(alignee),
+ GTMCGRectToNSRect(aligner),
+ alignment));
+}
+
+/// Scale rectangle
+//
+// Args:
+// scalee - rect to be scaled
+// size - size to scale to
+// scaling - way to scale the rectangle
+NSRect GTMScaleRectangleToSize(NSRect scalee, NSSize size,
+ NSImageScaling scaling);
+
+/// Scale rectangle
+//
+// Args:
+// scalee - rect to be scaled
+// size - size to scale to
+// scaling - way to scale the rectangle
+CG_INLINE CGRect GTMCGScaleRectangleToSize(CGRect scalee, CGSize size,
+ NSImageScaling scaling) {
+ return GTMNSRectToCGRect(GTMScaleRectangleToSize(GTMCGRectToNSRect(scalee),
+ GTMCGSizeToNSSize(size),
+ scaling));
+}
+
diff --git a/AppKit/GTMGeometryUtils.m b/AppKit/GTMGeometryUtils.m
new file mode 100644
index 0000000..efc543e
--- /dev/null
+++ b/AppKit/GTMGeometryUtils.m
@@ -0,0 +1,152 @@
+//
+// GTMGeometryUtils.m
+//
+// Copyright 2006-2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+
+#import "GTMGeometryUtils.h"
+
+float GTMGetMainDisplayHeight(void) {
+ float height = 0;
+ NSArray *screens = [NSScreen screens];
+ // We may have a headless machine without any screens. In this case we
+ // return 0.
+ if ([screens count] > 0) {
+ height = NSHeight([(NSScreen*)[screens objectAtIndex: 0] frame]);
+ }
+ return height;
+}
+
+
+// Rect conversion routines.
+HIRect GTMGlobalNSRectToHIRect(NSRect inRect) {
+ HIRect theRect;
+ theRect.origin = GTMGlobalNSPointToHIPoint(inRect.origin);
+ theRect.origin.y -= inRect.size.height;
+ theRect.size = CGSizeMake(inRect.size.width, inRect.size.height);
+ return theRect;
+}
+
+
+HIRect GTMRectToHIRect(Rect inRect) {
+ HIRect theRect;
+ theRect.origin = CGPointMake(inRect.left,inRect.top);
+ theRect.size = CGSizeMake(inRect.right - inRect.left, inRect.bottom - inRect.top);
+ return theRect;
+}
+
+
+NSRect GTMGlobalHIRectToNSRect(HIRect inRect) {
+ NSRect theRect;
+ theRect.origin = GTMGlobalHIPointToNSPoint(inRect.origin);
+ theRect.origin.y -= inRect.size.height;
+ theRect.size = NSMakeSize(inRect.size.width, inRect.size.height);
+ return theRect;
+}
+
+
+Rect GTMHIRectToRect(HIRect inRect) {
+ Rect theRect;
+ theRect.left = inRect.origin.x;
+ theRect.right = ceilf(inRect.origin.x + inRect.size.width);
+ theRect.top = inRect.origin.y;
+ theRect.bottom = ceilf(inRect.origin.y + inRect.size.height);
+ return theRect;
+}
+
+/// Align rectangles
+//
+// Args:
+// alignee - rect to be aligned
+// aligner - rect to be aligned to
+// alignment - alignment to be applied to alignee based on aligner
+
+NSRect GTMAlignRectangles(NSRect alignee, NSRect aligner, NSImageAlignment alignment) {
+ switch (alignment) {
+ case NSImageAlignTop:
+ alignee.origin.x = aligner.origin.x + (NSWidth(aligner) * .5f - NSWidth(alignee) * .5f);
+ alignee.origin.y = aligner.origin.y + NSHeight(aligner) - NSHeight(alignee);
+ break;
+
+ case NSImageAlignTopLeft:
+ alignee.origin.x = aligner.origin.x;
+ alignee.origin.y = aligner.origin.y + NSHeight(aligner) - NSHeight(alignee);
+ break;
+
+ case NSImageAlignTopRight:
+ alignee.origin.x = aligner.origin.x + NSWidth(aligner) - NSWidth(alignee);
+ alignee.origin.y = aligner.origin.y + NSHeight(aligner) - NSHeight(alignee);
+ break;
+
+ case NSImageAlignLeft:
+ alignee.origin.x = aligner.origin.x;
+ alignee.origin.y = aligner.origin.y + (NSHeight(aligner) * .5f - NSHeight(alignee) * .5f);
+ break;
+
+ case NSImageAlignBottomLeft:
+ alignee.origin.x = aligner.origin.x;
+ alignee.origin.y = aligner.origin.y;
+ break;
+
+ case NSImageAlignBottom:
+ alignee.origin.x = aligner.origin.x + (NSWidth(aligner) * .5f - NSWidth(alignee) * .5f);
+ alignee.origin.y = aligner.origin.y;
+ break;
+
+ case NSImageAlignBottomRight:
+ alignee.origin.x = aligner.origin.x + NSWidth(aligner) - NSWidth(alignee);
+ alignee.origin.y = aligner.origin.y;
+ break;
+
+ case NSImageAlignRight:
+ alignee.origin.x = aligner.origin.x + NSWidth(aligner) - NSWidth(alignee);
+ alignee.origin.y = aligner.origin.y + (NSHeight(aligner) * .5f - NSHeight(alignee) * .5f);
+ break;
+
+ default:
+ case NSImageAlignCenter:
+ alignee.origin.x = aligner.origin.x + (NSWidth(aligner) * .5f - NSWidth(alignee) * .5f);
+ alignee.origin.y = aligner.origin.y + (NSHeight(aligner) * .5f - NSHeight(alignee) * .5f);
+ break;
+ }
+ return alignee;
+}
+
+NSRect GTMScaleRectangleToSize(NSRect scalee, NSSize size, NSImageScaling scaling) {
+ switch (scaling) {
+ case NSScaleProportionally: {
+ float height = NSHeight(scalee);
+ float width = NSWidth(scalee);
+ if (isnormal(height) && isnormal(width) &&
+ (height > size.height || width > size.width)) {
+ float horiz = size.width / width;
+ float vert = size.height / height;
+ float newScale = horiz < vert ? horiz : vert;
+ scalee = GTMNSRectScale(scalee, newScale, newScale);
+ }
+ break;
+ }
+
+ case NSScaleToFit:
+ scalee.size = size;
+ break;
+
+ case NSScaleNone:
+ default:
+ // Do nothing
+ break;
+ }
+ return scalee;
+}
diff --git a/AppKit/GTMGeometryUtilsTest.m b/AppKit/GTMGeometryUtilsTest.m
new file mode 100644
index 0000000..0fad449
--- /dev/null
+++ b/AppKit/GTMGeometryUtilsTest.m
@@ -0,0 +1,323 @@
+//
+// GTMGeometryUtilsTest.m
+//
+// Copyright 2006-2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+
+#import <SenTestingKit/SenTestingKit.h>
+#import "GTMGeometryUtils.h"
+
+@interface GTMGeometryUtilsTest : SenTestCase
+@end
+
+@implementation GTMGeometryUtilsTest
+
+- (void)testGTMGetMainDisplayHeight {
+ STAssertTrue(CGRectGetHeight(CGDisplayBounds(CGMainDisplayID())) == GTMGetMainDisplayHeight(), nil);
+}
+
+
+- (void)testGTMGlobalHIPointToNSPoint {
+ HIPoint hiPoint = CGPointMake(12.5,14.5);
+ NSPoint nsPoint = GTMGlobalHIPointToNSPoint(hiPoint);
+ STAssertTrue(nsPoint.x == hiPoint.x &&
+ nsPoint.y == GTMGetMainDisplayHeight() - hiPoint.y, nil);
+}
+
+
+- (void)testGTMGlobalNSPointToHIPoint {
+ NSPoint nsPoint = NSMakePoint(12.5,14.5);
+ HIPoint hiPoint = GTMGlobalNSPointToHIPoint(nsPoint);
+ STAssertTrue(nsPoint.x == hiPoint.x &&
+ nsPoint.y == GTMGetMainDisplayHeight() - hiPoint.y, nil);
+}
+
+
+- (void)testGTMGlobalCGPointToNSPoint {
+ CGPoint cgPoint = CGPointMake(15.1,6.2);
+ NSPoint nsPoint = GTMCGPointToNSPoint(cgPoint);
+ STAssertTrue(CGPointEqualToPoint(*(CGPoint*)&nsPoint, cgPoint), nil);
+
+}
+
+
+- (void)testGTMGlobalNSPointToCGPoint {
+ NSPoint nsPoint = NSMakePoint(10.2,1.5);
+ CGPoint cgPoint = GTMNSPointToCGPoint(nsPoint);
+ STAssertTrue(CGPointEqualToPoint(cgPoint, *(CGPoint*)&nsPoint), nil);
+}
+
+
+- (void)testGTMGlobalCGPointToHIPoint {
+ CGPoint cgPoint = CGPointMake(12.5,14.5);
+ HIPoint hiPoint = GTMGlobalCGPointToHIPoint(cgPoint);
+ STAssertTrue(cgPoint.x == hiPoint.x &&
+ cgPoint.y == GTMGetMainDisplayHeight() - hiPoint.y, nil);
+}
+
+
+- (void)testGTMGlobalHIPointToCGPoint {
+ HIPoint hiPoint = CGPointMake(12.5,14.5);
+ CGPoint cgPoint = GTMGlobalHIPointToCGPoint(hiPoint);
+ STAssertTrue(cgPoint.x == hiPoint.x &&
+ cgPoint.y == GTMGetMainDisplayHeight() - hiPoint.y, nil);
+}
+
+
+- (void)testGTMGlobalNSRectToHIRect {
+ NSRect nsRect = NSMakeRect(40,16,17,18);
+ HIRect hiRect = GTMGlobalNSRectToHIRect(nsRect);
+ STAssertTrue(nsRect.origin.x == hiRect.origin.x &&
+ nsRect.origin.y == GTMGetMainDisplayHeight() - hiRect.origin.y - hiRect.size.height &&
+ nsRect.size.height == hiRect.size.height &&
+ nsRect.size.width == hiRect.size.width, nil);
+}
+
+
+- (void)testGTMGlobalCGRectToHIRect {
+ CGRect cgRect = CGRectMake(40,16,17,19.0);
+ HIRect hiRect = GTMGlobalCGRectToHIRect(cgRect);
+ STAssertTrue(cgRect.origin.x == hiRect.origin.x &&
+ cgRect.origin.y == GTMGetMainDisplayHeight() - hiRect.origin.y - hiRect.size.height &&
+ cgRect.size.height == hiRect.size.height &&
+ cgRect.size.width == hiRect.size.width, nil);
+}
+
+
+- (void)testGTMGlobalHIRectToNSRect {
+ HIRect hiRect = CGRectMake(40.2,16.3,17.2,18.9);
+ NSRect nsRect = GTMGlobalHIRectToNSRect(hiRect);
+ STAssertTrue(nsRect.origin.x == hiRect.origin.x &&
+ nsRect.origin.y == GTMGetMainDisplayHeight() - hiRect.origin.y - hiRect.size.height &&
+ nsRect.size.height == hiRect.size.height &&
+ nsRect.size.width == hiRect.size.width, nil);
+}
+
+
+- (void)testGTMGlobalCGRectToNSRect {
+ CGRect cgRect = CGRectMake(1.5,2.4,10.6,11.7);
+ NSRect nsRect = GTMCGRectToNSRect(cgRect);
+ STAssertTrue(CGRectEqualToRect(cgRect, *(CGRect*)&nsRect), nil);
+}
+
+
+- (void)testGTMGlobalNSRectToCGRect {
+ NSRect nsRect = NSMakeRect(4.6,3.2,22.1,45.0);
+ CGRect cgRect = GTMNSRectToCGRect(nsRect);
+ STAssertTrue(CGRectEqualToRect(cgRect, *(CGRect*)&nsRect), nil);
+}
+
+
+- (void)testGTMGlobalHIRectToCGRect {
+ HIRect hiRect = CGRectMake(40.2,16.3,17.2,18.9);
+ CGRect cgRect = GTMGlobalHIRectToCGRect(hiRect);
+ STAssertTrue(cgRect.origin.x == hiRect.origin.x &&
+ cgRect.origin.y == GTMGetMainDisplayHeight() - hiRect.origin.y - hiRect.size.height &&
+ cgRect.size.height == hiRect.size.height &&
+ cgRect.size.width == hiRect.size.width, nil);
+}
+
+
+- (void)testGTMGlobalRectToNSRect {
+ Rect rect = { 10,50,40,60 };
+ NSRect nsRect = GTMGlobalRectToNSRect(rect);
+ HIRect hiRect1 = GTMRectToHIRect(rect);
+ HIRect hiRect2 = GTMGlobalNSRectToHIRect(nsRect);
+ STAssertTrue(CGRectEqualToRect(hiRect1,hiRect2), nil);
+}
+
+
+- (void)testGTMGlobalNSRectToRect {
+ NSRect nsRect = NSMakeRect(1.5,2.4,10.6,11.7);
+ HIRect hiRect = GTMGlobalNSRectToHIRect(nsRect);
+ Rect rect1 = GTMGlobalNSRectToRect(nsRect);
+ Rect rect2 = GTMHIRectToRect(hiRect);
+ STAssertTrue(rect1.left == rect2.left &&
+ rect1.right == rect2.right &&
+ rect1.top == rect2.top &&
+ rect1.bottom == rect2.bottom, nil);
+}
+
+
+- (void)testGTMGlobalRectToHIRect {
+ Rect rect = { 10,20,30,40 };
+ HIRect hiRect = GTMRectToHIRect(rect);
+ STAssertTrue(CGRectEqualToRect(hiRect, CGRectMake(20,10,20,20)), nil);
+}
+
+
+- (void)testGTMGlobalHIRectToRect {
+ HIRect hiRect = CGRectMake(1.5,2.4,10.6,11.7);
+ Rect rect = GTMHIRectToRect(hiRect);
+ STAssertTrue(rect.left == 1 &&
+ rect.right == 13 &&
+ rect.top == 2 &&
+ rect.bottom == 15, nil);
+}
+
+
+- (void)testGTMGlobalCGRectToRect {
+ CGRect cgRect = CGRectMake(1.5,2.4,10.6,11.7);
+ HIRect hiRect = GTMGlobalCGRectToHIRect(cgRect);
+ Rect rect1 = GTMGlobalCGRectToRect(cgRect);
+ Rect rect2 = GTMHIRectToRect(hiRect);
+ STAssertTrue(rect1.left == rect2.left &&
+ rect1.right == rect2.right &&
+ rect1.top == rect2.top &&
+ rect1.bottom == rect2.bottom, nil);
+}
+
+
+- (void)testGTMGlobalRectToCGRect {
+ Rect rect = { 10,50,40,60 };
+ CGRect nsRect = GTMGlobalRectToCGRect(rect);
+ HIRect hiRect1 = GTMRectToHIRect(rect);
+ HIRect hiRect2 = GTMGlobalCGRectToHIRect(nsRect);
+ STAssertTrue(CGRectEqualToRect(hiRect1,hiRect2), nil);
+}
+
+
+- (void)testGTMCGSizeToNSSize {
+ CGSize cgSize = {5,6};
+ NSSize nsSize = GTMCGSizeToNSSize(cgSize);
+ STAssertTrue(CGSizeEqualToSize(cgSize, *(CGSize*)&nsSize), nil);
+}
+
+
+- (void)testGTMNSSizeToCGSize {
+ NSSize nsSize = {22,15};
+ CGSize cgSize = GTMNSSizeToCGSize(nsSize);
+ STAssertTrue(CGSizeEqualToSize(cgSize, *(CGSize*)&nsSize), nil);
+}
+
+- (void)testGTMDistanceBetweenPoints {
+ NSPoint pt1 = NSMakePoint(0, 0);
+ NSPoint pt2 = NSMakePoint(3, 4);
+ STAssertEquals(GTMDistanceBetweenPoints(pt1, pt2), 5.0f, nil);
+ STAssertEquals(GTMDistanceBetweenPoints(pt2, pt1), 5.0f, nil);
+ pt1 = NSMakePoint(1, 1);
+ pt2 = NSMakePoint(1, 1);
+ STAssertEquals(GTMDistanceBetweenPoints(pt1, pt2), 0.0f, nil);
+}
+
+- (void)testGTMAlignRectangles {
+ typedef struct {
+ NSPoint expectedOrigin;
+ NSImageAlignment alignment;
+ } TestData;
+
+ TestData data[] = {
+ { {1,2}, NSImageAlignTop },
+ { {0,2}, NSImageAlignTopLeft },
+ { {2,2}, NSImageAlignTopRight },
+ { {0,1}, NSImageAlignLeft },
+ { {1,0}, NSImageAlignBottom },
+ { {0,0}, NSImageAlignBottomLeft },
+ { {2,0}, NSImageAlignBottomRight },
+ { {2,1}, NSImageAlignRight },
+ { {1,1}, NSImageAlignCenter },
+ };
+
+ NSRect rect1 = NSMakeRect(0, 0, 4, 4);
+ NSRect rect2 = NSMakeRect(0, 0, 2, 2);
+
+ for (int i = 0; i < sizeof(data) / sizeof(TestData); i++) {
+ NSRect expectedRect;
+ expectedRect.origin = data[i].expectedOrigin;
+ expectedRect.size = NSMakeSize(2, 2);
+ NSRect outRect = GTMAlignRectangles(rect2, rect1, data[i].alignment);
+ STAssertEquals(outRect, expectedRect, nil);
+ }
+}
+
+- (void)testGTMPointsOnRect {
+ NSRect rect = NSMakeRect(0, 0, 2, 2);
+ CGRect cgRect = GTMNSRectToCGRect(rect);
+
+ NSPoint point = GTMNSMidLeft(rect);
+ CGPoint cgPoint = GTMCGMidLeft(cgRect);
+ STAssertEquals(point.x, cgPoint.x, nil);
+ STAssertEquals(point.y, cgPoint.y, nil);
+ STAssertEqualsWithAccuracy(point.y, 1.0f, 0.01f, nil);
+ STAssertEqualsWithAccuracy(point.x, 0.0f, 0.01f, nil);
+
+ point = GTMNSMidRight(rect);
+ cgPoint = GTMCGMidRight(cgRect);
+ STAssertEquals(point.x, cgPoint.x, nil);
+ STAssertEquals(point.y, cgPoint.y, nil);
+ STAssertEqualsWithAccuracy(point.y, 1.0f, 0.01f, nil);
+ STAssertEqualsWithAccuracy(point.x, 2.0f, 0.01f, nil);
+
+ point = GTMNSMidTop(rect);
+ cgPoint = GTMCGMidTop(cgRect);
+ STAssertEquals(point.x, cgPoint.x, nil);
+ STAssertEquals(point.y, cgPoint.y, nil);
+ STAssertEqualsWithAccuracy(point.y, 2.0f, 0.01f, nil);
+ STAssertEqualsWithAccuracy(point.x, 1.0f, 0.01f, nil);
+
+ point = GTMNSMidBottom(rect);
+ cgPoint = GTMCGMidBottom(cgRect);
+ STAssertEquals(point.x, cgPoint.x, nil);
+ STAssertEquals(point.y, cgPoint.y, nil);
+ STAssertEqualsWithAccuracy(point.y, 0.0f, 0.01f, nil);
+ STAssertEqualsWithAccuracy(point.x, 1.0f, 0.01f, nil);
+}
+
+- (void)testGTMRectScaling {
+ NSRect rect = NSMakeRect(1.0f, 2.0f, 5.0f, 10.0f);
+ NSRect rect2 = NSMakeRect(1.0f, 2.0f, 1.0f, 12.0f);
+ STAssertEquals(GTMNSRectScale(rect, 0.2f, 1.2f),
+ rect2, nil);
+ STAssertEquals(GTMCGRectScale(GTMNSRectToCGRect(rect), 0.2f, 1.2f),
+ GTMNSRectToCGRect(rect2), nil);
+}
+
+- (void)testGTMScaleRectangleToSize {
+ NSRect rect = NSMakeRect(0.0f, 0.0f, 10.0f, 10.0f);
+ typedef struct {
+ NSSize size_;
+ NSSize newSize_;
+ } Test;
+ Test tests[] = {
+ { { 5.0, 10.0 }, { 5.0, 5.0 } },
+ { { 10.0, 5.0 }, { 5.0, 5.0 } },
+ { { 10.0, 10.0 }, { 10.0, 10.0 } },
+ { { 11.0, 11.0, }, { 10.0, 10.0 } },
+ { { 5.0, 2.0 }, { 2.0, 2.0 } },
+ { { 2.0, 5.0 }, { 2.0, 2.0 } },
+ { { 2.0, 2.0 }, { 2.0, 2.0 } },
+ { { 0.0, 10.0 }, { 0.0, 0.0 } }
+ };
+
+ for (size_t i = 0; i < sizeof(tests) / sizeof(Test); ++i) {
+ NSRect result = GTMScaleRectangleToSize(rect, tests[i].size_,
+ NSScaleProportionally);
+ STAssertEquals(result, GTMNSRectOfSize(tests[i].newSize_), @"failed on test %z", i);
+ }
+
+ NSRect result = GTMScaleRectangleToSize(NSZeroRect, tests[0].size_,
+ NSScaleProportionally);
+ STAssertEquals(result, NSZeroRect, nil);
+
+ result = GTMScaleRectangleToSize(rect, tests[0].size_,
+ NSScaleToFit);
+ STAssertEquals(result, GTMNSRectOfSize(tests[0].size_), nil);
+
+ result = GTMScaleRectangleToSize(rect, tests[0].size_,
+ NSScaleNone);
+ STAssertEquals(result, rect, nil);
+
+}
+@end
diff --git a/AppKit/GTMLinearRGBShading.h b/AppKit/GTMLinearRGBShading.h
new file mode 100644
index 0000000..f6b6405
--- /dev/null
+++ b/AppKit/GTMLinearRGBShading.h
@@ -0,0 +1,76 @@
+//
+// GTMLinearRGBShading.h
+//
+// Copyright 2006-2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+
+#import <Cocoa/Cocoa.h>
+#import "GTMShading.h"
+#import "GTMCalculatedRange.h"
+
+/// A shading that does returns smooth linear values for RGB.
+//
+/// Thus if you create a shading from 0.0->red to 1.0->blue you will get
+/// \verbatim
+/// - 0.5->purple
+/// - 0.75->eggplant
+/// - 0.25->magenta
+/// \endverbatim
+
+@interface GTMLinearRGBShading : GTMCalculatedRange <GTMShading> {
+@private
+ CGFunctionRef function_; // function used to calculated shading (STRONG)
+ CGColorSpaceRef colorSpace_; // colorspace used for shading (STRONG)
+ BOOL isCalibrated_; // are we using calibrated or device RGB.
+ float colorValue[4]; // the RGBA color values
+}
+
+/// Generate a shading with color |begin| at position 0.0 and color |end| at 1.0.
+//
+// Args:
+// begin: color at beginning of range
+// end: color at end of range
+// colorSpaceName: name of colorspace to draw into must be either
+// NSCalibratedRGBColorSpace or NSDeviceRGBColorSpace
+//
+// Returns:
+// a GTMLinearRGBShading
++ (id)shadingFromColor:(NSColor *)begin toColor:(NSColor *)end
+ fromSpaceNamed:(NSString*)colorSpaceName;
+
+/// Generate a shading with a collection of colors at various positions.
+//
+// Args:
+// colors: a C style array containg the colors we are adding
+// colorSpaceName: name of colorspace to draw into must be either
+// NSCalibratedRGBColorSpace or NSDeviceRGBColorSpace
+// positions: a C style array containg the positions we want to
+// add the colors at
+// numberOfColors: how many colors/positions we are adding
+//
+// Returns:
+// a GTMLinearRGBShading
++ (id)shadingWithColors:(NSColor **)colors
+ fromSpaceNamed:(NSString*)colorSpaceName
+ atPositions:(float *)positions
+ count:(unsigned int)numberOfColors;
+
+/// Designated initializer
+// Args:
+// colorSpaceName - name of the colorspace to use must be either
+// NSCalibratedRGBColorSpace or NSDeviceRGBColorSpace
+- (id)initWithColorSpaceName:(NSString*)colorSpaceName;
+
+@end
diff --git a/AppKit/GTMLinearRGBShading.m b/AppKit/GTMLinearRGBShading.m
new file mode 100644
index 0000000..ab5bda6
--- /dev/null
+++ b/AppKit/GTMLinearRGBShading.m
@@ -0,0 +1,193 @@
+//
+// GTMLinearRGBShading.m
+//
+// Copyright 2006-2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+
+#import "GTMLinearRGBShading.h"
+
+// Carbon callback function required for CoreGraphics
+static void cShadeFunction(void *info, const float *inPos, float *outVals);
+
+@implementation GTMLinearRGBShading
++ (id)shadingFromColor:(NSColor *)begin toColor:(NSColor *)end
+ fromSpaceNamed:(NSString*)colorSpaceName {
+ NSColor *theColors[] = { begin, end };
+ float thePositions[] = { 0.0f, 1.0f };
+ return [[self class] shadingWithColors:theColors
+ fromSpaceNamed:colorSpaceName
+ atPositions:thePositions
+ count:(sizeof(thePositions)/sizeof(float))];
+}
+
++ (id)shadingWithColors:(NSColor **)colors fromSpaceNamed:(NSString*)colorSpaceName
+ atPositions:(float *)positions count:(unsigned)count {
+
+ GTMLinearRGBShading *theShading = [[[[self class] alloc] initWithColorSpaceName:colorSpaceName] autorelease];
+ for (unsigned int i = 0; i < count; ++i) {
+ [theShading insertStop:colors[i] atPosition:positions[i]];
+ }
+ return theShading;
+}
+
+- (id)initWithColorSpaceName:(NSString*)colorSpaceName {
+ if ((self = [super init])) {
+ if ([colorSpaceName isEqualToString:NSDeviceRGBColorSpace]) {
+ isCalibrated_ = NO;
+ } else if ([colorSpaceName isEqualToString:NSCalibratedRGBColorSpace]) {
+ isCalibrated_ = YES;
+ }
+ else {
+ [self dealloc];
+ self = nil;
+ }
+ }
+ return self;
+}
+
+- (void) dealloc {
+ if (nil != function_) {
+ CGFunctionRelease(function_);
+ }
+ if (nil != colorSpace_) {
+ CGColorSpaceRelease(colorSpace_);
+ }
+ [super dealloc];
+}
+
+
+- (void)insertStop:(id)item atPosition:(float)position {
+ NSString *colorSpaceName = isCalibrated_ ? NSCalibratedRGBColorSpace : NSDeviceRGBColorSpace;
+ NSColor *tempColor = [item colorUsingColorSpaceName: colorSpaceName];
+ if (nil != tempColor) {
+ [super insertStop:tempColor atPosition:position];
+ }
+}
+
+// Calculate a linear value based on our stops
+- (id)valueAtPosition:(float)position {
+ unsigned int index = 0;
+ unsigned int colorCount = [self stopCount];
+ float stop1Position = 0.0f;
+ NSColor *stop1Color = [self stopAtIndex:index position:&stop1Position];
+ index += 1;
+ float stop2Position = 0.0f;
+ NSColor *stop2Color = nil;
+ NSColor *theColor = nil;
+ if (colorCount > 1) {
+ stop2Color = [self stopAtIndex:index position:&stop2Position];
+ index += 1;
+ } else {
+ // if we only have one value, that's what we return
+ stop2Position = stop1Position;
+ stop2Color = stop1Color;
+ }
+
+ while (index < colorCount && stop2Position < position) {
+ stop1Color = stop2Color;
+ stop1Position = stop2Position;
+ stop2Color = [self stopAtIndex:index position:&stop2Position];
+ index += 1;
+ }
+
+ if (position <= stop1Position) {
+ // if we are less than our lowest position, return our first color
+ theColor = stop1Color;
+ [stop1Color getRed:&colorValue[0] green:&colorValue[1]
+ blue:&colorValue[2] alpha:&colorValue[3]];
+ } else if (position >= stop2Position) {
+ // likewise if we are greater than our highest position, return the last color
+ [stop2Color getRed:&colorValue[0] green:&colorValue[1]
+ blue:&colorValue[2] alpha:&colorValue[3]];
+ } else {
+ // otherwise interpolate between the two
+ position = (position - stop1Position) / (stop2Position - stop1Position);
+ float red1, red2, green1, green2, blue1, blue2, alpha1, alpha2;
+ [stop1Color getRed:&red1 green:&green1 blue:&blue1 alpha:&alpha1];
+ [stop2Color getRed:&red2 green:&green2 blue:&blue2 alpha:&alpha2];
+
+ colorValue[0] = (red2 - red1) * position + red1;
+ colorValue[1] = (green2 - green1) * position + green1;
+ colorValue[2] = (blue2 - blue1) * position + blue1;
+ colorValue[3] = (alpha2 - alpha1) * position + alpha1;
+ }
+
+ // Yes, I am casting a float[] to an id to pass it by the compiler. This
+ // significantly improves performance though as I avoid creating an NSColor
+ // for every scanline which later has to be cleaned up in an autorelease pool
+ // somewhere. Causes guardmalloc to run significantly faster.
+ return (id)colorValue;
+}
+
+//
+// switch from C to obj-C. The callback to a shader is a c function
+// but we want to call our objective c object to do all the
+// calculations for us. We have passed our function our
+// GTMLinearRGBShading as an obj-c object in the |info| so
+// we just turn around and ask it to calculate our value based
+// on |inPos| and then stick the results back in |outVals|
+//
+// Args:
+// info: is the GTMLinearRGBShading as an
+// obj-C object.
+// inPos: the position to calculate values for. This is a pointer to
+// a single float value
+// outVals: where we store our return values. Since we are calculating
+// an RGBA color, this is a pointer to an array of four float values
+// ranging from 0.0f to 1.0f
+//
+//
+static void cShadeFunction(void *info, const float *inPos, float *outVals) {
+ id object = (id)info;
+ float *colorValue = (float*)[object valueAtPosition:*inPos];
+ outVals[0] = colorValue[0];
+ outVals[1] = colorValue[1];
+ outVals[2] = colorValue[2];
+ outVals[3] = colorValue[3];
+}
+
+- (CGFunctionRef) shadeFunction {
+ // lazily create the function as necessary
+ if (nil == function_) {
+ // We have to go to carbon here, and create the CGFunction. Note that this
+ // diposed if necessary in the dealloc call.
+ const CGFunctionCallbacks shadeFunctionCallbacks = { 0, &cShadeFunction, NULL };
+
+ // TODO: (dmaclach): this code assumes that we have a range from 0.0f to 1.0f
+ // which may not be true according to the stops that the user has given us.
+ // In general you have stops at 0.0 and 1.0, so this will do for right now
+ // but may be an issue in the future.
+ const float inRange[2] = { 0.0f, 1.0f };
+ const float outRange[8] = { 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f };
+ function_ = CGFunctionCreate(self,
+ sizeof(inRange) / (sizeof(float) * 2), inRange,
+ sizeof(outRange) / (sizeof(float) * 2), outRange,
+ &shadeFunctionCallbacks);
+ }
+ return function_;
+}
+
+- (CGColorSpaceRef)colorSpace {
+ // lazily create the colorspace as necessary
+ if (nil == colorSpace_) {
+ if (isCalibrated_) {
+ colorSpace_ = CGColorSpaceCreateWithName(kCGColorSpaceUserRGB);
+ } else {
+ colorSpace_ = CGColorSpaceCreateDeviceRGB();
+ }
+ }
+ return colorSpace_;
+}
+@end
diff --git a/AppKit/GTMLinearRGBShadingTest.m b/AppKit/GTMLinearRGBShadingTest.m
new file mode 100644
index 0000000..6407e71
--- /dev/null
+++ b/AppKit/GTMLinearRGBShadingTest.m
@@ -0,0 +1,94 @@
+//
+// GTMLinearRGBShadingTest.m
+//
+// Copyright 2006-2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+
+#import <SenTestingKit/SenTestingKit.h>
+#import "GTMLinearRGBShading.h"
+#import "GTMNSColor+Theme.h"
+
+
+@interface GTMLinearRGBShadingTest : SenTestCase
+@end
+
+@implementation GTMLinearRGBShadingTest
+- (void)testShadingFrom {
+ // Create a shading from red to blue, and check if 50% is purple
+ NSColor *red = [NSColor gtm_deviceRedColor];
+ NSColor *blue = [NSColor gtm_deviceBlueColor];
+ NSColor *purple = [NSColor gtm_devicePurpleColor];
+ GTMLinearRGBShading *theShading =
+ [GTMLinearRGBShading shadingFromColor:red
+ toColor:blue
+ fromSpaceNamed:NSDeviceRGBColorSpace];
+ STAssertNotNil(theShading,nil);
+ STAssertTrue([theShading stopCount] == 2, nil);
+ float *theColor = (float*)[theShading valueAtPosition: 0.5];
+ STAssertTrue(theColor[0] == [purple redComponent] &&
+ theColor[1] == [purple greenComponent] &&
+ theColor[2] == [purple blueComponent] &&
+ theColor[3] == [purple alphaComponent], nil);
+}
+
+- (void)testShadingWith {
+ // Create a shading with kColorCount colors and make sure all the values are there.
+ const unsigned int kColorCount = 100;
+ NSColor *theColors[kColorCount];
+ float thePositions[kColorCount];
+ const float kColorIncrement = 1.0f / kColorCount;
+ for (unsigned int i = 0; i < kColorCount; i++) {
+ thePositions[i] = kColorIncrement * i;
+ theColors[i] = [NSColor colorWithDeviceRed:kColorIncrement * i
+ green:kColorIncrement * i
+ blue:kColorIncrement * i
+ alpha:kColorIncrement * i];
+ }
+ GTMLinearRGBShading *theShading =
+ [GTMLinearRGBShading shadingWithColors:theColors
+ fromSpaceNamed:NSDeviceRGBColorSpace
+ atPositions:thePositions
+ count:kColorCount];
+ for (unsigned int i = 0; i < kColorCount; i++) {
+ float *theColor = (float*)[theShading valueAtPosition: kColorIncrement * i];
+ STAssertTrue(theColor[0] == kColorIncrement * i &&
+ theColor[1] == kColorIncrement * i &&
+ theColor[2] == kColorIncrement * i &&
+ theColor[3] == kColorIncrement * i, nil);
+ }
+}
+
+- (void)testShadeFunction {
+ GTMLinearRGBShading *theShading =
+ [GTMLinearRGBShading shadingWithColors:nil
+ fromSpaceNamed:NSDeviceRGBColorSpace
+ atPositions:nil
+ count:0];
+ CGFunctionRef theFunction = [theShading shadeFunction];
+ STAssertTrue(nil != theFunction, nil);
+ STAssertTrue(CFGetTypeID(theFunction) == CGFunctionGetTypeID(), nil);
+}
+
+- (void)testColorSpace {
+ GTMLinearRGBShading *theShading =
+ [GTMLinearRGBShading shadingWithColors:nil
+ fromSpaceNamed:NSDeviceRGBColorSpace
+ atPositions:nil
+ count:0];
+ CGColorSpaceRef theColorSpace = [theShading colorSpace];
+ STAssertTrue(nil != theColorSpace, nil);
+ STAssertTrue(CFGetTypeID(theColorSpace) == CGColorSpaceGetTypeID(), nil);
+}
+@end
diff --git a/AppKit/GTMLoginItems.h b/AppKit/GTMLoginItems.h
new file mode 100644
index 0000000..1531bd0
--- /dev/null
+++ b/AppKit/GTMLoginItems.h
@@ -0,0 +1,87 @@
+//
+// GTMLoginItems.h
+//
+// Copyright 2007-2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+
+#import <Foundation/Foundation.h>
+
+/// Login items key constants, used as keys in |+loginItems|
+//
+// Item name
+extern NSString * const kGTMLoginItemsNameKey;
+// Item path
+extern NSString * const kGTMLoginItemsPathKey;
+// Hidden (NSNumber bool)
+extern NSString * const kGTMLoginItemsHiddenKey;
+
+/// GTMLoginItems
+//
+/// A helper class to manipulate the user's Login Items.
+@interface GTMLoginItems : NSObject
+
+/// Obtain a complete list of all login items.
+//
+// Returns:
+// Autoreleased array of dictionaries keyed with kGTMLoginItemsPathKey, etc.
+//
++ (NSArray *)loginItems:(NSError **)errorInfo;
+
+/// Check if the given path is in the current user's Login Items
+//
+// Args:
+// path: path to the application
+//
+// Returns:
+// YES if the path is in the Login Items
+//
++ (BOOL)pathInLoginItems:(NSString *)path;
+
+/// Check if the given name is in the current user's Login Items
+//
+// Args:
+// name: name to the application
+//
+// Returns:
+// YES if the name is in the Login Items
+//
++ (BOOL)itemWithNameInLoginItems:(NSString *)name;
+
+/// Add the given path to the current user's Login Items. Does nothing if the
+/// path is already there.
+//
+// Args:
+// path: path to add
+// hide: Set to YES to have the item launch hidden
+//
++ (void)addPathToLoginItems:(NSString *)path hide:(BOOL)hide;
+
+/// Remove the given path from the current user's Login Items. Does nothing if
+/// the path is not there.
+//
+// Args:
+// path: the path to remove
+//
++ (void)removePathFromLoginItems:(NSString *)path;
+
+/// Remove the given item name from the current user's Login Items. Does nothing
+/// if no item with that name is present.
+//
+// Args:
+// name: name of the item to remove
+//
++ (void)removeItemWithNameFromLoginItems:(NSString *)name;
+
+@end
diff --git a/AppKit/GTMLoginItems.m b/AppKit/GTMLoginItems.m
new file mode 100644
index 0000000..d3197c0
--- /dev/null
+++ b/AppKit/GTMLoginItems.m
@@ -0,0 +1,183 @@
+//
+// GTMLoginItems.m
+// Based on AELoginItems from DTS.
+//
+// Copyright 2007-2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+
+#import "GTMLoginItems.h"
+
+#include <Carbon/Carbon.h>
+
+// Exposed constants
+NSString * const kGTMLoginItemsNameKey = @"Name";
+NSString * const kGTMLoginItemsPathKey = @"Path";
+NSString * const kGTMLoginItemsHiddenKey = @"Hide";
+
+@interface GTMLoginItems (PrivateMethods)
++ (int)indexOfLoginItemWithValue:(id)value
+ forKey:(NSString *)key
+ loginItems:(NSArray *)items;
++ (BOOL)compileAndRunScript:(NSString *)script
+ withError:(NSError **)errorInfo;
+@end
+
+@implementation GTMLoginItems (PrivateMethods)
+
++ (int)indexOfLoginItemWithValue:(id)value
+ forKey:(NSString *)key
+ loginItems:(NSArray *)items {
+ if (!value || !key || !items) return NSNotFound;
+ NSDictionary *item = nil;
+ NSEnumerator *itemsEnum = [items objectEnumerator];
+ int found = -1;
+ while ((item = [itemsEnum nextObject])) {
+ ++found;
+ id itemValue = [item objectForKey:key];
+ if (itemValue && [itemValue isEqual:value]) {
+ return found;
+ }
+ }
+ return NSNotFound;
+}
+
++ (BOOL)compileAndRunScript:(NSString *)script
+ withError:(NSError **)errorInfo {
+ if ([script length] == 0) {
+ if (errorInfo)
+ *errorInfo = [NSError errorWithDomain:@"GTMLoginItems" code:-90 userInfo:nil];
+ return NO;
+ }
+ NSAppleScript *query = [[[NSAppleScript alloc] initWithSource:script] autorelease];
+ NSDictionary *errDict = nil;
+ if ( ![query compileAndReturnError:&errDict]) {
+ if (errorInfo)
+ *errorInfo = [NSError errorWithDomain:@"GTMLoginItems" code:-91 userInfo:errDict];
+ return NO;
+ }
+ NSAppleEventDescriptor *scriptResult = [query executeAndReturnError:&errDict];
+ if (!scriptResult) {
+ if (*errorInfo)
+ *errorInfo = [NSError errorWithDomain:@"GTMLoginItems" code:-92 userInfo:errDict];
+ return NO;
+ }
+ // we don't process the result
+ return YES;
+}
+
+@end
+
+@implementation GTMLoginItems
+
++ (NSArray*)loginItems:(NSError **)errorInfo {
+ NSDictionary *errDict = nil;
+ // get the script compiled and saved off
+ static NSAppleScript *query = nil;
+ if (!query) {
+ NSString *querySource = @"tell application \"System Events\" to get properties of login items";
+ query = [[NSAppleScript alloc] initWithSource:querySource];
+ if ( ![query compileAndReturnError:&errDict]) {
+ if (errorInfo)
+ *errorInfo = [NSError errorWithDomain:@"GTMLoginItems" code:-1 userInfo:errDict];
+ [query release];
+ query = nil;
+ return nil;
+ }
+ }
+ // run the script
+ NSAppleEventDescriptor *scriptResult = [query executeAndReturnError:&errDict];
+ if (!scriptResult) {
+ if (*errorInfo)
+ *errorInfo = [NSError errorWithDomain:@"GTMLoginItems" code:-2 userInfo:errDict];
+ return nil;
+ }
+ // build our results
+ NSMutableArray *result = [NSMutableArray array];
+ int count = [scriptResult numberOfItems];
+ for (int i = 0; i < count; ++i) {
+ NSAppleEventDescriptor *aeItem = [scriptResult descriptorAtIndex:i+1];
+ NSAppleEventDescriptor *hidn = [aeItem descriptorForKeyword:kAEHidden];
+ NSAppleEventDescriptor *nam = [aeItem descriptorForKeyword:pName];
+ NSAppleEventDescriptor *ppth = [aeItem descriptorForKeyword:'ppth'];
+ NSMutableDictionary *item = [NSMutableDictionary dictionary];
+ if (hidn && [hidn booleanValue]) {
+ [item setObject:[NSNumber numberWithBool:YES] forKey:kGTMLoginItemsHiddenKey];
+ }
+ if (nam) {
+ NSString *name = [nam stringValue];
+ if (name) {
+ [item setObject:name forKey:kGTMLoginItemsNameKey];
+ }
+ }
+ if (ppth) {
+ NSString *path = [ppth stringValue];
+ if (path) {
+ [item setObject:path forKey:kGTMLoginItemsPathKey];
+ }
+ }
+ [result addObject:item];
+ }
+
+ return result;
+}
+
++ (BOOL)pathInLoginItems:(NSString *)path {
+ NSArray *loginItems = [self loginItems:nil];
+ int index = [self indexOfLoginItemWithValue:path
+ forKey:kGTMLoginItemsPathKey
+ loginItems:loginItems];
+ return (index != NSNotFound) ? YES : NO;
+}
+
++ (BOOL)itemWithNameInLoginItems:(NSString *)name {
+ NSArray *loginItems = [self loginItems:nil];
+ int index = [self indexOfLoginItemWithValue:name
+ forKey:kGTMLoginItemsNameKey
+ loginItems:loginItems];
+ return (index != NSNotFound) ? YES : NO;
+}
+
++ (void)addPathToLoginItems:(NSString*)path hide:(BOOL)hide {
+ if (!path) return;
+ // make sure it isn't already there
+ if ([self pathInLoginItems:path]) return;
+ // now append it
+ NSString *scriptSource =
+ [NSString stringWithFormat:
+ @"tell application \"System Events\" to make new login item with properties { path:\"%s\", hidden:%s } at end",
+ [path UTF8String],
+ (hide ? "yes" : "no")];
+ [self compileAndRunScript:scriptSource withError:nil];
+}
+
++ (void)removePathFromLoginItems:(NSString*)path {
+ if ([path length] == 0) return;
+ NSString *scriptSource =
+ [NSString stringWithFormat:
+ @"tell application \"System Events\" to delete (login items whose path is \"%s\")",
+ [path UTF8String]];
+ [self compileAndRunScript:scriptSource withError:nil];
+}
+
++ (void)removeItemWithNameFromLoginItems:(NSString *)name {
+ if ([name length] == 0) return;
+ NSString *scriptSource =
+ [NSString stringWithFormat:
+ @"tell application \"System Events\" to delete (login items whose name is \"%s\")",
+ [name UTF8String]];
+ [self compileAndRunScript:scriptSource withError:nil];
+}
+
+@end
diff --git a/AppKit/GTMLoginItemsTest.m b/AppKit/GTMLoginItemsTest.m
new file mode 100644
index 0000000..05a93a1
--- /dev/null
+++ b/AppKit/GTMLoginItemsTest.m
@@ -0,0 +1,112 @@
+//
+// GTMLoginItemsTest.m
+//
+// Copyright 2006-2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+
+#import <SenTestingKit/SenTestingKit.h>
+#import "GTMSenTestCase.h"
+#import "GTMLoginItems.h"
+
+ // we don't really run this test because if someone had it in some automated
+ // tests, then if something did fail, it could leave things in the login items
+ // on the computer which could be a nasty surprise.
+#define TESTS_ENABLED 0
+
+
+@interface GTMLoginItemsTest : SenTestCase
+@end
+
+#if TESTS_ENABLED
+
+static BOOL ItemsListHasPath(NSArray *items, NSString *path) {
+ NSDictionary *item = nil;
+ NSEnumerator *itemsEnum = [items objectEnumerator];
+ while ((item = [itemsEnum nextObject])) {
+ NSString *itemPath = [item objectForKey:kGTMLoginItemsPathKey];
+ if (itemPath && [itemPath isEqual:path]) {
+ return YES;
+ }
+ }
+ return NO;
+}
+
+#endif // TESTS_ENABLED
+
+@implementation GTMLoginItemsTest
+
+- (void)testGTMLoginItems {
+
+#if TESTS_ENABLED
+
+ NSError *error = nil;
+ NSString *textEditPath = @"/Applications/TextEdit.app";
+ NSString *textEditName = @"TextEdit";
+
+ // fetch the starting values
+ NSArray *initialItems = [GTMLoginItems loginItems:&error];
+ STAssertNotNil(initialItems, @"shouldn't be nil (%@)", error);
+ STAssertFalse(ItemsListHasPath(initialItems, textEditPath),
+ @"textedit shouldn't be in list to start for test (%@)", initialItems);
+
+ // add textedit
+ [GTMLoginItems addPathToLoginItems:textEditPath hide:NO];
+ NSArray *curItems = [GTMLoginItems loginItems:nil];
+ STAssertNotEqualObjects(initialItems, curItems, nil);
+
+ // check by path
+ STAssertTrue([GTMLoginItems pathInLoginItems:textEditPath], nil);
+
+ // check by name
+ STAssertTrue([GTMLoginItems itemWithNameInLoginItems:textEditName], nil);
+
+ // remove it by path
+ [GTMLoginItems removePathFromLoginItems:textEditPath];
+ curItems = [GTMLoginItems loginItems:nil];
+ STAssertEqualObjects(initialItems, curItems, nil);
+
+ // check by path
+ STAssertFalse([GTMLoginItems pathInLoginItems:textEditPath], nil);
+
+ // check by name
+ STAssertFalse([GTMLoginItems itemWithNameInLoginItems:textEditName], nil);
+
+ // add textedit
+ [GTMLoginItems addPathToLoginItems:textEditPath hide:NO];
+ curItems = [GTMLoginItems loginItems:nil];
+ STAssertNotEqualObjects(initialItems, curItems, nil);
+
+ // check by path
+ STAssertTrue([GTMLoginItems pathInLoginItems:textEditPath], nil);
+
+ // check by name
+ STAssertTrue([GTMLoginItems itemWithNameInLoginItems:textEditName], nil);
+
+ // remove it by name
+ [GTMLoginItems removeItemWithNameFromLoginItems:textEditName];
+ curItems = [GTMLoginItems loginItems:nil];
+ STAssertEqualObjects(initialItems, curItems, nil);
+
+ // check by path
+ STAssertFalse([GTMLoginItems pathInLoginItems:textEditPath], nil);
+
+ // check by name
+ STAssertFalse([GTMLoginItems itemWithNameInLoginItems:textEditName], nil);
+
+#endif // TESTS_ENABLED
+
+}
+
+@end
diff --git a/AppKit/GTMNSBezierPath+CGPath.h b/AppKit/GTMNSBezierPath+CGPath.h
new file mode 100644
index 0000000..3349142
--- /dev/null
+++ b/AppKit/GTMNSBezierPath+CGPath.h
@@ -0,0 +1,33 @@
+//
+// GTMNSBezierPath+CGPath.h
+//
+// Copyright 2006-2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+
+#import <Cocoa/Cocoa.h>
+
+/// Category for extracting a CGPathRef from a NSBezierPath
+@interface NSBezierPath (GTMBezierPathCGPathAdditions)
+
+/// Extract a CGPathRef from a NSBezierPath.
+//
+// Args:
+//
+// Returns:
+// Converted CGPathRef. Must be released by client (CGPathRelease).
+// nil if failure.
+- (CGPathRef)gtm_createCGPath;
+
+@end
diff --git a/AppKit/GTMNSBezierPath+CGPath.m b/AppKit/GTMNSBezierPath+CGPath.m
new file mode 100644
index 0000000..97464fc
--- /dev/null
+++ b/AppKit/GTMNSBezierPath+CGPath.m
@@ -0,0 +1,68 @@
+//
+// GTMNSBezierPath+CGPath.h
+//
+// Category for extracting a CGPathRef from a NSBezierPath
+//
+// Copyright 2006-2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+#import "GTMNSBezierPath+CGPath.h"
+
+@implementation NSBezierPath (GTMBezierPathCGPathAdditions)
+
+// Extract a CGPathRef from a NSBezierPath.
+//
+// Args:
+//
+// Returns:
+// Converted CGPathRef. Must be released by client (CGPathRelease).
+// nil if failure.
+- (CGPathRef)gtm_createCGPath {
+ CGMutablePathRef thePath = CGPathCreateMutable();
+ if (!thePath) return nil;
+
+ unsigned int elementCount = [self elementCount];
+
+ // The maximum number of points is 3 for a NSCurveToBezierPathElement.
+ // (controlPoint1, controlPoint2, and endPoint)
+ NSPoint controlPoints[3];
+
+ for (unsigned int i = 0; i < elementCount; i++) {
+ switch ([self elementAtIndex:i associatedPoints:controlPoints]) {
+ case NSMoveToBezierPathElement:
+ CGPathMoveToPoint(thePath, &CGAffineTransformIdentity,
+ controlPoints[0].x, controlPoints[0].y);
+ break;
+ case NSLineToBezierPathElement:
+ CGPathAddLineToPoint(thePath, &CGAffineTransformIdentity,
+ controlPoints[0].x, controlPoints[0].y);
+ break;
+ case NSCurveToBezierPathElement:
+ CGPathAddCurveToPoint(thePath, &CGAffineTransformIdentity,
+ controlPoints[0].x, controlPoints[0].y,
+ controlPoints[1].x, controlPoints[1].y,
+ controlPoints[2].x, controlPoints[2].y);
+ break;
+ case NSClosePathBezierPathElement:
+ CGPathCloseSubpath(thePath);
+ break;
+ default:
+ NSLog(@"Unknown element at [NSBezierPath (GTMBezierPathCGPathAdditions) cgPath]");
+ break;
+ };
+ }
+ return thePath;
+}
+
+@end
diff --git a/AppKit/GTMNSBezierPath+CGPathTest.m b/AppKit/GTMNSBezierPath+CGPathTest.m
new file mode 100644
index 0000000..c8506d7
--- /dev/null
+++ b/AppKit/GTMNSBezierPath+CGPathTest.m
@@ -0,0 +1,78 @@
+//
+// GTMNSBezierPath+CGPathTest.m
+//
+// Copyright 2006-2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+
+#import <Cocoa/Cocoa.h>
+
+#import <SenTestingKit/SenTestingKit.h>
+#import "GTMNSBezierPath+CGPath.h"
+#import "GTMNSView+UnitTesting.h"
+
+@interface GTMNSBezierPath_CGPathTest : SenTestCase<GTMUnitTestViewDrawer>
+@end
+
+@implementation GTMNSBezierPath_CGPathTest
+
+- (void)testCreateCGPath {
+ GTMAssertDrawingEqualToFile(self, NSMakeSize(100, 100), @"GTMNSBezierPath+CGPathTest", nil, nil);
+}
+
+
+// Draws all of our tests so that we can compare this to our stored TIFF file.
+- (void)unitTestViewDrawRect:(NSRect)rect contextInfo:(void*)contextInfo{
+ NSBezierPath *thePath = [NSBezierPath bezierPath];
+ NSPoint theStart = NSMakePoint(20.0f, 20.0f);
+
+ // Test moveto/lineto
+ [thePath moveToPoint: theStart];
+ for (unsigned int i = 0; i < 10; ++i) {
+ NSPoint theNewPoint = NSMakePoint(i * 5, i * 10);
+ [thePath lineToPoint: theNewPoint];
+ theNewPoint = NSMakePoint(i * 2, i * 6);
+ [thePath moveToPoint: theNewPoint];
+ }
+
+ // Test moveto/curveto
+ for (unsigned int i = 0; i < 10; ++i) {
+ NSPoint startPoint = NSMakePoint(5.0f, 50.0f);
+ NSPoint endPoint = NSMakePoint(55.0f, 50.0f);
+ NSPoint controlPoint1 = NSMakePoint(17.5f, 50.0f + 5.0f * i);
+ NSPoint controlPoint2 = NSMakePoint(42.5f, 50.0f - 5.0f * i);
+ [thePath moveToPoint:startPoint];
+ [thePath curveToPoint:endPoint controlPoint1:controlPoint1 controlPoint2:controlPoint2];
+ }
+ // test close
+ [thePath closePath];
+
+ CGPathRef cgPath = [thePath gtm_createCGPath];
+ if (nil == cgPath) {
+ @throw [NSException failureInFile:[NSString stringWithCString:__FILE__]
+ atLine:__LINE__
+ withDescription:@"Nil CGPath"];
+ }
+ CGContextRef cgContext = [[NSGraphicsContext currentContext] graphicsPort];
+ if (nil == cgContext) {
+ @throw [NSException failureInFile:[NSString stringWithCString:__FILE__]
+ atLine:__LINE__
+ withDescription:@"Nil CGContext"];
+ }
+ CGContextAddPath(cgContext, cgPath);
+ CGContextStrokePath(cgContext);
+ CGPathRelease(cgPath);
+}
+
+@end
diff --git a/AppKit/GTMNSBezierPath+CGPathTest.tif b/AppKit/GTMNSBezierPath+CGPathTest.tif
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