/core/externals/google-toolbox-for-mac/AppKit/GTMFadeTruncatingTextFieldCell.m
Objective C | 237 lines | 163 code | 32 blank | 42 comment | 20 complexity | 77b63fd25b1c16df7cf8c28e0bb47e8e MD5 | raw file
1// GTMFadeTruncatingTextFieldCell.m 2// 3// Copyright 2009 Google Inc. 4// 5// Licensed under the Apache License, Version 2.0 (the "License"); you may not 6// use this file except in compliance with the License. You may obtain a copy 7// of the License at 8// 9// http://www.apache.org/licenses/LICENSE-2.0 10// 11// Unless required by applicable law or agreed to in writing, software 12// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14// License for the specific language governing permissions and limitations under 15// the License. 16// 17 18#import "GTMFadeTruncatingTextFieldCell.h" 19 20#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 21 22@implementation GTMFadeTruncatingTextFieldCell 23- (void)awakeFromNib { 24 // Force to clipping 25 [self setLineBreakMode:NSLineBreakByClipping]; 26} 27 28- (id)initTextCell:(NSString *)aString { 29 self = [super initTextCell:aString]; 30 if (self) { 31 // Force to clipping 32 [self setLineBreakMode:NSLineBreakByClipping]; 33 } 34 return self; 35} 36 37- (void)drawTextGradientPart:(NSAttributedString *)attributedString 38 titleRect:(NSRect)titleRect 39 backgroundRect:(NSRect)backgroundRect 40 clipRect:(NSRect)clipRect 41 mask:(NSGradient *)mask 42 fadeToRight:(BOOL)fadeToRight { 43 // Draw the gradient part with a transparency layer. This makes the text look 44 // suboptimal, but since it fades out, that's ok. 45 [NSGraphicsContext saveGraphicsState]; 46 [NSBezierPath clipRect:backgroundRect]; 47 CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort]; 48 CGContextBeginTransparencyLayerWithRect(context, 49 NSRectToCGRect(backgroundRect), 0); 50 51 if ([self drawsBackground]) { 52 [[self backgroundColor] set]; 53 NSRectFillUsingOperation(backgroundRect, 54 NSCompositeSourceOver); 55 } 56 57 [NSGraphicsContext saveGraphicsState]; 58 [NSBezierPath clipRect:clipRect]; 59 [attributedString drawInRect:titleRect]; 60 [NSGraphicsContext restoreGraphicsState]; 61 62 NSPoint startPoint; 63 NSPoint endPoint; 64 if (fadeToRight) { 65 startPoint = backgroundRect.origin; 66 endPoint = NSMakePoint(NSMaxX(backgroundRect), NSMinY(backgroundRect)); 67 } else { 68 startPoint = NSMakePoint(NSMaxX(backgroundRect), NSMinY(backgroundRect)); 69 endPoint = backgroundRect.origin; 70 } 71 72 // Draw the gradient mask 73 CGContextSetBlendMode(context, kCGBlendModeDestinationIn); 74 [mask drawFromPoint:startPoint 75 toPoint:endPoint 76 options:fadeToRight ? NSGradientDrawsBeforeStartingLocation : 77 NSGradientDrawsAfterEndingLocation]; 78 79 CGContextEndTransparencyLayer(context); 80 [NSGraphicsContext restoreGraphicsState]; 81} 82 83- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView { 84 NSRect titleRect = [self titleRectForBounds:cellFrame]; 85 // For some reason the title rect is too far to the left. 86 titleRect.origin.x += 2; 87 titleRect.size.width -= 2; 88 89 NSAttributedString *attributedString = [self attributedStringValue]; 90 NSSize stringSize = [attributedString size]; 91 92 // Don't complicate drawing unless we need to clip 93 if (stringSize.width <= NSWidth(titleRect)) { 94 [super drawInteriorWithFrame:cellFrame inView:controlView]; 95 return; 96 } 97 98 // Clip the string by drawing it to the left by |offsetX|. 99 CGFloat offsetX = 0; 100 switch (truncateMode_) { 101 case GTMFadeTruncatingTail: 102 break; 103 case GTMFadeTruncatingHead: 104 offsetX = stringSize.width - titleRect.size.width; 105 break; 106 case GTMFadeTruncatingHeadAndTail: { 107 if (desiredCharactersToTruncateFromHead_ > 0) { 108 NSAttributedString *clippedHeadString = 109 [attributedString attributedSubstringFromRange: 110 NSMakeRange(0, desiredCharactersToTruncateFromHead_)]; 111 NSSize clippedHeadSize = [clippedHeadString size]; 112 113 // This is the offset at which we start drawing. This causes the 114 // beginning of the string to get clipped. 115 offsetX = clippedHeadSize.width; 116 117 // Due to the fade effect the first character is hard to see. 118 // We want to make sure the first character starting at 119 // |desiredCharactersToTruncateFromHead_| is readable so we reduce 120 // the offset by a little bit. 121 offsetX = MAX(0, offsetX - stringSize.height); 122 123 // If the offset is so large that there's empty space at the tail 124 // then reduce the offset so we can use up the empty space. 125 CGFloat delta = stringSize.width - titleRect.size.width; 126 if (offsetX > delta) 127 offsetX = delta; 128 } else { 129 // Center the string and clip equal portions of the head and tail. 130 offsetX = round((stringSize.width - titleRect.size.width) / 2.0); 131 } 132 break; 133 } 134 } 135 136 NSRect offsetTitleRect = titleRect; 137 offsetTitleRect.origin.x -= offsetX; 138 offsetTitleRect.size.width += offsetX; 139 BOOL isTruncatingHead = offsetX > 0; 140 BOOL isTruncatingTail = (stringSize.width - titleRect.size.width) > offsetX; 141 142 // Gradient is about twice our line height long 143 CGFloat gradientWidth = MIN(stringSize.height * 2, round(NSWidth(cellFrame) / 4)); 144 145 // Head, solid, and tail rects for drawing the background. 146 NSRect solidBackgroundPart = [self drawingRectForBounds:cellFrame]; 147 NSRect headBackgroundPart = NSZeroRect; 148 NSRect tailBackgroundPart = NSZeroRect; 149 if (isTruncatingHead) 150 NSDivideRect(solidBackgroundPart, &headBackgroundPart, &solidBackgroundPart, 151 gradientWidth, NSMinXEdge); 152 if (isTruncatingTail) 153 NSDivideRect(solidBackgroundPart, &tailBackgroundPart, &solidBackgroundPart, 154 gradientWidth, NSMaxXEdge); 155 156 // Head, solid and tail rects for clipping the title. This is slightly 157 // smaller than the background rects. 158 NSRect solidTitleClipPart = titleRect; 159 NSRect headTitleClipPart = NSZeroRect; 160 NSRect tailTitleClipPart = NSZeroRect; 161 if (isTruncatingHead) { 162 CGFloat width = NSMinX(solidBackgroundPart) - NSMinX(solidTitleClipPart); 163 NSDivideRect(solidTitleClipPart, &headTitleClipPart, &solidTitleClipPart, 164 width, NSMinXEdge); 165 } 166 if (isTruncatingTail) { 167 CGFloat width = NSMaxX(solidTitleClipPart) - NSMaxX(solidBackgroundPart); 168 NSDivideRect(solidTitleClipPart, &tailTitleClipPart, &solidTitleClipPart, 169 width, NSMaxXEdge); 170 } 171 172 // Draw non-gradient part without transparency layer, as light text on a dark 173 // background looks bad with a gradient layer. 174 [NSGraphicsContext saveGraphicsState]; 175 if ([self drawsBackground]) { 176 [[self backgroundColor] set]; 177 NSRectFillUsingOperation(solidBackgroundPart, NSCompositeSourceOver); 178 } 179 // We draw the text ourselves because [super drawInteriorWithFrame:inView:] 180 // doesn't draw correctly if the cell draws its own background. 181 [NSBezierPath clipRect:solidTitleClipPart]; 182 [attributedString drawInRect:offsetTitleRect]; 183 [NSGraphicsContext restoreGraphicsState]; 184 185 NSColor *startColor = [self textColor];; 186 NSColor *endColor = [startColor colorWithAlphaComponent:0.0]; 187 NSGradient *mask = [[NSGradient alloc] initWithStartingColor:startColor 188 endingColor:endColor]; 189 190 if (isTruncatingHead) 191 [self drawTextGradientPart:attributedString 192 titleRect:offsetTitleRect 193 backgroundRect:headBackgroundPart 194 clipRect:headTitleClipPart 195 mask:mask 196 fadeToRight:NO]; 197 if (isTruncatingTail) 198 [self drawTextGradientPart:attributedString 199 titleRect:offsetTitleRect 200 backgroundRect:tailBackgroundPart 201 clipRect:tailTitleClipPart 202 mask:mask 203 fadeToRight:YES]; 204 205 [mask release]; 206} 207 208- (void)setTruncateMode:(GTMFadeTruncateMode)mode { 209 if (truncateMode_ != mode) { 210 truncateMode_ = mode; 211 [[self controlView] setNeedsDisplay:YES]; 212 } 213} 214 215- (GTMFadeTruncateMode)truncateMode { 216 return truncateMode_; 217} 218 219- (void)setDesiredCharactersToTruncateFromHead:(NSUInteger)length { 220 if (desiredCharactersToTruncateFromHead_ != length) { 221 desiredCharactersToTruncateFromHead_ = length; 222 [[self controlView] setNeedsDisplay:YES]; 223 } 224} 225 226- (NSUInteger)desiredCharactersToTruncateFromHead { 227 return desiredCharactersToTruncateFromHead_; 228} 229 230// The faded ends of the cell are not opaque. 231- (BOOL)isOpaque { 232 return NO; 233} 234 235@end 236 237#endif