// GTMFadeTruncatingTextFieldCell.m // // Copyright 2009 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 "GTMFadeTruncatingTextFieldCell.h" #if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 @implementation GTMFadeTruncatingTextFieldCell - (void)awakeFromNib { // Force to clipping [self setLineBreakMode:NSLineBreakByClipping]; } - (id)initTextCell:(NSString *)aString { self = [super initTextCell:aString]; if (self) { // Force to clipping [self setLineBreakMode:NSLineBreakByClipping]; } return self; } - (void)drawTextGradientPart:(NSAttributedString *)attributedString titleRect:(NSRect)titleRect backgroundRect:(NSRect)backgroundRect clipRect:(NSRect)clipRect mask:(NSGradient *)mask fadeToRight:(BOOL)fadeToRight { // Draw the gradient part with a transparency layer. This makes the text look // suboptimal, but since it fades out, that's ok. [NSGraphicsContext saveGraphicsState]; [NSBezierPath clipRect:backgroundRect]; CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort]; CGContextBeginTransparencyLayerWithRect(context, NSRectToCGRect(backgroundRect), 0); if ([self drawsBackground]) { [[self backgroundColor] set]; NSRectFillUsingOperation(backgroundRect, NSCompositeSourceOver); } [NSGraphicsContext saveGraphicsState]; [NSBezierPath clipRect:clipRect]; [attributedString drawInRect:titleRect]; [NSGraphicsContext restoreGraphicsState]; NSPoint startPoint; NSPoint endPoint; if (fadeToRight) { startPoint = backgroundRect.origin; endPoint = NSMakePoint(NSMaxX(backgroundRect), NSMinY(backgroundRect)); } else { startPoint = NSMakePoint(NSMaxX(backgroundRect), NSMinY(backgroundRect)); endPoint = backgroundRect.origin; } // Draw the gradient mask CGContextSetBlendMode(context, kCGBlendModeDestinationIn); [mask drawFromPoint:startPoint toPoint:endPoint options:fadeToRight ? NSGradientDrawsBeforeStartingLocation : NSGradientDrawsAfterEndingLocation]; CGContextEndTransparencyLayer(context); [NSGraphicsContext restoreGraphicsState]; } - (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView { NSRect titleRect = [self titleRectForBounds:cellFrame]; // For some reason the title rect is too far to the left. titleRect.origin.x += 2; titleRect.size.width -= 2; NSAttributedString *attributedString = [self attributedStringValue]; NSSize stringSize = [attributedString size]; // Don't complicate drawing unless we need to clip if (stringSize.width <= NSWidth(titleRect)) { [super drawInteriorWithFrame:cellFrame inView:controlView]; return; } // Clip the string by drawing it to the left by |offsetX|. CGFloat offsetX = 0; switch (truncateMode_) { case GTMFadeTruncatingTail: break; case GTMFadeTruncatingHead: offsetX = stringSize.width - titleRect.size.width; break; case GTMFadeTruncatingHeadAndTail: { if (desiredCharactersToTruncateFromHead_ > 0) { NSAttributedString *clippedHeadString = [attributedString attributedSubstringFromRange: NSMakeRange(0, desiredCharactersToTruncateFromHead_)]; NSSize clippedHeadSize = [clippedHeadString size]; // This is the offset at which we start drawing. This causes the // beginning of the string to get clipped. offsetX = clippedHeadSize.width; // Due to the fade effect the first character is hard to see. // We want to make sure the first character starting at // |desiredCharactersToTruncateFromHead_| is readable so we reduce // the offset by a little bit. offsetX = MAX(0, offsetX - stringSize.height); // If the offset is so large that there's empty space at the tail // then reduce the offset so we can use up the empty space. CGFloat delta = stringSize.width - titleRect.size.width; if (offsetX > delta) offsetX = delta; } else { // Center the string and clip equal portions of the head and tail. offsetX = round((stringSize.width - titleRect.size.width) / 2.0); } break; } } NSRect offsetTitleRect = titleRect; offsetTitleRect.origin.x -= offsetX; offsetTitleRect.size.width += offsetX; BOOL isTruncatingHead = offsetX > 0; BOOL isTruncatingTail = (stringSize.width - titleRect.size.width) > offsetX; // Gradient is about twice our line height long CGFloat gradientWidth = MIN(stringSize.height * 2, round(NSWidth(cellFrame) / 4)); // Head, solid, and tail rects for drawing the background. NSRect solidBackgroundPart = [self drawingRectForBounds:cellFrame]; NSRect headBackgroundPart = NSZeroRect; NSRect tailBackgroundPart = NSZeroRect; if (isTruncatingHead) NSDivideRect(solidBackgroundPart, &headBackgroundPart, &solidBackgroundPart, gradientWidth, NSMinXEdge); if (isTruncatingTail) NSDivideRect(solidBackgroundPart, &tailBackgroundPart, &solidBackgroundPart, gradientWidth, NSMaxXEdge); // Head, solid and tail rects for clipping the title. This is slightly // smaller than the background rects. NSRect solidTitleClipPart = titleRect; NSRect headTitleClipPart = NSZeroRect; NSRect tailTitleClipPart = NSZeroRect; if (isTruncatingHead) { CGFloat width = NSMinX(solidBackgroundPart) - NSMinX(solidTitleClipPart); NSDivideRect(solidTitleClipPart, &headTitleClipPart, &solidTitleClipPart, width, NSMinXEdge); } if (isTruncatingTail) { CGFloat width = NSMaxX(solidTitleClipPart) - NSMaxX(solidBackgroundPart); NSDivideRect(solidTitleClipPart, &tailTitleClipPart, &solidTitleClipPart, width, NSMaxXEdge); } // Draw non-gradient part without transparency layer, as light text on a dark // background looks bad with a gradient layer. [NSGraphicsContext saveGraphicsState]; if ([self drawsBackground]) { [[self backgroundColor] set]; NSRectFillUsingOperation(solidBackgroundPart, NSCompositeSourceOver); } // We draw the text ourselves because [super drawInteriorWithFrame:inView:] // doesn't draw correctly if the cell draws its own background. [NSBezierPath clipRect:solidTitleClipPart]; [attributedString drawInRect:offsetTitleRect]; [NSGraphicsContext restoreGraphicsState]; NSColor *startColor = [self textColor];; NSColor *endColor = [startColor colorWithAlphaComponent:0.0]; NSGradient *mask = [[NSGradient alloc] initWithStartingColor:startColor endingColor:endColor]; if (isTruncatingHead) [self drawTextGradientPart:attributedString titleRect:offsetTitleRect backgroundRect:headBackgroundPart clipRect:headTitleClipPart mask:mask fadeToRight:NO]; if (isTruncatingTail) [self drawTextGradientPart:attributedString titleRect:offsetTitleRect backgroundRect:tailBackgroundPart clipRect:tailTitleClipPart mask:mask fadeToRight:YES]; [mask release]; } - (void)setTruncateMode:(GTMFadeTruncateMode)mode { if (truncateMode_ != mode) { truncateMode_ = mode; [[self controlView] setNeedsDisplay:YES]; } } - (GTMFadeTruncateMode)truncateMode { return truncateMode_; } - (void)setDesiredCharactersToTruncateFromHead:(NSUInteger)length { if (desiredCharactersToTruncateFromHead_ != length) { desiredCharactersToTruncateFromHead_ = length; [[self controlView] setNeedsDisplay:YES]; } } - (NSUInteger)desiredCharactersToTruncateFromHead { return desiredCharactersToTruncateFromHead_; } // The faded ends of the cell are not opaque. - (BOOL)isOpaque { return NO; } @end #endif