Check USB device path access when prompting users to select a device.
[chromium-blink-merge.git] / chrome / browser / ui / cocoa / profiles / avatar_menu_bubble_controller.mm
blob3d0f39d8a0632868b3fffb2c4229d5abaf4a4815
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.
5 #import "chrome/browser/ui/cocoa/profiles/avatar_menu_bubble_controller.h"
7 #include "base/mac/bundle_locations.h"
8 #include "base/mac/sdk_forward_declarations.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 "chrome/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/cocoa_base_utils.h"
23 #import "ui/base/cocoa/controls/hyperlink_button_cell.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"
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
49 namespace {
51 // Constants taken from the Windows/Views implementation at:
52 //    chrome/browser/ui/views/avatar_menu_bubble_view.cc
53 const CGFloat kBubbleMinWidth = 175;
54 const CGFloat kBubbleMaxWidth = 800;
55 const CGFloat kMaxItemTextWidth = 200;
57 // Values derived from the XIB.
58 const CGFloat kVerticalSpacing = 10.0;
59 const CGFloat kLinkSpacing = 15.0;
60 const CGFloat kLabelInset = 49.0;
62 // The offset of the supervised user information label and the "switch user"
63 // link.
64 const CGFloat kSupervisedUserSpacing = 26.0;
66 }  // namespace
68 @implementation AvatarMenuBubbleController
70 - (id)initWithBrowser:(Browser*)parentBrowser
71            anchoredAt:(NSPoint)point {
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();
82   if ((self = [self initWithMenu:menu
83                      parentWindow:parentBrowser->window()->GetNativeWindow()
84                        anchoredAt:point])) {
85   }
86   return self;
89 - (IBAction)newProfile:(id)sender {
90   menu_->AddNewProfile(ProfileMetrics::ADD_NEW_USER_ICON);
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);
101 - (IBAction)editProfile:(id)sender {
102   menu_->EditProfile([sender menuIndex]);
105 - (IBAction)switchProfile:(id)sender {
106   expanded_ = YES;
107   [self performLayout];
110 // Private /////////////////////////////////////////////////////////////////////
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);
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];
135     [[self bubble] setArrowLocation:info_bubble::kTopRight];
136     [self performLayout];
137   }
138   return self;
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();
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     if ([nameField respondsToSelector:@selector(setAllowsExpansionToolTips:)])
164       [nameField setAllowsExpansionToolTips:YES];
165   }
166   *widthAdjust = std::max(*widthAdjust, delta.width);
168   // Repeat for the sync state/email.
169   NSTextField* emailField = itemView.emailField;
170   emailField.stringValue = base::SysUTF16ToNSString(item.sync_state);
171   delta = [GTMUILocalizerAndLayoutTweaker sizeToFitView:emailField];
172   if (NSWidth([emailField frame]) > kMaxItemTextWidth) {
173     delta.width -= (NSWidth([emailField frame]) - kMaxItemTextWidth);
174     NSRect frame = [emailField frame];
175     frame.size.width = kMaxItemTextWidth;
176     [emailField setFrame:frame];
177   }
178   *widthAdjust = std::max(*widthAdjust, delta.width);
180   if (!item.active) {
181     // In the inactive case, hide additional UI.
182     [itemView.activeView setHidden:YES];
183     [itemView.editButton setHidden:YES];
184   } else {
185     // Otherwise, set up the edit button and its three interaction states.
186     itemView.activeView.image =
187         rb.GetImageNamed(IDR_PROFILE_SELECTED).ToNSImage();
188   }
190   // Add the item to the content view.
191   [[itemView view] setFrameOrigin:NSMakePoint(0, yOffset)];
193   // Keep track of the view controller.
194   [items_ addObject:itemView];
195   return itemView;
198 - (void)setWindowFrame:(CGFloat)yOffset widthAdjust:(CGFloat)width {
199   // Set the window frame, clamping the width at a sensible max.
200   NSRect frame = [[self window] frame];
201   // Adjust the origin after we have switched from the supervised user menu to
202   // the regular menu.
203   CGFloat newWidth = std::min(kBubbleMinWidth + width, kBubbleMaxWidth);
204   if (expanded_) {
205     frame.origin.x += frame.size.width - newWidth;
206     frame.origin.y += frame.size.height - yOffset;
207   }
208   frame.size.height = yOffset;
209   frame.size.width = newWidth;
210   [[self window] setFrame:frame display:YES];
213 - (void)initMenuContents {
214   NSView* contentView = [[self window] contentView];
216   // |yOffset| is the next position at which to draw in contentView coordinates.
217   // Use a little more vertical spacing because the items have padding built-
218   // into the xib, and this gives a little more space to visually match.
219   CGFloat yOffset = kLinkSpacing;
220   CGFloat widthAdjust = 0;
222   if (menu_->ShouldShowAddNewProfileLink()) {
223     // Since drawing happens bottom-up, start with the "New User" link.
224     NSButton* newButton =
225         [self configureNewUserButton:yOffset updateWidthAdjust:&widthAdjust];
226     [contentView addSubview:newButton];
227     yOffset += NSHeight([newButton frame]) + kVerticalSpacing;
229     NSBox* separator = [self horizontalSeparatorWithFrame:
230         NSMakeRect(10, yOffset, NSWidth([contentView frame]) - 20, 0)];
231     [separator setAutoresizingMask:NSViewWidthSizable];
232     [contentView addSubview:separator];
234     yOffset += NSHeight([separator frame]);
235   } else {
236     yOffset = 7;
237   }
239   // Loop over the profiles in reverse, constructing the menu items.
240   for (int i = menu_->GetNumberOfItems() - 1; i >= 0; --i) {
241     AvatarMenuItemController* itemView = [self initAvatarItem:i
242                                             updateWidthAdjust:&widthAdjust
243                                                    setYOffset:yOffset];
244     [contentView addSubview:[itemView view]];
245     yOffset += NSHeight([[itemView view] frame]);
246   }
248   yOffset += kVerticalSpacing * 1.5;
249   [self setWindowFrame:yOffset widthAdjust:widthAdjust];
252 - (void)initSupervisedUserContents {
253   NSView* contentView = [[self window] contentView];
255   // |yOffset| is the next position at which to draw in contentView coordinates.
256   // Use a little more vertical spacing because the items have padding built-
257   // into the xib, and this gives a little more space to visually match.
258   CGFloat yOffset = kLinkSpacing;
259   CGFloat widthAdjust = 0;
261   // Since drawing happens bottom-up, start with the "Switch User" link.
262   NSButton* newButton =
263       [self configureSwitchUserButton:yOffset updateWidthAdjust:&widthAdjust];
264   [contentView addSubview:newButton];
265   yOffset += NSHeight([newButton frame]) + kVerticalSpacing;
267   NSBox* separator = [self horizontalSeparatorWithFrame:
268       NSMakeRect(10, yOffset, NSWidth([contentView frame]) - 20, 0)];
269   [separator setAutoresizingMask:NSViewWidthSizable];
270   [contentView addSubview:separator];
272   yOffset += NSHeight([separator frame]) + kVerticalSpacing;
274   // First init the active profile in order to determine the required width. We
275   // will have to adjust its frame later after adding general information about
276   // supervised users.
277   AvatarMenuItemController* itemView =
278       [self initAvatarItem:menu_->GetActiveProfileIndex()
279           updateWidthAdjust:&widthAdjust
280                  setYOffset:yOffset];
282   // Don't increase the width too much (the total size should be at most
283   // |kBubbleMaxWidth|).
284   widthAdjust = std::min(widthAdjust, kBubbleMaxWidth - kBubbleMinWidth);
285   CGFloat newWidth = kBubbleMinWidth + widthAdjust;
287   // Add general information about supervised users.
288   NSView* info = [self configureSupervisedUserInformation:newWidth];
289   [info setFrameOrigin:NSMakePoint(0, yOffset)];
290   [contentView addSubview:info];
291   yOffset += NSHeight([info frame]) + kVerticalSpacing;
293   separator = [self horizontalSeparatorWithFrame:
294       NSMakeRect(10, yOffset, NSWidth([contentView frame]) - 20, 0)];
295   [separator setAutoresizingMask:NSViewWidthSizable];
296   [contentView addSubview:separator];
298   yOffset += NSHeight([separator frame]);
300   // Now update the frame of the active profile and add it.
301   NSRect frame = [[itemView view] frame];
302   frame.origin.y = yOffset;
303   [[itemView view] setFrame:frame];
304   [contentView addSubview:[itemView view]];
306   yOffset += NSHeight(frame) + kVerticalSpacing * 1.5;
307   [self setWindowFrame:yOffset widthAdjust:widthAdjust];
310 - (void)performLayout {
311   NSView* contentView = [[self window] contentView];
313   // Reset the array of controllers and remove all the views.
314   items_.reset([[NSMutableArray alloc] init]);
315   [contentView setSubviews:[NSArray array]];
317   if (menu_->GetSupervisedUserInformation().empty() || expanded_)
318     [self initMenuContents];
319   else
320     [self initSupervisedUserContents];
323 - (NSView*)configureSupervisedUserInformation:(CGFloat)width {
324   base::scoped_nsobject<NSView> container(
325       [[NSView alloc] initWithFrame:NSZeroRect]);
327   // Add the limited user icon on the left side of the information TextView.
328   base::scoped_nsobject<NSImageView> iconView(
329       [[NSImageView alloc] initWithFrame:NSMakeRect(5, 0, 16, 16)]);
330   [iconView setImage:menu_->GetSupervisedUserIcon().ToNSImage()];
331   [container addSubview:iconView];
333   NSString* info =
334       base::SysUTF16ToNSString(menu_->GetSupervisedUserInformation());
335   NSDictionary* attributes =
336       @{ NSFontAttributeName : [NSFont labelFontOfSize:12] };
337   base::scoped_nsobject<NSAttributedString> attrString(
338       [[NSAttributedString alloc] initWithString:info attributes:attributes]);
339   base::scoped_nsobject<NSTextView> label(
340       [[NSTextView alloc] initWithFrame:NSMakeRect(
341           kSupervisedUserSpacing, 0, width - kSupervisedUserSpacing - 5, 0)]);
342   [[label textStorage] setAttributedString:attrString];
343   [label setHorizontallyResizable:NO];
344   [label setEditable:NO];
345   [label sizeToFit];
346   [container addSubview:label];
347   [container setFrameSize:NSMakeSize(width, NSHeight([label frame]))];
349   // Reposition the limited user icon so that it is on top.
350   [iconView setFrameOrigin:NSMakePoint(5, NSHeight([label frame]) - 16)];
351   return container.autorelease();
354 - (NSButton*)configureNewUserButton:(CGFloat)yOffset
355                   updateWidthAdjust:(CGFloat*)widthAdjust {
356   base::scoped_nsobject<NSButton> newButton([[NSButton alloc] initWithFrame:
357           NSMakeRect(kLabelInset, yOffset, kBubbleMinWidth - kLabelInset, 16)]);
358   base::scoped_nsobject<HyperlinkButtonCell> buttonCell(
359       [[HyperlinkButtonCell alloc] initTextCell:
360               l10n_util::GetNSString(IDS_PROFILES_CREATE_NEW_PROFILE_LINK)]);
361   [newButton setCell:buttonCell.get()];
362   [newButton setFont:[NSFont labelFontOfSize:12.0]];
363   [newButton setBezelStyle:NSRegularSquareBezelStyle];
364   [newButton setTarget:self];
365   [newButton setAction:@selector(newProfile:)];
366   NSSize delta = [GTMUILocalizerAndLayoutTweaker sizeToFitView:newButton];
367   if (delta.width > 0)
368     *widthAdjust = std::max(*widthAdjust, delta.width);
369   return newButton.autorelease();
372 - (NSButton*)configureSwitchUserButton:(CGFloat)yOffset
373                      updateWidthAdjust:(CGFloat*)widthAdjust {
374   base::scoped_nsobject<NSButton> newButton(
375       [[NSButton alloc] initWithFrame:NSMakeRect(
376           kSupervisedUserSpacing, yOffset, kBubbleMinWidth - kLabelInset, 16)]);
377   base::scoped_nsobject<HyperlinkButtonCell> buttonCell(
378       [[HyperlinkButtonCell alloc] initTextCell:
379               l10n_util::GetNSString(IDS_PROFILES_SWITCH_PROFILE_LINK)]);
380   [newButton setCell:buttonCell.get()];
381   [newButton setFont:[NSFont labelFontOfSize:12.0]];
382   [newButton setBezelStyle:NSRegularSquareBezelStyle];
383   [newButton setTarget:self];
384   [newButton setAction:@selector(switchProfile:)];
385   NSSize delta = [GTMUILocalizerAndLayoutTweaker sizeToFitView:newButton];
386   if (delta.width > 0)
387     *widthAdjust = std::max(*widthAdjust, delta.width);
388   return newButton.autorelease();
391 - (NSMutableArray*)items {
392   return items_.get();
395 - (void)keyDown:(NSEvent*)theEvent {
396   [self interpretKeyEvents:[NSArray arrayWithObject:theEvent]];
399 - (void)moveDown:(id)sender {
400   [self highlightNextItemByDelta:-1];
403 - (void)moveUp:(id)sender {
404   [self highlightNextItemByDelta:1];
407 - (void)insertNewline:(id)sender {
408   for (AvatarMenuItemController* item in items_.get()) {
409     if ([item isHighlighted]) {
410       [self switchToProfile:item];
411       return;
412     }
413   }
416 - (void)highlightNextItemByDelta:(NSInteger)delta {
417   NSUInteger count = [items_ count];
418   if (count == 0)
419     return;
421   NSInteger old_index = -1;
422   for (NSUInteger i = 0; i < count; ++i) {
423     if ([[items_ objectAtIndex:i] isHighlighted]) {
424       old_index = i;
425       break;
426     }
427   }
429   NSInteger new_index;
430   // If nothing is selected then start at the top if we're going down and start
431   // at the bottom if we're going up.
432   if (old_index == -1)
433     new_index = delta < 0 ? (count - 1) : 0;
434   else
435     new_index = old_index + delta;
437   // Cap the index. We don't wrap around to match the behavior of Mac menus.
438   new_index =
439       std::min(std::max(static_cast<NSInteger>(0), new_index),
440                static_cast<NSInteger>(count - 1));
442   [self highlightItem:[items_ objectAtIndex:new_index]];
445 - (void)highlightItem:(AvatarMenuItemController*)newItem {
446   AvatarMenuItemController* oldItem = nil;
447   for (AvatarMenuItemController* item in items_.get()) {
448     if ([item isHighlighted]) {
449       oldItem = item;
450       break;
451     }
452   }
454   if (oldItem == newItem)
455     return;
457   [oldItem setIsHighlighted:NO];
458   [newItem setIsHighlighted:YES];
462 @end
464 // Menu Item Controller ////////////////////////////////////////////////////////
466 @interface AvatarMenuItemController (Private)
467 - (void)animateFromView:(NSView*)outView toView:(NSView*)inView;
468 @end
470 @implementation AvatarMenuItemController
472 @synthesize menuIndex = menuIndex_;
473 @synthesize isHighlighted = isHighlighted_;
474 @synthesize iconView = iconView_;
475 @synthesize activeView = activeView_;
476 @synthesize nameField = nameField_;
477 @synthesize emailField = emailField_;
478 @synthesize editButton = editButton_;
480 - (id)initWithMenuIndex:(size_t)menuIndex
481           menuController:(AvatarMenuBubbleController*)controller {
482   if ((self = [super initWithNibName:@"AvatarMenuItem"
483                               bundle:base::mac::FrameworkBundle()])) {
484     menuIndex_ = menuIndex;
485     controller_ = controller;
486     [self loadView];
487     [nameField_ setAutoresizingMask:NSViewNotSizable];
488     [[nameField_ cell] setLineBreakMode:NSLineBreakByTruncatingTail];
489     [emailField_ setAutoresizingMask:NSViewNotSizable];
490     [[emailField_ cell] setLineBreakMode:NSLineBreakByTruncatingTail];
491   }
492   return self;
495 - (void)dealloc {
496   static_cast<AvatarMenuItemView*>(self.view).viewController = nil;
497   [linkAnimation_ stopAnimation];
498   [linkAnimation_ setDelegate:nil];
499   [super dealloc];
502 - (void)awakeFromNib {
503   [GTMUILocalizerAndLayoutTweaker sizeToFitView:self.editButton];
504   self.editButton.hidden = YES;
507 - (IBAction)switchToProfile:(id)sender {
508   [controller_ switchToProfile:self];
511 - (IBAction)editProfile:(id)sender {
512   [controller_ editProfile:self];
515 - (void)highlightForEventType:(NSEventType)type {
516   switch (type) {
517     case NSMouseEntered:
518       [controller_ highlightItem:self];
519       break;
521     case NSMouseExited:
522       [controller_ highlightItem:nil];
523       break;
525     default:
526       NOTREACHED();
527   };
530 - (void)setIsHighlighted:(BOOL)isHighlighted {
531   if (isHighlighted_ == isHighlighted)
532     return;
534   isHighlighted_ = isHighlighted;
535   [[self view] setNeedsDisplay:YES];
537   // Cancel any running animation.
538   if (linkAnimation_.get()) {
539     [NSObject cancelPreviousPerformRequestsWithTarget:linkAnimation_
540                                              selector:@selector(startAnimation)
541                                                object:nil];
542   }
544   // Fade the edit link in or out only if this is the active view.
545   if (self.activeView.isHidden)
546     return;
548   if (isHighlighted_) {
549     [self animateFromView:self.emailField toView:self.editButton];
550   } else {
551     // If the edit button is visible or the animation to make it so is
552     // running, stop the animation and fade it back to the email. If not, then
553     // don't run an animation to prevent flickering.
554     if (!self.editButton.isHidden || [linkAnimation_ isAnimating]) {
555       [linkAnimation_ stopAnimation];
556       linkAnimation_.reset();
557       [self animateFromView:self.editButton toView:self.emailField];
558     }
559   }
562 - (void)animateFromView:(NSView*)outView toView:(NSView*)inView {
563   const NSTimeInterval kAnimationDuration = 0.175;
565   NSDictionary* outDict = [NSDictionary dictionaryWithObjectsAndKeys:
566       outView, NSViewAnimationTargetKey,
567       NSViewAnimationFadeOutEffect, NSViewAnimationEffectKey,
568       nil
569   ];
570   NSDictionary* inDict = [NSDictionary dictionaryWithObjectsAndKeys:
571       inView, NSViewAnimationTargetKey,
572       NSViewAnimationFadeInEffect, NSViewAnimationEffectKey,
573       nil
574   ];
576   linkAnimation_.reset([[NSViewAnimation alloc] initWithViewAnimations:
577       [NSArray arrayWithObjects:outDict, inDict, nil]]);
578   [linkAnimation_ setDelegate:self];
579   [linkAnimation_ setDuration:kAnimationDuration];
581   [self willStartAnimation:linkAnimation_];
583   [linkAnimation_ performSelector:@selector(startAnimation)
584                        withObject:nil
585                        afterDelay:0.2];
588 - (void)willStartAnimation:(NSAnimation*)animation {
591 - (void)animationDidEnd:(NSAnimation*)animation {
592   if (animation == linkAnimation_.get())
593     linkAnimation_.reset();
596 - (void)animationDidStop:(NSAnimation*)animation {
597   if (animation == linkAnimation_.get())
598     linkAnimation_.reset();
601 @end
603 // Profile Switch Button ///////////////////////////////////////////////////////
605 @implementation AvatarMenuItemView
607 @synthesize viewController = viewController_;
609 - (void)awakeFromNib {
610   [self updateTrackingAreas];
613 - (void)updateTrackingAreas {
614   if (trackingArea_.get())
615     [self removeTrackingArea:trackingArea_.get()];
617   trackingArea_.reset(
618       [[CrTrackingArea alloc] initWithRect:[self bounds]
619                                    options:NSTrackingMouseEnteredAndExited |
620                                            NSTrackingActiveInKeyWindow
621                                      owner:self
622                                   userInfo:nil]);
623   [self addTrackingArea:trackingArea_.get()];
625   [super updateTrackingAreas];
628 - (void)mouseEntered:(id)sender {
629   [viewController_ highlightForEventType:[[NSApp currentEvent] type]];
630   [self setNeedsDisplay:YES];
633 - (void)mouseExited:(id)sender {
634   [viewController_ highlightForEventType:[[NSApp currentEvent] type]];
635   [self setNeedsDisplay:YES];
638 - (void)mouseUp:(id)sender {
639   [viewController_ switchToProfile:self];
642 - (void)drawRect:(NSRect)dirtyRect {
643   NSColor* backgroundColor = nil;
644   if ([viewController_ isHighlighted]) {
645     backgroundColor = [NSColor colorWithCalibratedRed:223.0/255
646                                                 green:238.0/255
647                                                  blue:246.0/255
648                                                 alpha:1.0];
649   } else {
650     backgroundColor = [NSColor clearColor];
651   }
653   [backgroundColor set];
654   [NSBezierPath fillRect:[self bounds]];
657 // Make sure the element is focusable for accessibility.
658 - (BOOL)canBecomeKeyView {
659   return YES;
662 - (BOOL)accessibilityIsIgnored {
663   return NO;
666 - (NSArray*)accessibilityAttributeNames {
667   NSMutableArray* attributes =
668       [[super accessibilityAttributeNames] mutableCopy];
669   [attributes addObject:NSAccessibilityTitleAttribute];
670   [attributes addObject:NSAccessibilityEnabledAttribute];
672   return [attributes autorelease];
675 - (NSArray*)accessibilityActionNames {
676   NSArray* parentActions = [super accessibilityActionNames];
677   return [parentActions arrayByAddingObject:NSAccessibilityPressAction];
680 - (id)accessibilityAttributeValue:(NSString*)attribute {
681   if ([attribute isEqual:NSAccessibilityRoleAttribute])
682     return NSAccessibilityButtonRole;
684   if ([attribute isEqual:NSAccessibilityRoleDescriptionAttribute])
685     return NSAccessibilityRoleDescription(NSAccessibilityButtonRole, nil);
687   if ([attribute isEqual:NSAccessibilityTitleAttribute]) {
688     return l10n_util::GetNSStringF(
689         IDS_PROFILES_SWITCH_TO_PROFILE_ACCESSIBLE_NAME,
690         base::SysNSStringToUTF16(self.viewController.nameField.stringValue));
691   }
693   if ([attribute isEqual:NSAccessibilityEnabledAttribute])
694     return [NSNumber numberWithBool:YES];
696   return [super accessibilityAttributeValue:attribute];
699 - (void)accessibilityPerformAction:(NSString*)action {
700   if ([action isEqual:NSAccessibilityPressAction]) {
701     [viewController_ switchToProfile:self];
702     return;
703   }
705   [super accessibilityPerformAction:action];
708 @end
710 ////////////////////////////////////////////////////////////////////////////////
712 @implementation AccessibilityIgnoredImageCell
713 - (BOOL)accessibilityIsIgnored {
714   return YES;
716 @end
718 @implementation AccessibilityIgnoredTextFieldCell
719 - (BOOL)accessibilityIsIgnored {
720   return YES;
722 @end