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)
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;
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"
64 const CGFloat kSupervisedUserSpacing = 26.0;
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(),
82 if ((self = [self initWithMenu:menu
83 parentWindow:parentBrowser->window()->GetNativeWindow()
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 {
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
123 if ((self = [super initWithWindow:window
125 anchoredAt:point])) {
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];
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];
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.username);
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];
178 *widthAdjust = std::max(*widthAdjust, delta.width);
181 // In the inactive case, hide additional UI.
182 [itemView.activeView setHidden:YES];
183 [itemView.editButton setHidden:YES];
185 // Otherwise, set up the edit button and its three interaction states.
186 itemView.activeView.image =
187 rb.GetImageNamed(IDR_PROFILE_SELECTED).ToNSImage();
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];
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
203 CGFloat newWidth = std::min(kBubbleMinWidth + width, kBubbleMaxWidth);
205 frame.origin.x += frame.size.width - newWidth;
206 frame.origin.y += frame.size.height - yOffset;
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]);
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
244 [contentView addSubview:[itemView view]];
245 yOffset += NSHeight([[itemView view] frame]);
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
277 AvatarMenuItemController* itemView =
278 [self initAvatarItem:menu_->GetActiveProfileIndex()
279 updateWidthAdjust:&widthAdjust
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];
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 [container addSubview:iconView];
333 base::SysUTF16ToNSString(menu_->GetSupervisedUserInformation());
334 NSDictionary* attributes =
335 @{ NSFontAttributeName : [NSFont labelFontOfSize:12] };
336 base::scoped_nsobject<NSAttributedString> attrString(
337 [[NSAttributedString alloc] initWithString:info attributes:attributes]);
338 base::scoped_nsobject<NSTextView> label(
339 [[NSTextView alloc] initWithFrame:NSMakeRect(
340 kSupervisedUserSpacing, 0, width - kSupervisedUserSpacing - 5, 0)]);
341 [[label textStorage] setAttributedString:attrString];
342 [label setHorizontallyResizable:NO];
343 [label setEditable:NO];
345 [container addSubview:label];
346 [container setFrameSize:NSMakeSize(width, NSHeight([label frame]))];
348 // Reposition the limited user icon so that it is on top.
349 [iconView setFrameOrigin:NSMakePoint(5, NSHeight([label frame]) - 16)];
350 return container.autorelease();
353 - (NSButton*)configureNewUserButton:(CGFloat)yOffset
354 updateWidthAdjust:(CGFloat*)widthAdjust {
355 base::scoped_nsobject<NSButton> newButton([[NSButton alloc] initWithFrame:
356 NSMakeRect(kLabelInset, yOffset, kBubbleMinWidth - kLabelInset, 16)]);
357 base::scoped_nsobject<HyperlinkButtonCell> buttonCell(
358 [[HyperlinkButtonCell alloc] initTextCell:
359 l10n_util::GetNSString(IDS_PROFILES_CREATE_NEW_PROFILE_LINK)]);
360 [newButton setCell:buttonCell.get()];
361 [newButton setFont:[NSFont labelFontOfSize:12.0]];
362 [newButton setBezelStyle:NSRegularSquareBezelStyle];
363 [newButton setTarget:self];
364 [newButton setAction:@selector(newProfile:)];
365 NSSize delta = [GTMUILocalizerAndLayoutTweaker sizeToFitView:newButton];
367 *widthAdjust = std::max(*widthAdjust, delta.width);
368 return newButton.autorelease();
371 - (NSButton*)configureSwitchUserButton:(CGFloat)yOffset
372 updateWidthAdjust:(CGFloat*)widthAdjust {
373 base::scoped_nsobject<NSButton> newButton(
374 [[NSButton alloc] initWithFrame:NSMakeRect(
375 kSupervisedUserSpacing, yOffset, kBubbleMinWidth - kLabelInset, 16)]);
376 base::scoped_nsobject<HyperlinkButtonCell> buttonCell(
377 [[HyperlinkButtonCell alloc] initTextCell:
378 l10n_util::GetNSString(IDS_PROFILES_SWITCH_PROFILE_LINK)]);
379 [newButton setCell:buttonCell.get()];
380 [newButton setFont:[NSFont labelFontOfSize:12.0]];
381 [newButton setBezelStyle:NSRegularSquareBezelStyle];
382 [newButton setTarget:self];
383 [newButton setAction:@selector(switchProfile:)];
384 NSSize delta = [GTMUILocalizerAndLayoutTweaker sizeToFitView:newButton];
386 *widthAdjust = std::max(*widthAdjust, delta.width);
387 return newButton.autorelease();
390 - (NSMutableArray*)items {
394 - (void)keyDown:(NSEvent*)theEvent {
395 [self interpretKeyEvents:[NSArray arrayWithObject:theEvent]];
398 - (void)moveDown:(id)sender {
399 [self highlightNextItemByDelta:-1];
402 - (void)moveUp:(id)sender {
403 [self highlightNextItemByDelta:1];
406 - (void)insertNewline:(id)sender {
407 for (AvatarMenuItemController* item in items_.get()) {
408 if ([item isHighlighted]) {
409 [self switchToProfile:item];
415 - (void)highlightNextItemByDelta:(NSInteger)delta {
416 NSUInteger count = [items_ count];
420 NSInteger old_index = -1;
421 for (NSUInteger i = 0; i < count; ++i) {
422 if ([[items_ objectAtIndex:i] isHighlighted]) {
429 // If nothing is selected then start at the top if we're going down and start
430 // at the bottom if we're going up.
432 new_index = delta < 0 ? (count - 1) : 0;
434 new_index = old_index + delta;
436 // Cap the index. We don't wrap around to match the behavior of Mac menus.
438 std::min(std::max(static_cast<NSInteger>(0), new_index),
439 static_cast<NSInteger>(count - 1));
441 [self highlightItem:[items_ objectAtIndex:new_index]];
444 - (void)highlightItem:(AvatarMenuItemController*)newItem {
445 AvatarMenuItemController* oldItem = nil;
446 for (AvatarMenuItemController* item in items_.get()) {
447 if ([item isHighlighted]) {
453 if (oldItem == newItem)
456 [oldItem setIsHighlighted:NO];
457 [newItem setIsHighlighted:YES];
463 // Menu Item Controller ////////////////////////////////////////////////////////
465 @interface AvatarMenuItemController (Private)
466 - (void)animateFromView:(NSView*)outView toView:(NSView*)inView;
469 @implementation AvatarMenuItemController
471 @synthesize menuIndex = menuIndex_;
472 @synthesize isHighlighted = isHighlighted_;
473 @synthesize iconView = iconView_;
474 @synthesize activeView = activeView_;
475 @synthesize nameField = nameField_;
476 @synthesize emailField = emailField_;
477 @synthesize editButton = editButton_;
479 - (id)initWithMenuIndex:(size_t)menuIndex
480 menuController:(AvatarMenuBubbleController*)controller {
481 if ((self = [super initWithNibName:@"AvatarMenuItem"
482 bundle:base::mac::FrameworkBundle()])) {
483 menuIndex_ = menuIndex;
484 controller_ = controller;
486 [nameField_ setAutoresizingMask:NSViewNotSizable];
487 [[nameField_ cell] setLineBreakMode:NSLineBreakByTruncatingTail];
488 [emailField_ setAutoresizingMask:NSViewNotSizable];
489 [[emailField_ cell] setLineBreakMode:NSLineBreakByTruncatingTail];
495 static_cast<AvatarMenuItemView*>(self.view).viewController = nil;
496 [linkAnimation_ stopAnimation];
497 [linkAnimation_ setDelegate:nil];
501 - (void)awakeFromNib {
502 [GTMUILocalizerAndLayoutTweaker sizeToFitView:self.editButton];
503 self.editButton.hidden = YES;
506 - (IBAction)switchToProfile:(id)sender {
507 [controller_ switchToProfile:self];
510 - (IBAction)editProfile:(id)sender {
511 [controller_ editProfile:self];
514 - (void)highlightForEventType:(NSEventType)type {
517 [controller_ highlightItem:self];
521 [controller_ highlightItem:nil];
529 - (void)setIsHighlighted:(BOOL)isHighlighted {
530 if (isHighlighted_ == isHighlighted)
533 isHighlighted_ = isHighlighted;
534 [[self view] setNeedsDisplay:YES];
536 // Cancel any running animation.
537 if (linkAnimation_.get()) {
538 [NSObject cancelPreviousPerformRequestsWithTarget:linkAnimation_
539 selector:@selector(startAnimation)
543 // Fade the edit link in or out only if this is the active view.
544 if (self.activeView.isHidden)
547 if (isHighlighted_) {
548 [self animateFromView:self.emailField toView:self.editButton];
550 // If the edit button is visible or the animation to make it so is
551 // running, stop the animation and fade it back to the email. If not, then
552 // don't run an animation to prevent flickering.
553 if (!self.editButton.isHidden || [linkAnimation_ isAnimating]) {
554 [linkAnimation_ stopAnimation];
555 linkAnimation_.reset();
556 [self animateFromView:self.editButton toView:self.emailField];
561 - (void)animateFromView:(NSView*)outView toView:(NSView*)inView {
562 const NSTimeInterval kAnimationDuration = 0.175;
564 NSDictionary* outDict = [NSDictionary dictionaryWithObjectsAndKeys:
565 outView, NSViewAnimationTargetKey,
566 NSViewAnimationFadeOutEffect, NSViewAnimationEffectKey,
569 NSDictionary* inDict = [NSDictionary dictionaryWithObjectsAndKeys:
570 inView, NSViewAnimationTargetKey,
571 NSViewAnimationFadeInEffect, NSViewAnimationEffectKey,
575 linkAnimation_.reset([[NSViewAnimation alloc] initWithViewAnimations:
576 [NSArray arrayWithObjects:outDict, inDict, nil]]);
577 [linkAnimation_ setDelegate:self];
578 [linkAnimation_ setDuration:kAnimationDuration];
580 [self willStartAnimation:linkAnimation_];
582 [linkAnimation_ performSelector:@selector(startAnimation)
587 - (void)willStartAnimation:(NSAnimation*)animation {
590 - (void)animationDidEnd:(NSAnimation*)animation {
591 if (animation == linkAnimation_.get())
592 linkAnimation_.reset();
595 - (void)animationDidStop:(NSAnimation*)animation {
596 if (animation == linkAnimation_.get())
597 linkAnimation_.reset();
602 // Profile Switch Button ///////////////////////////////////////////////////////
604 @implementation AvatarMenuItemView
606 @synthesize viewController = viewController_;
608 - (void)awakeFromNib {
609 [self updateTrackingAreas];
612 - (void)updateTrackingAreas {
613 if (trackingArea_.get())
614 [self removeTrackingArea:trackingArea_.get()];
617 [[CrTrackingArea alloc] initWithRect:[self bounds]
618 options:NSTrackingMouseEnteredAndExited |
619 NSTrackingActiveInKeyWindow
622 [self addTrackingArea:trackingArea_.get()];
624 [super updateTrackingAreas];
627 - (void)mouseEntered:(id)sender {
628 [viewController_ highlightForEventType:[[NSApp currentEvent] type]];
629 [self setNeedsDisplay:YES];
632 - (void)mouseExited:(id)sender {
633 [viewController_ highlightForEventType:[[NSApp currentEvent] type]];
634 [self setNeedsDisplay:YES];
637 - (void)mouseUp:(id)sender {
638 [viewController_ switchToProfile:self];
641 - (void)drawRect:(NSRect)dirtyRect {
642 NSColor* backgroundColor = nil;
643 if ([viewController_ isHighlighted]) {
644 backgroundColor = [NSColor colorWithCalibratedRed:223.0/255
649 backgroundColor = [NSColor clearColor];
652 [backgroundColor set];
653 [NSBezierPath fillRect:[self bounds]];
656 // Make sure the element is focusable for accessibility.
657 - (BOOL)canBecomeKeyView {
661 - (BOOL)accessibilityIsIgnored {
665 - (NSArray*)accessibilityAttributeNames {
666 NSMutableArray* attributes =
667 [[super accessibilityAttributeNames] mutableCopy];
668 [attributes addObject:NSAccessibilityTitleAttribute];
669 [attributes addObject:NSAccessibilityEnabledAttribute];
671 return [attributes autorelease];
674 - (NSArray*)accessibilityActionNames {
675 NSArray* parentActions = [super accessibilityActionNames];
676 return [parentActions arrayByAddingObject:NSAccessibilityPressAction];
679 - (id)accessibilityAttributeValue:(NSString*)attribute {
680 if ([attribute isEqual:NSAccessibilityRoleAttribute])
681 return NSAccessibilityButtonRole;
683 if ([attribute isEqual:NSAccessibilityRoleDescriptionAttribute])
684 return NSAccessibilityRoleDescription(NSAccessibilityButtonRole, nil);
686 if ([attribute isEqual:NSAccessibilityTitleAttribute]) {
687 return l10n_util::GetNSStringF(
688 IDS_PROFILES_SWITCH_TO_PROFILE_ACCESSIBLE_NAME,
689 base::SysNSStringToUTF16(self.viewController.nameField.stringValue));
692 if ([attribute isEqual:NSAccessibilityEnabledAttribute])
693 return [NSNumber numberWithBool:YES];
695 return [super accessibilityAttributeValue:attribute];
698 - (void)accessibilityPerformAction:(NSString*)action {
699 if ([action isEqual:NSAccessibilityPressAction]) {
700 [viewController_ switchToProfile:self];
704 [super accessibilityPerformAction:action];
709 ////////////////////////////////////////////////////////////////////////////////
711 @implementation AccessibilityIgnoredImageCell
712 - (BOOL)accessibilityIsIgnored {
717 @implementation AccessibilityIgnoredTextFieldCell
718 - (BOOL)accessibilityIsIgnored {