OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #import "chrome/browser/cocoa/download_item_cell.h" |
| 6 |
| 7 #include "app/gfx/text_elider.h" |
| 8 #include "app/l10n_util.h" |
| 9 #include "base/mac_util.h" |
| 10 #include "base/sys_string_conversions.h" |
| 11 #import "chrome/browser/cocoa/download_item_cell.h" |
| 12 #include "chrome/browser/download/download_item_model.h" |
| 13 #include "chrome/browser/download/download_manager.h" |
| 14 #import "third_party/GTM/AppKit/GTMTheme.h" |
| 15 |
| 16 namespace { |
| 17 |
| 18 // Distance from top border to icon |
| 19 const CGFloat kImagePaddingTop = 1; |
| 20 |
| 21 // Distance from left border to icon |
| 22 const CGFloat kImagePaddingLeft = 1; |
| 23 |
| 24 // Width of icon |
| 25 const CGFloat kImageWidth = 32; |
| 26 |
| 27 // Height of icon |
| 28 const CGFloat kImageHeight = 32; |
| 29 |
| 30 // x coordinate of download name string, in view coords |
| 31 const CGFloat kTextPosLeft = kImagePaddingLeft + kImageWidth + 1; |
| 32 |
| 33 // Distance from end of download name string to dropdown area |
| 34 const CGFloat kTextPaddingRight = 3; |
| 35 |
| 36 // y coordinate of download name string, in view coords, when status message |
| 37 // is visible |
| 38 const CGFloat kPrimaryTextPosTop = 5; |
| 39 |
| 40 // y coordinate of download name string, in view coords, when status message |
| 41 // is not visible |
| 42 const CGFloat kPrimaryTextOnlyPosTop = 10; |
| 43 |
| 44 // y coordinate of status message, in view coords |
| 45 const CGFloat kSecondaryTextPosTop = 17; |
| 46 |
| 47 // Width of dropdown area on the right |
| 48 const CGFloat kDropdownAreaWidth = 18; |
| 49 |
| 50 // Width of dropdown arrow |
| 51 const CGFloat kDropdownArrowWidth = 5; |
| 52 |
| 53 // Height of dropdown arrow |
| 54 const CGFloat kDropdownArrowHeight = 3; |
| 55 |
| 56 // Duration of the two-lines-to-one-line animation, in seconds |
| 57 NSTimeInterval kHideStatusDuration = 0.3; |
| 58 |
| 59 } |
| 60 |
| 61 // This is a helper class to animate the fading out of the status text. |
| 62 @interface HideSecondaryTitleAnimation : NSAnimation { |
| 63 DownloadItemCell* cell_; |
| 64 } |
| 65 - (id)initWithDownloadItemCell:(DownloadItemCell*)cell; |
| 66 @end |
| 67 |
| 68 @interface DownloadItemCell(Private) |
| 69 - (void)updateTrackingAreas:(id)sender; |
| 70 - (void)hideSecondaryTitle; |
| 71 - (void)animationProgressed:(NSAnimationProgress)progress; |
| 72 @end |
| 73 |
| 74 @implementation DownloadItemCell |
| 75 |
| 76 @synthesize secondaryTitle = secondaryTitle_; |
| 77 @synthesize secondaryFont = secondaryFont_; |
| 78 |
| 79 - (void)setInitialState { |
| 80 isStatusTextVisible_ = NO; |
| 81 titleY_ = kPrimaryTextPosTop; |
| 82 statusAlpha_ = 1.0; |
| 83 |
| 84 [self setFont:[NSFont systemFontOfSize: |
| 85 [NSFont systemFontSizeForControlSize:NSSmallControlSize]]]; |
| 86 [self setSecondaryFont:[NSFont systemFontOfSize: |
| 87 [NSFont systemFontSizeForControlSize:NSMiniControlSize]]]; |
| 88 |
| 89 [self updateTrackingAreas:self]; |
| 90 [[NSNotificationCenter defaultCenter] |
| 91 addObserver:self |
| 92 selector:@selector(updateTrackingAreas:) |
| 93 name:NSViewFrameDidChangeNotification |
| 94 object:[self controlView]]; |
| 95 } |
| 96 |
| 97 // For nib instantiations |
| 98 - (id)initWithCoder:(NSCoder*)decoder { |
| 99 if ((self = [super initWithCoder:decoder])) { |
| 100 [self setInitialState]; |
| 101 } |
| 102 return self; |
| 103 } |
| 104 |
| 105 // For programmatic instantiations |
| 106 - (id)initTextCell:(NSString *)string { |
| 107 if ((self = [super initTextCell:string])) { |
| 108 [self setInitialState]; |
| 109 } |
| 110 return self; |
| 111 } |
| 112 |
| 113 - (void)dealloc { |
| 114 [[NSNotificationCenter defaultCenter] removeObserver:self]; |
| 115 [secondaryTitle_ release]; |
| 116 [secondaryFont_ release]; |
| 117 [super dealloc]; |
| 118 } |
| 119 |
| 120 - (void)setStateFromDownload:(BaseDownloadItemModel*)downloadModel { |
| 121 // Set name and icon of download. |
| 122 downloadPath_ = downloadModel->download()->GetFileName(); |
| 123 |
| 124 // TODO(paulg): Use IconManager for loading icons on the file thread |
| 125 // (crbug.com/16226). |
| 126 NSString* extension = base::SysUTF8ToNSString(downloadPath_.Extension()); |
| 127 NSImage* icon = [[NSWorkspace sharedWorkspace] iconForFileType:extension]; |
| 128 [self setImage:icon]; |
| 129 |
| 130 std::wstring statusText = downloadModel->GetStatusText(); |
| 131 if (statusText.empty()) { |
| 132 // Remove the status text label. |
| 133 [self hideSecondaryTitle]; |
| 134 isStatusTextVisible_ = NO; |
| 135 } else { |
| 136 // Set status text. |
| 137 NSString* statusString = base::SysWideToNSString(statusText); |
| 138 [self setSecondaryTitle:statusString]; |
| 139 isStatusTextVisible_ = YES; |
| 140 } |
| 141 } |
| 142 |
| 143 - (void)updateTrackingAreas:(id)sender { |
| 144 if (trackingAreaButton_) { |
| 145 [[self controlView] removeTrackingArea:trackingAreaButton_.get()]; |
| 146 trackingAreaButton_.reset(nil); |
| 147 } |
| 148 if (trackingAreaDropdown_) { |
| 149 [[self controlView] removeTrackingArea:trackingAreaDropdown_.get()]; |
| 150 trackingAreaDropdown_.reset(nil); |
| 151 } |
| 152 |
| 153 // Use two distinct tracking rects for left and right parts. |
| 154 NSRect bounds = [[self controlView] bounds]; |
| 155 NSRect buttonRect, dropdownRect; |
| 156 NSDivideRect(bounds, &dropdownRect, &buttonRect, |
| 157 kDropdownAreaWidth, NSMaxXEdge); |
| 158 |
| 159 trackingAreaButton_.reset([[NSTrackingArea alloc] |
| 160 initWithRect:buttonRect |
| 161 options:(NSTrackingMouseEnteredAndExited | |
| 162 NSTrackingActiveInActiveApp) |
| 163 owner:self |
| 164 userInfo:nil]); |
| 165 [[self controlView] addTrackingArea:trackingAreaButton_.get()]; |
| 166 |
| 167 trackingAreaDropdown_.reset([[NSTrackingArea alloc] |
| 168 initWithRect:dropdownRect |
| 169 options:(NSTrackingMouseEnteredAndExited | |
| 170 NSTrackingActiveInActiveApp) |
| 171 owner:self |
| 172 userInfo:nil]); |
| 173 [[self controlView] addTrackingArea:trackingAreaDropdown_.get()]; |
| 174 } |
| 175 |
| 176 - (void)setShowsBorderOnlyWhileMouseInside:(BOOL)showOnly { |
| 177 // Override to make sure it doesn't do anything if it's called accidentally. |
| 178 } |
| 179 |
| 180 - (void)mouseEntered:(NSEvent*)theEvent { |
| 181 mouseInsideCount_++; |
| 182 if ([theEvent trackingArea] == trackingAreaButton_.get()) |
| 183 mousePosition_ = kDownloadItemMouseOverButtonPart; |
| 184 else if ([theEvent trackingArea] == trackingAreaDropdown_.get()) |
| 185 mousePosition_ = kDownloadItemMouseOverButtonPart; |
| 186 [[self controlView] setNeedsDisplay:YES]; |
| 187 } |
| 188 |
| 189 - (void)mouseExited:(NSEvent *)theEvent { |
| 190 mouseInsideCount_--; |
| 191 if (mouseInsideCount_ == 0) |
| 192 mousePosition_ = kDownloadItemMouseOutside; |
| 193 [[self controlView] setNeedsDisplay:YES]; |
| 194 } |
| 195 |
| 196 - (BOOL)isMouseInside { |
| 197 return mousePosition_ != kDownloadItemMouseOutside; |
| 198 } |
| 199 |
| 200 - (BOOL)isMouseOverButtonPart { |
| 201 return mousePosition_ == kDownloadItemMouseOverButtonPart; |
| 202 } |
| 203 |
| 204 - (BOOL)isButtonPartPressed { |
| 205 return [self isHighlighted] |
| 206 && mousePosition_ == kDownloadItemMouseOverButtonPart; |
| 207 } |
| 208 |
| 209 - (BOOL)isMouseOverDropdownPart { |
| 210 return mousePosition_ == kDownloadItemMouseOverButtonPart; |
| 211 } |
| 212 |
| 213 - (BOOL)isDropdownPartPressed { |
| 214 return [self isHighlighted] |
| 215 && mousePosition_ == kDownloadItemMouseOverButtonPart; |
| 216 } |
| 217 |
| 218 - (NSBezierPath*)leftRoundedPath:(CGFloat)radius inRect:(NSRect)rect { |
| 219 |
| 220 NSPoint topLeft = NSMakePoint(NSMinX(rect), NSMaxY(rect)); |
| 221 NSPoint topRight = NSMakePoint(NSMaxX(rect), NSMaxY(rect)); |
| 222 NSPoint bottomRight = NSMakePoint(NSMaxX(rect) , NSMinY(rect)); |
| 223 |
| 224 NSBezierPath* path = [NSBezierPath bezierPath]; |
| 225 [path moveToPoint:topRight]; |
| 226 [path appendBezierPathWithArcFromPoint:topLeft |
| 227 toPoint:rect.origin |
| 228 radius:radius]; |
| 229 [path appendBezierPathWithArcFromPoint:rect.origin |
| 230 toPoint:bottomRight |
| 231 radius:radius]; |
| 232 [path lineToPoint:bottomRight]; |
| 233 return path; |
| 234 } |
| 235 |
| 236 - (NSBezierPath*)rightRoundedPath:(CGFloat)radius inRect:(NSRect)rect { |
| 237 |
| 238 NSPoint topLeft = NSMakePoint(NSMinX(rect), NSMaxY(rect)); |
| 239 NSPoint topRight = NSMakePoint(NSMaxX(rect), NSMaxY(rect)); |
| 240 NSPoint bottomRight = NSMakePoint(NSMaxX(rect), NSMinY(rect)); |
| 241 |
| 242 NSBezierPath* path = [NSBezierPath bezierPath]; |
| 243 [path moveToPoint:rect.origin]; |
| 244 [path appendBezierPathWithArcFromPoint:bottomRight |
| 245 toPoint:topRight |
| 246 radius:radius]; |
| 247 [path appendBezierPathWithArcFromPoint:topRight |
| 248 toPoint:topLeft |
| 249 radius:radius]; |
| 250 [path lineToPoint:topLeft]; |
| 251 [path closePath]; // Right path is closed |
| 252 return path; |
| 253 } |
| 254 |
| 255 - (void)elideTitle:(int)availableWidth { |
| 256 NSFont* font = [self font]; |
| 257 gfx::Font font_chr = |
| 258 gfx::Font::CreateFont(base::SysNSStringToWide([font fontName]), |
| 259 [font pointSize]); |
| 260 |
| 261 NSString* titleString = base::SysWideToNSString( |
| 262 ElideFilename(downloadPath_, font_chr, availableWidth)); |
| 263 [self setTitle:titleString]; |
| 264 } |
| 265 |
| 266 - (void)drawWithFrame:(NSRect)cellFrame inView:(NSView*)controlView { |
| 267 |
| 268 // Constants from Cole. Will kConstan them once the feedback loop |
| 269 // is complete. |
| 270 NSRect drawFrame = NSInsetRect(cellFrame, 0.5, 0.5); |
| 271 NSRect innerFrame = NSInsetRect(cellFrame, 1, 1); |
| 272 |
| 273 const float radius = 3.5; |
| 274 NSWindow* window = [controlView window]; |
| 275 BOOL active = [window isKeyWindow] || [window isMainWindow]; |
| 276 |
| 277 GTMTheme* theme = [controlView gtm_theme]; |
| 278 |
| 279 NSRect buttonDrawRect, dropdownDrawRect; |
| 280 NSDivideRect(drawFrame, &dropdownDrawRect, &buttonDrawRect, |
| 281 kDropdownAreaWidth, NSMaxXEdge); |
| 282 |
| 283 NSRect buttonInnerRect, dropdownInnerRect; |
| 284 NSDivideRect(innerFrame, &dropdownInnerRect, &buttonInnerRect, |
| 285 kDropdownAreaWidth, NSMaxXEdge); |
| 286 |
| 287 NSBezierPath* buttonInnerPath = [self |
| 288 leftRoundedPath:radius inRect:buttonDrawRect]; |
| 289 NSBezierPath* buttonOuterPath = [self |
| 290 leftRoundedPath:(radius + 1) |
| 291 inRect:NSInsetRect(buttonDrawRect, -1, -1)]; |
| 292 |
| 293 NSBezierPath* dropdownInnerPath = [self |
| 294 rightRoundedPath:radius inRect:dropdownDrawRect]; |
| 295 NSBezierPath* dropdownOuterPath = [self |
| 296 rightRoundedPath:(radius + 1) |
| 297 inRect:NSInsetRect(dropdownDrawRect, -1, -1)]; |
| 298 |
| 299 // Stroke the borders and appropriate fill gradient. If we're borderless, |
| 300 // the only time we want to draw the inner gradient is if we're highlighted. |
| 301 if ([self isHighlighted] || [self isMouseInside]) { |
| 302 [self drawBorderAndFillForTheme:theme |
| 303 controlView:controlView |
| 304 outerPath:buttonOuterPath |
| 305 innerPath:buttonInnerPath |
| 306 showHighlightGradient:[self isMouseOverButtonPart] |
| 307 showClickedGradient:[self isButtonPartPressed] |
| 308 active:active |
| 309 cellFrame:cellFrame]; |
| 310 |
| 311 [self drawBorderAndFillForTheme: theme |
| 312 controlView:controlView |
| 313 outerPath:dropdownOuterPath |
| 314 innerPath:dropdownInnerPath |
| 315 showHighlightGradient:[self isMouseOverDropdownPart] |
| 316 showClickedGradient:[self isDropdownPartPressed] |
| 317 active:active |
| 318 cellFrame:cellFrame]; |
| 319 } |
| 320 |
| 321 [self drawInteriorWithFrame:innerFrame inView:controlView]; |
| 322 } |
| 323 |
| 324 - (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView*)controlView { |
| 325 // Draw title |
| 326 [self elideTitle:cellFrame.size.width - |
| 327 (kTextPosLeft + kTextPaddingRight + kDropdownAreaWidth)]; |
| 328 |
| 329 NSColor* color = [self isButtonPartPressed] |
| 330 ? [NSColor alternateSelectedControlTextColor] : [NSColor textColor]; |
| 331 NSString* primaryText = [self title]; |
| 332 |
| 333 NSDictionary* primaryTextAttributes = [NSDictionary |
| 334 dictionaryWithObjectsAndKeys: |
| 335 color, NSForegroundColorAttributeName, |
| 336 [self font], NSFontAttributeName, |
| 337 nil]; |
| 338 NSPoint primaryPos = NSMakePoint( |
| 339 cellFrame.origin.x + kTextPosLeft, |
| 340 titleY_); |
| 341 |
| 342 [primaryText drawAtPoint:primaryPos withAttributes:primaryTextAttributes]; |
| 343 |
| 344 // Draw secondary title, if any |
| 345 if ([self secondaryTitle] != nil && statusAlpha_ > 0) { |
| 346 NSString* secondaryText = [self secondaryTitle]; |
| 347 NSColor* secondaryColor = [color colorWithAlphaComponent:statusAlpha_]; |
| 348 NSDictionary* secondaryTextAttributes = [NSDictionary |
| 349 dictionaryWithObjectsAndKeys: |
| 350 secondaryColor, NSForegroundColorAttributeName, |
| 351 [self secondaryFont], NSFontAttributeName, |
| 352 nil]; |
| 353 NSPoint secondaryPos = NSMakePoint( |
| 354 cellFrame.origin.x + kTextPosLeft, |
| 355 kSecondaryTextPosTop); |
| 356 [secondaryText drawAtPoint:secondaryPos |
| 357 withAttributes:secondaryTextAttributes]; |
| 358 } |
| 359 |
| 360 // Draw icon |
| 361 NSRect imageRect = NSZeroRect; |
| 362 imageRect.size = [[self image] size]; |
| 363 [[self image] setFlipped:[controlView isFlipped]]; |
| 364 [[self image] drawInRect:[self imageRectForBounds:cellFrame] |
| 365 fromRect:imageRect |
| 366 operation:NSCompositeSourceOver |
| 367 fraction:[self isEnabled] ? 1.0 : 0.5]; |
| 368 |
| 369 // Popup arrow. Put center of mass of the arrow in the center of the |
| 370 // dropdown area. |
| 371 CGFloat cx = NSMaxX(cellFrame) - kDropdownAreaWidth/2; |
| 372 CGFloat cy = NSMidY(cellFrame); |
| 373 NSPoint p1 = NSMakePoint(cx - kDropdownArrowWidth/2, |
| 374 cy - kDropdownArrowHeight/3); |
| 375 NSPoint p2 = NSMakePoint(cx + kDropdownArrowWidth/2, |
| 376 cy - kDropdownArrowHeight/3); |
| 377 NSPoint p3 = NSMakePoint(cx, cy + kDropdownArrowHeight*2/3); |
| 378 NSBezierPath *triangle = [NSBezierPath bezierPath]; |
| 379 [triangle moveToPoint:p1]; |
| 380 [triangle lineToPoint:p2]; |
| 381 [triangle lineToPoint:p3]; |
| 382 [triangle closePath]; |
| 383 |
| 384 NSColor* fill = [self isDropdownPartPressed] |
| 385 ? [NSColor alternateSelectedControlTextColor] : [NSColor textColor]; |
| 386 [fill setFill]; |
| 387 [triangle fill]; |
| 388 } |
| 389 |
| 390 - (NSRect)imageRectForBounds:(NSRect)cellFrame { |
| 391 return NSMakeRect( |
| 392 kImagePaddingLeft, kImagePaddingTop, kImageWidth, kImageHeight); |
| 393 } |
| 394 |
| 395 - (void)hideSecondaryTitle { |
| 396 if (isStatusTextVisible_) { |
| 397 // No core animation -- text in CA layers is not subpixel antialiased :-/ |
| 398 hideStatusAnimation_.reset([[HideSecondaryTitleAnimation alloc] |
| 399 initWithDownloadItemCell:self]); |
| 400 [hideStatusAnimation_.get() setDelegate:self]; |
| 401 [hideStatusAnimation_.get() startAnimation]; |
| 402 } else { |
| 403 // If the download is done so quickly that the status line is never visible, |
| 404 // don't show an animation |
| 405 [self animationProgressed:1.0]; |
| 406 } |
| 407 } |
| 408 |
| 409 - (void)animationProgressed:(NSAnimationProgress)progress { |
| 410 titleY_ = progress*kPrimaryTextOnlyPosTop + (1 - progress)*kPrimaryTextPosTop; |
| 411 statusAlpha_ = 1 - progress; |
| 412 [[self controlView] setNeedsDisplay:YES]; |
| 413 } |
| 414 |
| 415 - (void)animationDidEnd:(NSAnimation *)animation { |
| 416 hideStatusAnimation_.reset(); |
| 417 } |
| 418 |
| 419 @end |
| 420 |
| 421 @implementation HideSecondaryTitleAnimation |
| 422 |
| 423 - (id)initWithDownloadItemCell:(DownloadItemCell*)cell { |
| 424 if ((self = [super initWithDuration:kHideStatusDuration |
| 425 animationCurve:NSAnimationEaseIn])) { |
| 426 cell_ = cell; |
| 427 [self setAnimationBlockingMode:NSAnimationNonblocking]; |
| 428 } |
| 429 return self; |
| 430 } |
| 431 |
| 432 - (void)setCurrentProgress:(NSAnimationProgress)progress { |
| 433 [super setCurrentProgress:progress]; |
| 434 [cell_ animationProgressed:progress]; |
| 435 } |
| 436 |
| 437 @end |
OLD | NEW |