• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2014 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/ui/cocoa/profiles/avatar_menu_bubble_controller.h"
6
7#include "base/mac/bundle_locations.h"
8#include "base/mac/mac_util.h"
9#include "base/strings/sys_string_conversions.h"
10#include "chrome/browser/browser_process.h"
11#include "chrome/browser/profiles/avatar_menu.h"
12#include "chrome/browser/profiles/profile_info_cache.h"
13#include "chrome/browser/profiles/profile_manager.h"
14#include "chrome/browser/profiles/profile_metrics.h"
15#include "chrome/browser/ui/browser.h"
16#include "chrome/browser/ui/browser_window.h"
17#import "chrome/browser/ui/cocoa/info_bubble_view.h"
18#import "chrome/browser/ui/cocoa/info_bubble_window.h"
19#include "grit/generated_resources.h"
20#include "grit/theme_resources.h"
21#import "third_party/google_toolbox_for_mac/src/AppKit/GTMUILocalizerAndLayoutTweaker.h"
22#import "ui/base/cocoa/controls/hyperlink_button_cell.h"
23#import "ui/base/cocoa/cocoa_base_utils.h"
24#include "ui/base/l10n/l10n_util_mac.h"
25#include "ui/base/resource/resource_bundle.h"
26#include "ui/gfx/image/image.h"
27
28@interface AvatarMenuBubbleController (Private)
29- (AvatarMenu*)menu;
30- (NSView*)configureSupervisedUserInformation:(CGFloat)width;
31- (NSButton*)configureNewUserButton:(CGFloat)yOffset
32                  updateWidthAdjust:(CGFloat*)widthAdjust;
33- (NSButton*)configureSwitchUserButton:(CGFloat)yOffset
34                     updateWidthAdjust:(CGFloat*)widthAdjust;
35- (AvatarMenuItemController*)initAvatarItem:(int)itemIndex
36                          updateWidthAdjust:(CGFloat*)widthAdjust
37                                 setYOffset:(CGFloat)yOffset;
38- (void)setWindowFrame:(CGFloat)yOffset widthAdjust:(CGFloat)width;
39- (void)initMenuContents;
40- (void)initSupervisedUserContents;
41- (void)keyDown:(NSEvent*)theEvent;
42- (void)moveDown:(id)sender;
43- (void)moveUp:(id)sender;
44- (void)insertNewline:(id)sender;
45- (void)highlightNextItemByDelta:(NSInteger)delta;
46- (void)highlightItem:(AvatarMenuItemController*)newItem;
47@end
48
49namespace {
50
51// Constants taken from the Windows/Views implementation at:
52//    chrome/browser/ui/views/avatar_menu_bubble_view.cc
53const CGFloat kBubbleMinWidth = 175;
54const CGFloat kBubbleMaxWidth = 800;
55const CGFloat kMaxItemTextWidth = 200;
56
57// Values derived from the XIB.
58const CGFloat kVerticalSpacing = 10.0;
59const CGFloat kLinkSpacing = 15.0;
60const CGFloat kLabelInset = 49.0;
61
62// The offset of the supervised user information label and the "switch user"
63// link.
64const CGFloat kSupervisedUserSpacing = 26.0;
65
66}  // namespace
67
68@implementation AvatarMenuBubbleController
69
70- (id)initWithBrowser:(Browser*)parentBrowser
71           anchoredAt:(NSPoint)point {
72
73  // Pass in a NULL observer. Rebuilding while the bubble is open will cause it
74  // to be positioned incorrectly. Since the bubble will be dismissed on losing
75  // key status, it's impossible for the user to edit the information in a
76  // meaningful way such that it would need to be redrawn.
77  AvatarMenu* menu = new AvatarMenu(
78      &g_browser_process->profile_manager()->GetProfileInfoCache(),
79      NULL, parentBrowser);
80  menu->RebuildMenu();
81
82  if ((self = [self initWithMenu:menu
83                     parentWindow:parentBrowser->window()->GetNativeWindow()
84                       anchoredAt:point])) {
85  }
86  return self;
87}
88
89- (IBAction)newProfile:(id)sender {
90  menu_->AddNewProfile(ProfileMetrics::ADD_NEW_USER_ICON);
91}
92
93- (IBAction)switchToProfile:(id)sender {
94  // Check the event flags to see if a new window should be crated.
95  bool always_create = ui::WindowOpenDispositionFromNSEvent(
96      [NSApp currentEvent]) == NEW_WINDOW;
97  menu_->SwitchToProfile([sender menuIndex], always_create,
98                         ProfileMetrics::SWITCH_PROFILE_ICON);
99}
100
101- (IBAction)editProfile:(id)sender {
102  menu_->EditProfile([sender menuIndex]);
103}
104
105- (IBAction)switchProfile:(id)sender {
106  expanded_ = YES;
107  [self performLayout];
108}
109
110// Private /////////////////////////////////////////////////////////////////////
111
112- (id)initWithMenu:(AvatarMenu*)menu
113       parentWindow:(NSWindow*)parent
114         anchoredAt:(NSPoint)point {
115  // Use an arbitrary height because it will reflect the size of the content.
116  NSRect contentRect = NSMakeRect(0, 0, kBubbleMinWidth, 150);
117  // Create an empty window into which content is placed.
118  base::scoped_nsobject<InfoBubbleWindow> window(
119      [[InfoBubbleWindow alloc] initWithContentRect:contentRect
120                                          styleMask:NSBorderlessWindowMask
121                                            backing:NSBackingStoreBuffered
122                                              defer:NO]);
123  if ((self = [super initWithWindow:window
124                       parentWindow:parent
125                         anchoredAt:point])) {
126    menu_.reset(menu);
127
128    [window accessibilitySetOverrideValue:
129        l10n_util::GetNSString(IDS_PROFILES_BUBBLE_ACCESSIBLE_NAME)
130                             forAttribute:NSAccessibilityTitleAttribute];
131    [window accessibilitySetOverrideValue:
132        l10n_util::GetNSString(IDS_PROFILES_BUBBLE_ACCESSIBLE_DESCRIPTION)
133                             forAttribute:NSAccessibilityHelpAttribute];
134
135    [[self bubble] setArrowLocation:info_bubble::kTopRight];
136    [self performLayout];
137  }
138  return self;
139}
140
141- (AvatarMenuItemController*)initAvatarItem:(int)itemIndex
142                          updateWidthAdjust:(CGFloat*)widthAdjust
143                                 setYOffset:(CGFloat)yOffset {
144  ResourceBundle& rb = ResourceBundle::GetSharedInstance();
145  const AvatarMenu::Item& item = menu_->GetItemAt(itemIndex);
146  // Create the item view controller. Autorelease it because it will be owned
147  // by the |items_| array.
148  AvatarMenuItemController* itemView =
149      [[[AvatarMenuItemController alloc] initWithMenuIndex:itemIndex
150                                             menuController:self] autorelease];
151  itemView.iconView.image = item.icon.ToNSImage();
152
153  // Adjust the name field to fit the string. If it overflows, record by how
154  // much the window needs to grow to accomodate the new size of the field.
155  NSTextField* nameField = itemView.nameField;
156  nameField.stringValue = base::SysUTF16ToNSString(item.name);
157  NSSize delta = [GTMUILocalizerAndLayoutTweaker sizeToFitView:nameField];
158  if (NSWidth([nameField frame]) > kMaxItemTextWidth) {
159    delta.width -= (NSWidth([nameField frame]) - kMaxItemTextWidth);
160    NSRect frame = [nameField frame];
161    frame.size.width = kMaxItemTextWidth;
162    [nameField setFrame:frame];
163  }
164  *widthAdjust = std::max(*widthAdjust, delta.width);
165
166  // Repeat for the sync state/email.
167  NSTextField* emailField = itemView.emailField;
168  emailField.stringValue = base::SysUTF16ToNSString(item.sync_state);
169  delta = [GTMUILocalizerAndLayoutTweaker sizeToFitView:emailField];
170  if (NSWidth([emailField frame]) > kMaxItemTextWidth) {
171    delta.width -= (NSWidth([emailField frame]) - kMaxItemTextWidth);
172    NSRect frame = [emailField frame];
173    frame.size.width = kMaxItemTextWidth;
174    [emailField setFrame:frame];
175  }
176  *widthAdjust = std::max(*widthAdjust, delta.width);
177
178  if (!item.active) {
179    // In the inactive case, hide additional UI.
180    [itemView.activeView setHidden:YES];
181    [itemView.editButton setHidden:YES];
182  } else {
183    // Otherwise, set up the edit button and its three interaction states.
184    itemView.activeView.image =
185        rb.GetImageNamed(IDR_PROFILE_SELECTED).ToNSImage();
186  }
187
188  // Add the item to the content view.
189  [[itemView view] setFrameOrigin:NSMakePoint(0, yOffset)];
190
191  // Keep track of the view controller.
192  [items_ addObject:itemView];
193  return itemView;
194}
195
196- (void)setWindowFrame:(CGFloat)yOffset widthAdjust:(CGFloat)width {
197  // Set the window frame, clamping the width at a sensible max.
198  NSRect frame = [[self window] frame];
199  // Adjust the origin after we have switched from the supervised user menu to
200  // the regular menu.
201  CGFloat newWidth = std::min(kBubbleMinWidth + width, kBubbleMaxWidth);
202  if (expanded_) {
203    frame.origin.x += frame.size.width - newWidth;
204    frame.origin.y += frame.size.height - yOffset;
205  }
206  frame.size.height = yOffset;
207  frame.size.width = newWidth;
208  [[self window] setFrame:frame display:YES];
209}
210
211- (void)initMenuContents {
212  NSView* contentView = [[self window] contentView];
213
214  // |yOffset| is the next position at which to draw in contentView coordinates.
215  // Use a little more vertical spacing because the items have padding built-
216  // into the xib, and this gives a little more space to visually match.
217  CGFloat yOffset = kLinkSpacing;
218  CGFloat widthAdjust = 0;
219
220  if (menu_->ShouldShowAddNewProfileLink()) {
221    // Since drawing happens bottom-up, start with the "New User" link.
222    NSButton* newButton =
223        [self configureNewUserButton:yOffset updateWidthAdjust:&widthAdjust];
224    [contentView addSubview:newButton];
225    yOffset += NSHeight([newButton frame]) + kVerticalSpacing;
226
227    NSBox* separator = [self horizontalSeparatorWithFrame:
228        NSMakeRect(10, yOffset, NSWidth([contentView frame]) - 20, 0)];
229    [separator setAutoresizingMask:NSViewWidthSizable];
230    [contentView addSubview:separator];
231
232    yOffset += NSHeight([separator frame]);
233  } else {
234    yOffset = 7;
235  }
236
237  // Loop over the profiles in reverse, constructing the menu items.
238  for (int i = menu_->GetNumberOfItems() - 1; i >= 0; --i) {
239    AvatarMenuItemController* itemView = [self initAvatarItem:i
240                                            updateWidthAdjust:&widthAdjust
241                                                   setYOffset:yOffset];
242    [contentView addSubview:[itemView view]];
243    yOffset += NSHeight([[itemView view] frame]);
244  }
245
246  yOffset += kVerticalSpacing * 1.5;
247  [self setWindowFrame:yOffset widthAdjust:widthAdjust];
248}
249
250- (void)initSupervisedUserContents {
251  NSView* contentView = [[self window] contentView];
252
253  // |yOffset| is the next position at which to draw in contentView coordinates.
254  // Use a little more vertical spacing because the items have padding built-
255  // into the xib, and this gives a little more space to visually match.
256  CGFloat yOffset = kLinkSpacing;
257  CGFloat widthAdjust = 0;
258
259  // Since drawing happens bottom-up, start with the "Switch User" link.
260  NSButton* newButton =
261      [self configureSwitchUserButton:yOffset updateWidthAdjust:&widthAdjust];
262  [contentView addSubview:newButton];
263  yOffset += NSHeight([newButton frame]) + kVerticalSpacing;
264
265  NSBox* separator = [self horizontalSeparatorWithFrame:
266      NSMakeRect(10, yOffset, NSWidth([contentView frame]) - 20, 0)];
267  [separator setAutoresizingMask:NSViewWidthSizable];
268  [contentView addSubview:separator];
269
270  yOffset += NSHeight([separator frame]) + kVerticalSpacing;
271
272  // First init the active profile in order to determine the required width. We
273  // will have to adjust its frame later after adding general information about
274  // supervised users.
275  AvatarMenuItemController* itemView =
276      [self initAvatarItem:menu_->GetActiveProfileIndex()
277          updateWidthAdjust:&widthAdjust
278                 setYOffset:yOffset];
279
280  // Don't increase the width too much (the total size should be at most
281  // |kBubbleMaxWidth|).
282  widthAdjust = std::min(widthAdjust, kBubbleMaxWidth - kBubbleMinWidth);
283  CGFloat newWidth = kBubbleMinWidth + widthAdjust;
284
285  // Add general information about supervised users.
286  NSView* info = [self configureSupervisedUserInformation:newWidth];
287  [info setFrameOrigin:NSMakePoint(0, yOffset)];
288  [contentView addSubview:info];
289  yOffset += NSHeight([info frame]) + kVerticalSpacing;
290
291  separator = [self horizontalSeparatorWithFrame:
292      NSMakeRect(10, yOffset, NSWidth([contentView frame]) - 20, 0)];
293  [separator setAutoresizingMask:NSViewWidthSizable];
294  [contentView addSubview:separator];
295
296  yOffset += NSHeight([separator frame]);
297
298  // Now update the frame of the active profile and add it.
299  NSRect frame = [[itemView view] frame];
300  frame.origin.y = yOffset;
301  [[itemView view] setFrame:frame];
302  [contentView addSubview:[itemView view]];
303
304  yOffset += NSHeight(frame) + kVerticalSpacing * 1.5;
305  [self setWindowFrame:yOffset widthAdjust:widthAdjust];
306}
307
308- (void)performLayout {
309  NSView* contentView = [[self window] contentView];
310
311  // Reset the array of controllers and remove all the views.
312  items_.reset([[NSMutableArray alloc] init]);
313  [contentView setSubviews:[NSArray array]];
314
315  if (menu_->GetSupervisedUserInformation().empty() || expanded_)
316    [self initMenuContents];
317  else
318    [self initSupervisedUserContents];
319}
320
321- (NSView*)configureSupervisedUserInformation:(CGFloat)width {
322  base::scoped_nsobject<NSView> container(
323      [[NSView alloc] initWithFrame:NSZeroRect]);
324
325  // Add the limited user icon on the left side of the information TextView.
326  base::scoped_nsobject<NSImageView> iconView(
327      [[NSImageView alloc] initWithFrame:NSMakeRect(5, 0, 16, 16)]);
328  [iconView setImage:menu_->GetSupervisedUserIcon().ToNSImage()];
329  [container addSubview:iconView];
330
331  NSString* info =
332      base::SysUTF16ToNSString(menu_->GetSupervisedUserInformation());
333  NSDictionary* attributes =
334      @{ NSFontAttributeName : [NSFont labelFontOfSize:12] };
335  base::scoped_nsobject<NSAttributedString> attrString(
336      [[NSAttributedString alloc] initWithString:info attributes:attributes]);
337  base::scoped_nsobject<NSTextView> label(
338      [[NSTextView alloc] initWithFrame:NSMakeRect(
339          kSupervisedUserSpacing, 0, width - kSupervisedUserSpacing - 5, 0)]);
340  [[label textStorage] setAttributedString:attrString];
341  [label setHorizontallyResizable:NO];
342  [label setEditable:NO];
343  [label sizeToFit];
344  [container addSubview:label];
345  [container setFrameSize:NSMakeSize(width, NSHeight([label frame]))];
346
347  // Reposition the limited user icon so that it is on top.
348  [iconView setFrameOrigin:NSMakePoint(5, NSHeight([label frame]) - 16)];
349  return container.autorelease();
350}
351
352- (NSButton*)configureNewUserButton:(CGFloat)yOffset
353                  updateWidthAdjust:(CGFloat*)widthAdjust {
354  base::scoped_nsobject<NSButton> newButton([[NSButton alloc] initWithFrame:
355          NSMakeRect(kLabelInset, yOffset, kBubbleMinWidth - kLabelInset, 16)]);
356  base::scoped_nsobject<HyperlinkButtonCell> buttonCell(
357      [[HyperlinkButtonCell alloc] initTextCell:
358              l10n_util::GetNSString(IDS_PROFILES_CREATE_NEW_PROFILE_LINK)]);
359  [newButton setCell:buttonCell.get()];
360  [newButton setFont:[NSFont labelFontOfSize:12.0]];
361  [newButton setBezelStyle:NSRegularSquareBezelStyle];
362  [newButton setTarget:self];
363  [newButton setAction:@selector(newProfile:)];
364  NSSize delta = [GTMUILocalizerAndLayoutTweaker sizeToFitView:newButton];
365  if (delta.width > 0)
366    *widthAdjust = std::max(*widthAdjust, delta.width);
367  return newButton.autorelease();
368}
369
370- (NSButton*)configureSwitchUserButton:(CGFloat)yOffset
371                     updateWidthAdjust:(CGFloat*)widthAdjust {
372  base::scoped_nsobject<NSButton> newButton(
373      [[NSButton alloc] initWithFrame:NSMakeRect(
374          kSupervisedUserSpacing, yOffset, kBubbleMinWidth - kLabelInset, 16)]);
375  base::scoped_nsobject<HyperlinkButtonCell> buttonCell(
376      [[HyperlinkButtonCell alloc] initTextCell:
377              l10n_util::GetNSString(IDS_PROFILES_SWITCH_PROFILE_LINK)]);
378  [newButton setCell:buttonCell.get()];
379  [newButton setFont:[NSFont labelFontOfSize:12.0]];
380  [newButton setBezelStyle:NSRegularSquareBezelStyle];
381  [newButton setTarget:self];
382  [newButton setAction:@selector(switchProfile:)];
383  NSSize delta = [GTMUILocalizerAndLayoutTweaker sizeToFitView:newButton];
384  if (delta.width > 0)
385    *widthAdjust = std::max(*widthAdjust, delta.width);
386  return newButton.autorelease();
387}
388
389- (NSMutableArray*)items {
390  return items_.get();
391}
392
393- (void)keyDown:(NSEvent*)theEvent {
394  [self interpretKeyEvents:[NSArray arrayWithObject:theEvent]];
395}
396
397- (void)moveDown:(id)sender {
398  [self highlightNextItemByDelta:-1];
399}
400
401- (void)moveUp:(id)sender {
402  [self highlightNextItemByDelta:1];
403}
404
405- (void)insertNewline:(id)sender {
406  for (AvatarMenuItemController* item in items_.get()) {
407    if ([item isHighlighted]) {
408      [self switchToProfile:item];
409      return;
410    }
411  }
412}
413
414- (void)highlightNextItemByDelta:(NSInteger)delta {
415  NSUInteger count = [items_ count];
416  if (count == 0)
417    return;
418
419  NSInteger old_index = -1;
420  for (NSUInteger i = 0; i < count; ++i) {
421    if ([[items_ objectAtIndex:i] isHighlighted]) {
422      old_index = i;
423      break;
424    }
425  }
426
427  NSInteger new_index;
428  // If nothing is selected then start at the top if we're going down and start
429  // at the bottom if we're going up.
430  if (old_index == -1)
431    new_index = delta < 0 ? (count - 1) : 0;
432  else
433    new_index = old_index + delta;
434
435  // Cap the index. We don't wrap around to match the behavior of Mac menus.
436  new_index =
437      std::min(std::max(static_cast<NSInteger>(0), new_index),
438               static_cast<NSInteger>(count - 1));
439
440  [self highlightItem:[items_ objectAtIndex:new_index]];
441}
442
443- (void)highlightItem:(AvatarMenuItemController*)newItem {
444  AvatarMenuItemController* oldItem = nil;
445  for (AvatarMenuItemController* item in items_.get()) {
446    if ([item isHighlighted]) {
447      oldItem = item;
448      break;
449    }
450  }
451
452  if (oldItem == newItem)
453    return;
454
455  [oldItem setIsHighlighted:NO];
456  [newItem setIsHighlighted:YES];
457}
458
459
460@end
461
462// Menu Item Controller ////////////////////////////////////////////////////////
463
464@interface AvatarMenuItemController (Private)
465- (void)animateFromView:(NSView*)outView toView:(NSView*)inView;
466@end
467
468@implementation AvatarMenuItemController
469
470@synthesize menuIndex = menuIndex_;
471@synthesize isHighlighted = isHighlighted_;
472@synthesize iconView = iconView_;
473@synthesize activeView = activeView_;
474@synthesize nameField = nameField_;
475@synthesize emailField = emailField_;
476@synthesize editButton = editButton_;
477
478- (id)initWithMenuIndex:(size_t)menuIndex
479          menuController:(AvatarMenuBubbleController*)controller {
480  if ((self = [super initWithNibName:@"AvatarMenuItem"
481                              bundle:base::mac::FrameworkBundle()])) {
482    menuIndex_ = menuIndex;
483    controller_ = controller;
484    [self loadView];
485    [nameField_ setAutoresizingMask:NSViewNotSizable];
486    [[nameField_ cell] setLineBreakMode:NSLineBreakByTruncatingTail];
487    [emailField_ setAutoresizingMask:NSViewNotSizable];
488    [[emailField_ cell] setLineBreakMode:NSLineBreakByTruncatingTail];
489  }
490  return self;
491}
492
493- (void)dealloc {
494  static_cast<AvatarMenuItemView*>(self.view).viewController = nil;
495  [linkAnimation_ stopAnimation];
496  [linkAnimation_ setDelegate:nil];
497  [super dealloc];
498}
499
500- (void)awakeFromNib {
501  [GTMUILocalizerAndLayoutTweaker sizeToFitView:self.editButton];
502  self.editButton.hidden = YES;
503}
504
505- (IBAction)switchToProfile:(id)sender {
506  [controller_ switchToProfile:self];
507}
508
509- (IBAction)editProfile:(id)sender {
510  [controller_ editProfile:self];
511}
512
513- (void)highlightForEventType:(NSEventType)type {
514  switch (type) {
515    case NSMouseEntered:
516      [controller_ highlightItem:self];
517      break;
518
519    case NSMouseExited:
520      [controller_ highlightItem:nil];
521      break;
522
523    default:
524      NOTREACHED();
525  };
526}
527
528- (void)setIsHighlighted:(BOOL)isHighlighted {
529  if (isHighlighted_ == isHighlighted)
530    return;
531
532  isHighlighted_ = isHighlighted;
533  [[self view] setNeedsDisplay:YES];
534
535  // Cancel any running animation.
536  if (linkAnimation_.get()) {
537    [NSObject cancelPreviousPerformRequestsWithTarget:linkAnimation_
538                                             selector:@selector(startAnimation)
539                                               object:nil];
540  }
541
542  // Fade the edit link in or out only if this is the active view.
543  if (self.activeView.isHidden)
544    return;
545
546  if (isHighlighted_) {
547    [self animateFromView:self.emailField toView:self.editButton];
548  } else {
549    // If the edit button is visible or the animation to make it so is
550    // running, stop the animation and fade it back to the email. If not, then
551    // don't run an animation to prevent flickering.
552    if (!self.editButton.isHidden || [linkAnimation_ isAnimating]) {
553      [linkAnimation_ stopAnimation];
554      linkAnimation_.reset();
555      [self animateFromView:self.editButton toView:self.emailField];
556    }
557  }
558}
559
560- (void)animateFromView:(NSView*)outView toView:(NSView*)inView {
561  const NSTimeInterval kAnimationDuration = 0.175;
562
563  NSDictionary* outDict = [NSDictionary dictionaryWithObjectsAndKeys:
564      outView, NSViewAnimationTargetKey,
565      NSViewAnimationFadeOutEffect, NSViewAnimationEffectKey,
566      nil
567  ];
568  NSDictionary* inDict = [NSDictionary dictionaryWithObjectsAndKeys:
569      inView, NSViewAnimationTargetKey,
570      NSViewAnimationFadeInEffect, NSViewAnimationEffectKey,
571      nil
572  ];
573
574  linkAnimation_.reset([[NSViewAnimation alloc] initWithViewAnimations:
575      [NSArray arrayWithObjects:outDict, inDict, nil]]);
576  [linkAnimation_ setDelegate:self];
577  [linkAnimation_ setDuration:kAnimationDuration];
578
579  [self willStartAnimation:linkAnimation_];
580
581  [linkAnimation_ performSelector:@selector(startAnimation)
582                       withObject:nil
583                       afterDelay:0.2];
584}
585
586- (void)willStartAnimation:(NSAnimation*)animation {
587}
588
589- (void)animationDidEnd:(NSAnimation*)animation {
590  if (animation == linkAnimation_.get())
591    linkAnimation_.reset();
592}
593
594- (void)animationDidStop:(NSAnimation*)animation {
595  if (animation == linkAnimation_.get())
596    linkAnimation_.reset();
597}
598
599@end
600
601// Profile Switch Button ///////////////////////////////////////////////////////
602
603@implementation AvatarMenuItemView
604
605@synthesize viewController = viewController_;
606
607- (void)awakeFromNib {
608  [self updateTrackingAreas];
609}
610
611- (void)updateTrackingAreas {
612  if (trackingArea_.get())
613    [self removeTrackingArea:trackingArea_.get()];
614
615  trackingArea_.reset(
616      [[CrTrackingArea alloc] initWithRect:[self bounds]
617                                   options:NSTrackingMouseEnteredAndExited |
618                                           NSTrackingActiveInKeyWindow
619                                     owner:self
620                                  userInfo:nil]);
621  [self addTrackingArea:trackingArea_.get()];
622
623  [super updateTrackingAreas];
624}
625
626- (void)mouseEntered:(id)sender {
627  [viewController_ highlightForEventType:[[NSApp currentEvent] type]];
628  [self setNeedsDisplay:YES];
629}
630
631- (void)mouseExited:(id)sender {
632  [viewController_ highlightForEventType:[[NSApp currentEvent] type]];
633  [self setNeedsDisplay:YES];
634}
635
636- (void)mouseUp:(id)sender {
637  [viewController_ switchToProfile:self];
638}
639
640- (void)drawRect:(NSRect)dirtyRect {
641  NSColor* backgroundColor = nil;
642  if ([viewController_ isHighlighted]) {
643    backgroundColor = [NSColor colorWithCalibratedRed:223.0/255
644                                                green:238.0/255
645                                                 blue:246.0/255
646                                                alpha:1.0];
647  } else {
648    backgroundColor = [NSColor clearColor];
649  }
650
651  [backgroundColor set];
652  [NSBezierPath fillRect:[self bounds]];
653}
654
655// Make sure the element is focusable for accessibility.
656- (BOOL)canBecomeKeyView {
657  return YES;
658}
659
660- (BOOL)accessibilityIsIgnored {
661  return NO;
662}
663
664- (NSArray*)accessibilityAttributeNames {
665  NSMutableArray* attributes =
666      [[super accessibilityAttributeNames] mutableCopy];
667  [attributes addObject:NSAccessibilityTitleAttribute];
668  [attributes addObject:NSAccessibilityEnabledAttribute];
669
670  return [attributes autorelease];
671}
672
673- (NSArray*)accessibilityActionNames {
674  NSArray* parentActions = [super accessibilityActionNames];
675  return [parentActions arrayByAddingObject:NSAccessibilityPressAction];
676}
677
678- (id)accessibilityAttributeValue:(NSString*)attribute {
679  if ([attribute isEqual:NSAccessibilityRoleAttribute])
680    return NSAccessibilityButtonRole;
681
682  if ([attribute isEqual:NSAccessibilityRoleDescriptionAttribute])
683    return NSAccessibilityRoleDescription(NSAccessibilityButtonRole, nil);
684
685  if ([attribute isEqual:NSAccessibilityTitleAttribute]) {
686    return l10n_util::GetNSStringF(
687        IDS_PROFILES_SWITCH_TO_PROFILE_ACCESSIBLE_NAME,
688        base::SysNSStringToUTF16(self.viewController.nameField.stringValue));
689  }
690
691  if ([attribute isEqual:NSAccessibilityEnabledAttribute])
692    return [NSNumber numberWithBool:YES];
693
694  return [super accessibilityAttributeValue:attribute];
695}
696
697- (void)accessibilityPerformAction:(NSString*)action {
698  if ([action isEqual:NSAccessibilityPressAction]) {
699    [viewController_ switchToProfile:self];
700    return;
701  }
702
703  [super accessibilityPerformAction:action];
704}
705
706@end
707
708////////////////////////////////////////////////////////////////////////////////
709
710@implementation AccessibilityIgnoredImageCell
711- (BOOL)accessibilityIsIgnored {
712  return YES;
713}
714@end
715
716@implementation AccessibilityIgnoredTextFieldCell
717- (BOOL)accessibilityIsIgnored {
718  return YES;
719}
720@end
721