blob: ff178bb279dbe89986c82f4d8d2a7384f9209695 [file] [log] [blame]
// 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