| Index: chrome/browser/cocoa/download_item_cell.mm
|
| ===================================================================
|
| --- chrome/browser/cocoa/download_item_cell.mm (revision 0)
|
| +++ chrome/browser/cocoa/download_item_cell.mm (revision 0)
|
| @@ -0,0 +1,437 @@
|
| +// Copyright (c) 2009 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +#import "chrome/browser/cocoa/download_item_cell.h"
|
| +
|
| +#include "app/gfx/text_elider.h"
|
| +#include "app/l10n_util.h"
|
| +#include "base/mac_util.h"
|
| +#include "base/sys_string_conversions.h"
|
| +#import "chrome/browser/cocoa/download_item_cell.h"
|
| +#include "chrome/browser/download/download_item_model.h"
|
| +#include "chrome/browser/download/download_manager.h"
|
| +#import "third_party/GTM/AppKit/GTMTheme.h"
|
| +
|
| +namespace {
|
| +
|
| +// Distance from top border to icon
|
| +const CGFloat kImagePaddingTop = 1;
|
| +
|
| +// Distance from left border to icon
|
| +const CGFloat kImagePaddingLeft = 1;
|
| +
|
| +// Width of icon
|
| +const CGFloat kImageWidth = 32;
|
| +
|
| +// Height of icon
|
| +const CGFloat kImageHeight = 32;
|
| +
|
| +// x coordinate of download name string, in view coords
|
| +const CGFloat kTextPosLeft = kImagePaddingLeft + kImageWidth + 1;
|
| +
|
| +// Distance from end of download name string to dropdown area
|
| +const CGFloat kTextPaddingRight = 3;
|
| +
|
| +// y coordinate of download name string, in view coords, when status message
|
| +// is visible
|
| +const CGFloat kPrimaryTextPosTop = 5;
|
| +
|
| +// y coordinate of download name string, in view coords, when status message
|
| +// is not visible
|
| +const CGFloat kPrimaryTextOnlyPosTop = 10;
|
| +
|
| +// y coordinate of status message, in view coords
|
| +const CGFloat kSecondaryTextPosTop = 17;
|
| +
|
| +// Width of dropdown area on the right
|
| +const CGFloat kDropdownAreaWidth = 18;
|
| +
|
| +// Width of dropdown arrow
|
| +const CGFloat kDropdownArrowWidth = 5;
|
| +
|
| +// Height of dropdown arrow
|
| +const CGFloat kDropdownArrowHeight = 3;
|
| +
|
| +// Duration of the two-lines-to-one-line animation, in seconds
|
| +NSTimeInterval kHideStatusDuration = 0.3;
|
| +
|
| +}
|
| +
|
| +// This is a helper class to animate the fading out of the status text.
|
| +@interface HideSecondaryTitleAnimation : NSAnimation {
|
| + DownloadItemCell* cell_;
|
| +}
|
| +- (id)initWithDownloadItemCell:(DownloadItemCell*)cell;
|
| +@end
|
| +
|
| +@interface DownloadItemCell(Private)
|
| +- (void)updateTrackingAreas:(id)sender;
|
| +- (void)hideSecondaryTitle;
|
| +- (void)animationProgressed:(NSAnimationProgress)progress;
|
| +@end
|
| +
|
| +@implementation DownloadItemCell
|
| +
|
| +@synthesize secondaryTitle = secondaryTitle_;
|
| +@synthesize secondaryFont = secondaryFont_;
|
| +
|
| +- (void)setInitialState {
|
| + isStatusTextVisible_ = NO;
|
| + titleY_ = kPrimaryTextPosTop;
|
| + statusAlpha_ = 1.0;
|
| +
|
| + [self setFont:[NSFont systemFontOfSize:
|
| + [NSFont systemFontSizeForControlSize:NSSmallControlSize]]];
|
| + [self setSecondaryFont:[NSFont systemFontOfSize:
|
| + [NSFont systemFontSizeForControlSize:NSMiniControlSize]]];
|
| +
|
| + [self updateTrackingAreas:self];
|
| + [[NSNotificationCenter defaultCenter]
|
| + addObserver:self
|
| + selector:@selector(updateTrackingAreas:)
|
| + name:NSViewFrameDidChangeNotification
|
| + object:[self controlView]];
|
| +}
|
| +
|
| +// For nib instantiations
|
| +- (id)initWithCoder:(NSCoder*)decoder {
|
| + if ((self = [super initWithCoder:decoder])) {
|
| + [self setInitialState];
|
| + }
|
| + return self;
|
| +}
|
| +
|
| +// For programmatic instantiations
|
| +- (id)initTextCell:(NSString *)string {
|
| + if ((self = [super initTextCell:string])) {
|
| + [self setInitialState];
|
| + }
|
| + return self;
|
| +}
|
| +
|
| +- (void)dealloc {
|
| + [[NSNotificationCenter defaultCenter] removeObserver:self];
|
| + [secondaryTitle_ release];
|
| + [secondaryFont_ release];
|
| + [super dealloc];
|
| +}
|
| +
|
| +- (void)setStateFromDownload:(BaseDownloadItemModel*)downloadModel {
|
| + // Set name and icon of download.
|
| + downloadPath_ = downloadModel->download()->GetFileName();
|
| +
|
| + // TODO(paulg): Use IconManager for loading icons on the file thread
|
| + // (crbug.com/16226).
|
| + NSString* extension = base::SysUTF8ToNSString(downloadPath_.Extension());
|
| + NSImage* icon = [[NSWorkspace sharedWorkspace] iconForFileType:extension];
|
| + [self setImage:icon];
|
| +
|
| + std::wstring statusText = downloadModel->GetStatusText();
|
| + if (statusText.empty()) {
|
| + // Remove the status text label.
|
| + [self hideSecondaryTitle];
|
| + isStatusTextVisible_ = NO;
|
| + } else {
|
| + // Set status text.
|
| + NSString* statusString = base::SysWideToNSString(statusText);
|
| + [self setSecondaryTitle:statusString];
|
| + isStatusTextVisible_ = YES;
|
| + }
|
| +}
|
| +
|
| +- (void)updateTrackingAreas:(id)sender {
|
| + if (trackingAreaButton_) {
|
| + [[self controlView] removeTrackingArea:trackingAreaButton_.get()];
|
| + trackingAreaButton_.reset(nil);
|
| + }
|
| + if (trackingAreaDropdown_) {
|
| + [[self controlView] removeTrackingArea:trackingAreaDropdown_.get()];
|
| + trackingAreaDropdown_.reset(nil);
|
| + }
|
| +
|
| + // Use two distinct tracking rects for left and right parts.
|
| + NSRect bounds = [[self controlView] bounds];
|
| + NSRect buttonRect, dropdownRect;
|
| + NSDivideRect(bounds, &dropdownRect, &buttonRect,
|
| + kDropdownAreaWidth, NSMaxXEdge);
|
| +
|
| + trackingAreaButton_.reset([[NSTrackingArea alloc]
|
| + initWithRect:buttonRect
|
| + options:(NSTrackingMouseEnteredAndExited |
|
| + NSTrackingActiveInActiveApp)
|
| + owner:self
|
| + userInfo:nil]);
|
| + [[self controlView] addTrackingArea:trackingAreaButton_.get()];
|
| +
|
| + trackingAreaDropdown_.reset([[NSTrackingArea alloc]
|
| + initWithRect:dropdownRect
|
| + options:(NSTrackingMouseEnteredAndExited |
|
| + NSTrackingActiveInActiveApp)
|
| + owner:self
|
| + userInfo:nil]);
|
| + [[self controlView] addTrackingArea:trackingAreaDropdown_.get()];
|
| +}
|
| +
|
| +- (void)setShowsBorderOnlyWhileMouseInside:(BOOL)showOnly {
|
| + // Override to make sure it doesn't do anything if it's called accidentally.
|
| +}
|
| +
|
| +- (void)mouseEntered:(NSEvent*)theEvent {
|
| + mouseInsideCount_++;
|
| + if ([theEvent trackingArea] == trackingAreaButton_.get())
|
| + mousePosition_ = kDownloadItemMouseOverButtonPart;
|
| + else if ([theEvent trackingArea] == trackingAreaDropdown_.get())
|
| + mousePosition_ = kDownloadItemMouseOverButtonPart;
|
| + [[self controlView] setNeedsDisplay:YES];
|
| +}
|
| +
|
| +- (void)mouseExited:(NSEvent *)theEvent {
|
| + mouseInsideCount_--;
|
| + if (mouseInsideCount_ == 0)
|
| + mousePosition_ = kDownloadItemMouseOutside;
|
| + [[self controlView] setNeedsDisplay:YES];
|
| +}
|
| +
|
| +- (BOOL)isMouseInside {
|
| + return mousePosition_ != kDownloadItemMouseOutside;
|
| +}
|
| +
|
| +- (BOOL)isMouseOverButtonPart {
|
| + return mousePosition_ == kDownloadItemMouseOverButtonPart;
|
| +}
|
| +
|
| +- (BOOL)isButtonPartPressed {
|
| + return [self isHighlighted]
|
| + && mousePosition_ == kDownloadItemMouseOverButtonPart;
|
| +}
|
| +
|
| +- (BOOL)isMouseOverDropdownPart {
|
| + return mousePosition_ == kDownloadItemMouseOverButtonPart;
|
| +}
|
| +
|
| +- (BOOL)isDropdownPartPressed {
|
| + return [self isHighlighted]
|
| + && mousePosition_ == kDownloadItemMouseOverButtonPart;
|
| +}
|
| +
|
| +- (NSBezierPath*)leftRoundedPath:(CGFloat)radius inRect:(NSRect)rect {
|
| +
|
| + NSPoint topLeft = NSMakePoint(NSMinX(rect), NSMaxY(rect));
|
| + NSPoint topRight = NSMakePoint(NSMaxX(rect), NSMaxY(rect));
|
| + NSPoint bottomRight = NSMakePoint(NSMaxX(rect) , NSMinY(rect));
|
| +
|
| + NSBezierPath* path = [NSBezierPath bezierPath];
|
| + [path moveToPoint:topRight];
|
| + [path appendBezierPathWithArcFromPoint:topLeft
|
| + toPoint:rect.origin
|
| + radius:radius];
|
| + [path appendBezierPathWithArcFromPoint:rect.origin
|
| + toPoint:bottomRight
|
| + radius:radius];
|
| + [path lineToPoint:bottomRight];
|
| + return path;
|
| +}
|
| +
|
| +- (NSBezierPath*)rightRoundedPath:(CGFloat)radius inRect:(NSRect)rect {
|
| +
|
| + NSPoint topLeft = NSMakePoint(NSMinX(rect), NSMaxY(rect));
|
| + NSPoint topRight = NSMakePoint(NSMaxX(rect), NSMaxY(rect));
|
| + NSPoint bottomRight = NSMakePoint(NSMaxX(rect), NSMinY(rect));
|
| +
|
| + NSBezierPath* path = [NSBezierPath bezierPath];
|
| + [path moveToPoint:rect.origin];
|
| + [path appendBezierPathWithArcFromPoint:bottomRight
|
| + toPoint:topRight
|
| + radius:radius];
|
| + [path appendBezierPathWithArcFromPoint:topRight
|
| + toPoint:topLeft
|
| + radius:radius];
|
| + [path lineToPoint:topLeft];
|
| + [path closePath]; // Right path is closed
|
| + return path;
|
| +}
|
| +
|
| +- (void)elideTitle:(int)availableWidth {
|
| + NSFont* font = [self font];
|
| + gfx::Font font_chr =
|
| + gfx::Font::CreateFont(base::SysNSStringToWide([font fontName]),
|
| + [font pointSize]);
|
| +
|
| + NSString* titleString = base::SysWideToNSString(
|
| + ElideFilename(downloadPath_, font_chr, availableWidth));
|
| + [self setTitle:titleString];
|
| +}
|
| +
|
| +- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView*)controlView {
|
| +
|
| + // Constants from Cole. Will kConstan them once the feedback loop
|
| + // is complete.
|
| + NSRect drawFrame = NSInsetRect(cellFrame, 0.5, 0.5);
|
| + NSRect innerFrame = NSInsetRect(cellFrame, 1, 1);
|
| +
|
| + const float radius = 3.5;
|
| + NSWindow* window = [controlView window];
|
| + BOOL active = [window isKeyWindow] || [window isMainWindow];
|
| +
|
| + GTMTheme* theme = [controlView gtm_theme];
|
| +
|
| + NSRect buttonDrawRect, dropdownDrawRect;
|
| + NSDivideRect(drawFrame, &dropdownDrawRect, &buttonDrawRect,
|
| + kDropdownAreaWidth, NSMaxXEdge);
|
| +
|
| + NSRect buttonInnerRect, dropdownInnerRect;
|
| + NSDivideRect(innerFrame, &dropdownInnerRect, &buttonInnerRect,
|
| + kDropdownAreaWidth, NSMaxXEdge);
|
| +
|
| + NSBezierPath* buttonInnerPath = [self
|
| + leftRoundedPath:radius inRect:buttonDrawRect];
|
| + NSBezierPath* buttonOuterPath = [self
|
| + leftRoundedPath:(radius + 1)
|
| + inRect:NSInsetRect(buttonDrawRect, -1, -1)];
|
| +
|
| + NSBezierPath* dropdownInnerPath = [self
|
| + rightRoundedPath:radius inRect:dropdownDrawRect];
|
| + NSBezierPath* dropdownOuterPath = [self
|
| + rightRoundedPath:(radius + 1)
|
| + inRect:NSInsetRect(dropdownDrawRect, -1, -1)];
|
| +
|
| + // Stroke the borders and appropriate fill gradient. If we're borderless,
|
| + // the only time we want to draw the inner gradient is if we're highlighted.
|
| + if ([self isHighlighted] || [self isMouseInside]) {
|
| + [self drawBorderAndFillForTheme:theme
|
| + controlView:controlView
|
| + outerPath:buttonOuterPath
|
| + innerPath:buttonInnerPath
|
| + showHighlightGradient:[self isMouseOverButtonPart]
|
| + showClickedGradient:[self isButtonPartPressed]
|
| + active:active
|
| + cellFrame:cellFrame];
|
| +
|
| + [self drawBorderAndFillForTheme: theme
|
| + controlView:controlView
|
| + outerPath:dropdownOuterPath
|
| + innerPath:dropdownInnerPath
|
| + showHighlightGradient:[self isMouseOverDropdownPart]
|
| + showClickedGradient:[self isDropdownPartPressed]
|
| + active:active
|
| + cellFrame:cellFrame];
|
| + }
|
| +
|
| + [self drawInteriorWithFrame:innerFrame inView:controlView];
|
| +}
|
| +
|
| +- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView*)controlView {
|
| + // Draw title
|
| + [self elideTitle:cellFrame.size.width -
|
| + (kTextPosLeft + kTextPaddingRight + kDropdownAreaWidth)];
|
| +
|
| + NSColor* color = [self isButtonPartPressed]
|
| + ? [NSColor alternateSelectedControlTextColor] : [NSColor textColor];
|
| + NSString* primaryText = [self title];
|
| +
|
| + NSDictionary* primaryTextAttributes = [NSDictionary
|
| + dictionaryWithObjectsAndKeys:
|
| + color, NSForegroundColorAttributeName,
|
| + [self font], NSFontAttributeName,
|
| + nil];
|
| + NSPoint primaryPos = NSMakePoint(
|
| + cellFrame.origin.x + kTextPosLeft,
|
| + titleY_);
|
| +
|
| + [primaryText drawAtPoint:primaryPos withAttributes:primaryTextAttributes];
|
| +
|
| + // Draw secondary title, if any
|
| + if ([self secondaryTitle] != nil && statusAlpha_ > 0) {
|
| + NSString* secondaryText = [self secondaryTitle];
|
| + NSColor* secondaryColor = [color colorWithAlphaComponent:statusAlpha_];
|
| + NSDictionary* secondaryTextAttributes = [NSDictionary
|
| + dictionaryWithObjectsAndKeys:
|
| + secondaryColor, NSForegroundColorAttributeName,
|
| + [self secondaryFont], NSFontAttributeName,
|
| + nil];
|
| + NSPoint secondaryPos = NSMakePoint(
|
| + cellFrame.origin.x + kTextPosLeft,
|
| + kSecondaryTextPosTop);
|
| + [secondaryText drawAtPoint:secondaryPos
|
| + withAttributes:secondaryTextAttributes];
|
| + }
|
| +
|
| + // Draw icon
|
| + NSRect imageRect = NSZeroRect;
|
| + imageRect.size = [[self image] size];
|
| + [[self image] setFlipped:[controlView isFlipped]];
|
| + [[self image] drawInRect:[self imageRectForBounds:cellFrame]
|
| + fromRect:imageRect
|
| + operation:NSCompositeSourceOver
|
| + fraction:[self isEnabled] ? 1.0 : 0.5];
|
| +
|
| + // Popup arrow. Put center of mass of the arrow in the center of the
|
| + // dropdown area.
|
| + CGFloat cx = NSMaxX(cellFrame) - kDropdownAreaWidth/2;
|
| + CGFloat cy = NSMidY(cellFrame);
|
| + NSPoint p1 = NSMakePoint(cx - kDropdownArrowWidth/2,
|
| + cy - kDropdownArrowHeight/3);
|
| + NSPoint p2 = NSMakePoint(cx + kDropdownArrowWidth/2,
|
| + cy - kDropdownArrowHeight/3);
|
| + NSPoint p3 = NSMakePoint(cx, cy + kDropdownArrowHeight*2/3);
|
| + NSBezierPath *triangle = [NSBezierPath bezierPath];
|
| + [triangle moveToPoint:p1];
|
| + [triangle lineToPoint:p2];
|
| + [triangle lineToPoint:p3];
|
| + [triangle closePath];
|
| +
|
| + NSColor* fill = [self isDropdownPartPressed]
|
| + ? [NSColor alternateSelectedControlTextColor] : [NSColor textColor];
|
| + [fill setFill];
|
| + [triangle fill];
|
| +}
|
| +
|
| +- (NSRect)imageRectForBounds:(NSRect)cellFrame {
|
| + return NSMakeRect(
|
| + kImagePaddingLeft, kImagePaddingTop, kImageWidth, kImageHeight);
|
| +}
|
| +
|
| +- (void)hideSecondaryTitle {
|
| + if (isStatusTextVisible_) {
|
| + // No core animation -- text in CA layers is not subpixel antialiased :-/
|
| + hideStatusAnimation_.reset([[HideSecondaryTitleAnimation alloc]
|
| + initWithDownloadItemCell:self]);
|
| + [hideStatusAnimation_.get() setDelegate:self];
|
| + [hideStatusAnimation_.get() startAnimation];
|
| + } else {
|
| + // If the download is done so quickly that the status line is never visible,
|
| + // don't show an animation
|
| + [self animationProgressed:1.0];
|
| + }
|
| +}
|
| +
|
| +- (void)animationProgressed:(NSAnimationProgress)progress {
|
| + titleY_ = progress*kPrimaryTextOnlyPosTop + (1 - progress)*kPrimaryTextPosTop;
|
| + statusAlpha_ = 1 - progress;
|
| + [[self controlView] setNeedsDisplay:YES];
|
| +}
|
| +
|
| +- (void)animationDidEnd:(NSAnimation *)animation {
|
| + hideStatusAnimation_.reset();
|
| +}
|
| +
|
| +@end
|
| +
|
| +@implementation HideSecondaryTitleAnimation
|
| +
|
| +- (id)initWithDownloadItemCell:(DownloadItemCell*)cell {
|
| + if ((self = [super initWithDuration:kHideStatusDuration
|
| + animationCurve:NSAnimationEaseIn])) {
|
| + cell_ = cell;
|
| + [self setAnimationBlockingMode:NSAnimationNonblocking];
|
| + }
|
| + return self;
|
| +}
|
| +
|
| +- (void)setCurrentProgress:(NSAnimationProgress)progress {
|
| + [super setCurrentProgress:progress];
|
| + [cell_ animationProgressed:progress];
|
| +}
|
| +
|
| +@end
|
|
|
| Property changes on: chrome/browser/cocoa/download_item_cell.mm
|
| ___________________________________________________________________
|
| Added: svn:eol-style
|
| + LF
|
|
|
|
|