OLD | NEW |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #import "chrome/browser/ui/cocoa/profiles/profile_chooser_controller.h" | 5 #import "chrome/browser/ui/cocoa/profiles/profile_chooser_controller.h" |
6 | 6 |
7 #import <Carbon/Carbon.h> // kVK_Return. | 7 #import <Carbon/Carbon.h> // kVK_Return. |
8 #import <Cocoa/Cocoa.h> | 8 #import <Cocoa/Cocoa.h> |
9 #include <stddef.h> | 9 #include <stddef.h> |
10 | 10 |
(...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
86 #include "ui/gfx/paint_vector_icon.h" | 86 #include "ui/gfx/paint_vector_icon.h" |
87 #include "ui/gfx/text_elider.h" | 87 #include "ui/gfx/text_elider.h" |
88 #include "ui/native_theme/common_theme.h" | 88 #include "ui/native_theme/common_theme.h" |
89 #include "ui/native_theme/native_theme.h" | 89 #include "ui/native_theme/native_theme.h" |
90 #include "ui/vector_icons/vector_icons.h" | 90 #include "ui/vector_icons/vector_icons.h" |
91 | 91 |
92 namespace { | 92 namespace { |
93 | 93 |
94 // Constants taken from the Windows/Views implementation at: | 94 // Constants taken from the Windows/Views implementation at: |
95 // chrome/browser/ui/views/profile_chooser_view.cc | 95 // chrome/browser/ui/views/profile_chooser_view.cc |
96 const int kLargeImageSide = 88; | |
97 const int kMdImageSide = 40; | 96 const int kMdImageSide = 40; |
98 | 97 |
99 const CGFloat kFixedMenuWidth = 240.0; | 98 const CGFloat kFixedMenuWidth = 240.0; |
100 const int kIconImageSide = 18; | 99 const int kIconImageSide = 18; |
101 const CGFloat kVerticalSpacing = 16.0; | 100 const CGFloat kVerticalSpacing = 16.0; |
102 const CGFloat kSmallVerticalSpacing = 10.0; | 101 const CGFloat kSmallVerticalSpacing = 10.0; |
103 const CGFloat kRelatedControllVerticalSpacing = 8.0; | 102 const CGFloat kRelatedControllVerticalSpacing = 8.0; |
104 const CGFloat kHorizontalSpacing = 16.0; | 103 const CGFloat kHorizontalSpacing = 16.0; |
105 const CGFloat kTitleFontSize = 15.0; | 104 const CGFloat kTitleFontSize = 15.0; |
106 const CGFloat kTextFontSize = 12.0; | 105 const CGFloat kTextFontSize = 12.0; |
107 const CGFloat kProfileButtonHeight = 30; | 106 const CGFloat kProfileButtonHeight = 30; |
108 const int kBezelThickness = 3; // Width of the bezel on an NSButton. | |
109 const int kBlueButtonHeight = 30; | 107 const int kBlueButtonHeight = 30; |
110 const CGFloat kFocusRingLineWidth = 2; | 108 const CGFloat kFocusRingLineWidth = 2; |
111 | 109 |
112 // Fixed size for embedded sign in pages as defined in Gaia. | 110 // Fixed size for embedded sign in pages as defined in Gaia. |
113 const CGFloat kFixedGaiaViewWidth = 360; | 111 const CGFloat kFixedGaiaViewWidth = 360; |
114 | 112 |
115 // Fixed size for the account removal view. | 113 // Fixed size for the account removal view. |
116 const CGFloat kFixedAccountRemovalViewWidth = 280; | 114 const CGFloat kFixedAccountRemovalViewWidth = 280; |
117 | 115 |
118 // Fixed size for the switch user view. | 116 // Fixed size for the switch user view. |
(...skipping 407 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
526 | 524 |
527 @implementation CustomCircleImageCell | 525 @implementation CustomCircleImageCell |
528 - (void)drawWithFrame:(NSRect)frame inView:(NSView *)controlView { | 526 - (void)drawWithFrame:(NSRect)frame inView:(NSView *)controlView { |
529 // Display everything as a circle that spans the entire control. | 527 // Display everything as a circle that spans the entire control. |
530 NSBezierPath* path = [NSBezierPath bezierPathWithOvalInRect:frame]; | 528 NSBezierPath* path = [NSBezierPath bezierPathWithOvalInRect:frame]; |
531 [path addClip]; | 529 [path addClip]; |
532 [super drawImage:[self image] withFrame:frame inView:controlView]; | 530 [super drawImage:[self image] withFrame:frame inView:controlView]; |
533 } | 531 } |
534 @end | 532 @end |
535 | 533 |
536 // A custom image control that shows a "Change" button when moused over. | |
537 @interface EditableProfilePhoto : HoverImageButton { | |
538 @private | |
539 AvatarMenu* avatarMenu_; // Weak; Owned by ProfileChooserController. | |
540 base::scoped_nsobject<TransparentBackgroundImageView> changePhotoImage_; | |
541 ProfileChooserController* controller_; | |
542 } | |
543 | |
544 - (id)initWithFrame:(NSRect)frameRect | |
545 avatarMenu:(AvatarMenu*)avatarMenu | |
546 profileIcon:(const gfx::Image&)profileIcon | |
547 editingAllowed:(BOOL)editingAllowed | |
548 withController:(ProfileChooserController*)controller; | |
549 | |
550 // Called when the "Change" button is clicked. | |
551 - (void)editPhoto:(id)sender; | |
552 | |
553 @end | |
554 | |
555 @implementation EditableProfilePhoto | |
556 - (id)initWithFrame:(NSRect)frameRect | |
557 avatarMenu:(AvatarMenu*)avatarMenu | |
558 profileIcon:(const gfx::Image&)profileIcon | |
559 editingAllowed:(BOOL)editingAllowed | |
560 withController:(ProfileChooserController*)controller { | |
561 if ((self = [super initWithFrame:frameRect])) { | |
562 avatarMenu_ = avatarMenu; | |
563 controller_ = controller; | |
564 | |
565 [self setBordered:NO]; | |
566 | |
567 base::scoped_nsobject<CustomCircleImageCell> cell( | |
568 [[CustomCircleImageCell alloc] init]); | |
569 [self setCell:cell.get()]; | |
570 | |
571 [self setDefaultImage:CreateProfileImage(profileIcon, kLargeImageSide, | |
572 profiles::SHAPE_SQUARE)]; | |
573 [self setImagePosition:NSImageOnly]; | |
574 | |
575 if (editingAllowed) { | |
576 NSRect bounds = NSMakeRect(0, 0, kLargeImageSide, kLargeImageSide); | |
577 [self setTarget:self]; | |
578 [self setAction:@selector(editPhoto:)]; | |
579 changePhotoImage_.reset([[TransparentBackgroundImageView alloc] | |
580 initWithFrame:bounds]); | |
581 [changePhotoImage_ setImage:ui::ResourceBundle::GetSharedInstance(). | |
582 GetNativeImageNamed(IDR_ICON_PROFILES_EDIT_CAMERA).AsNSImage()]; | |
583 [self addSubview:changePhotoImage_]; | |
584 | |
585 // Hide the image until the button is hovered over. | |
586 [changePhotoImage_ setHidden:YES]; | |
587 } | |
588 | |
589 // Set the image cell's accessibility strings to be the same as the | |
590 // button's strings. | |
591 [[self cell] accessibilitySetOverrideValue:l10n_util::GetNSString( | |
592 editingAllowed ? | |
593 IDS_PROFILES_NEW_AVATAR_MENU_CHANGE_PHOTO_ACCESSIBLE_NAME : | |
594 IDS_PROFILES_NEW_AVATAR_MENU_PHOTO_ACCESSIBLE_NAME) | |
595 forAttribute:NSAccessibilityTitleAttribute]; | |
596 [[self cell] accessibilitySetOverrideValue: | |
597 editingAllowed ? NSAccessibilityButtonRole : NSAccessibilityImageRole | |
598 forAttribute:NSAccessibilityRoleAttribute]; | |
599 [[self cell] accessibilitySetOverrideValue: | |
600 NSAccessibilityRoleDescription(NSAccessibilityButtonRole, nil) | |
601 forAttribute:NSAccessibilityRoleDescriptionAttribute]; | |
602 | |
603 // The button and the cell should read the same thing. | |
604 [self accessibilitySetOverrideValue:l10n_util::GetNSString( | |
605 editingAllowed ? | |
606 IDS_PROFILES_NEW_AVATAR_MENU_CHANGE_PHOTO_ACCESSIBLE_NAME : | |
607 IDS_PROFILES_NEW_AVATAR_MENU_PHOTO_ACCESSIBLE_NAME) | |
608 forAttribute:NSAccessibilityTitleAttribute]; | |
609 [self accessibilitySetOverrideValue:NSAccessibilityButtonRole | |
610 forAttribute:NSAccessibilityRoleAttribute]; | |
611 [self accessibilitySetOverrideValue: | |
612 NSAccessibilityRoleDescription(NSAccessibilityButtonRole, nil) | |
613 forAttribute:NSAccessibilityRoleDescriptionAttribute]; | |
614 } | |
615 return self; | |
616 } | |
617 | |
618 - (void)editPhoto:(id)sender { | |
619 avatarMenu_->EditProfile(avatarMenu_->GetActiveProfileIndex()); | |
620 [controller_ | |
621 postActionPerformed:ProfileMetrics::PROFILE_DESKTOP_MENU_EDIT_IMAGE]; | |
622 } | |
623 | |
624 - (void)setHoverState:(HoverState)state { | |
625 [super setHoverState:state]; | |
626 [changePhotoImage_ setHidden:([self hoverState] == kHoverStateNone)]; | |
627 } | |
628 | |
629 - (BOOL)canBecomeKeyView { | |
630 return false; | |
631 } | |
632 | |
633 - (BOOL)accessibilityIsIgnored { | |
634 return NO; | |
635 } | |
636 | |
637 - (NSArray*)accessibilityActionNames { | |
638 NSArray* parentActions = [super accessibilityActionNames]; | |
639 return [parentActions arrayByAddingObject:NSAccessibilityPressAction]; | |
640 } | |
641 | |
642 - (void)accessibilityPerformAction:(NSString*)action { | |
643 if ([action isEqualToString:NSAccessibilityPressAction]) { | |
644 avatarMenu_->EditProfile(avatarMenu_->GetActiveProfileIndex()); | |
645 } | |
646 | |
647 [super accessibilityPerformAction:action]; | |
648 } | |
649 | |
650 @end | |
651 | |
652 // A custom view with a filled circular background. | 534 // A custom view with a filled circular background. |
653 @interface BackgroundCircleView : NSView { | 535 @interface BackgroundCircleView : NSView { |
654 @private | 536 @private |
655 base::scoped_nsobject<NSColor> fillColor_; | 537 base::scoped_nsobject<NSColor> fillColor_; |
656 } | 538 } |
657 @end | 539 @end |
658 | 540 |
659 @implementation BackgroundCircleView | 541 @implementation BackgroundCircleView |
660 - (id)initWithFrame:(NSRect)frameRect withFillColor:(NSColor*)fillColor { | 542 - (id)initWithFrame:(NSRect)frameRect withFillColor:(NSColor*)fillColor { |
661 if ((self = [super initWithFrame:frameRect])) | 543 if ((self = [super initWithFrame:frameRect])) |
662 fillColor_.reset([fillColor retain]); | 544 fillColor_.reset([fillColor retain]); |
663 return self; | 545 return self; |
664 } | 546 } |
665 | 547 |
666 - (void)drawRect:(NSRect)dirtyRect { | 548 - (void)drawRect:(NSRect)dirtyRect { |
667 [fillColor_ setFill]; | 549 [fillColor_ setFill]; |
668 NSBezierPath* circlePath = [NSBezierPath bezierPath]; | 550 NSBezierPath* circlePath = [NSBezierPath bezierPath]; |
669 [circlePath appendBezierPathWithOvalInRect:[self bounds]]; | 551 [circlePath appendBezierPathWithOvalInRect:[self bounds]]; |
670 [circlePath fill]; | 552 [circlePath fill]; |
671 | 553 |
672 [super drawRect:dirtyRect]; | 554 [super drawRect:dirtyRect]; |
673 } | 555 } |
674 @end | 556 @end |
675 | 557 |
676 // A custom text control that turns into a textfield for editing when clicked. | |
677 @interface EditableProfileNameButton : HoverImageButton<NSTextFieldDelegate> { | |
678 @private | |
679 base::scoped_nsobject<NSTextField> profileNameTextField_; | |
680 Profile* profile_; // Weak. | |
681 ProfileChooserController* controller_; | |
682 } | |
683 | |
684 - (id)initWithFrame:(NSRect)frameRect | |
685 profile:(Profile*)profile | |
686 profileName:(NSString*)profileName | |
687 editingAllowed:(BOOL)editingAllowed | |
688 withController:(ProfileChooserController*)controller; | |
689 | |
690 // Called when the button is clicked. | |
691 - (void)showEditableView:(id)sender; | |
692 | |
693 // Called when enter is pressed in the text field. | |
694 - (void)saveProfileName; | |
695 | |
696 @end | |
697 | |
698 @implementation EditableProfileNameButton | |
699 - (id)initWithFrame:(NSRect)frameRect | |
700 profile:(Profile*)profile | |
701 profileName:(NSString*)profileName | |
702 editingAllowed:(BOOL)editingAllowed | |
703 withController:(ProfileChooserController*)controller { | |
704 if ((self = [super initWithFrame:frameRect])) { | |
705 profile_ = profile; | |
706 controller_ = controller; | |
707 | |
708 CGFloat availableWidth = frameRect.size.width; | |
709 NSSize textSize = [profileName sizeWithAttributes:@{ | |
710 NSFontAttributeName : [self font] | |
711 }]; | |
712 | |
713 if (editingAllowed) { | |
714 // Show an "edit" pencil icon when hovering over. In the default state, | |
715 // we need to create an empty placeholder of the correct size, so that | |
716 // the text doesn't jump around when the hovered icon appears. | |
717 ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance(); | |
718 NSImage* hoverImage = rb->GetNativeImageNamed( | |
719 IDR_ICON_PROFILES_EDIT_HOVER).AsNSImage(); | |
720 | |
721 // In order to center the button title, we need to add a left padding of | |
722 // the same width as the pencil icon. | |
723 base::scoped_nsobject<CustomPaddingImageButtonCell> cell( | |
724 [[CustomPaddingImageButtonCell alloc] | |
725 initWithLeftMarginSpacing:[hoverImage size].width | |
726 imageTitleSpacing:0]); | |
727 [self setCell:cell.get()]; | |
728 | |
729 NSImage* placeholder = [[NSImage alloc] initWithSize:[hoverImage size]]; | |
730 [self setDefaultImage:placeholder]; | |
731 [self setHoverImage:hoverImage]; | |
732 [self setAlternateImage: | |
733 rb->GetNativeImageNamed(IDR_ICON_PROFILES_EDIT_PRESSED).AsNSImage()]; | |
734 [self setImagePosition:NSImageRight]; | |
735 [self setTarget:self]; | |
736 [self setAction:@selector(showEditableView:)]; | |
737 | |
738 // We need to subtract the width of the bezel from the frame rect, so that | |
739 // the textfield can take the exact same space as the button. | |
740 frameRect.size.height -= 2 * kBezelThickness; | |
741 frameRect.origin = NSMakePoint(0, kBezelThickness); | |
742 profileNameTextField_.reset( | |
743 [[NSTextField alloc] initWithFrame:frameRect]); | |
744 [profileNameTextField_ setStringValue:profileName]; | |
745 [profileNameTextField_ setFont:[NSFont labelFontOfSize:kTitleFontSize]]; | |
746 [profileNameTextField_ setEditable:YES]; | |
747 [profileNameTextField_ setDrawsBackground:YES]; | |
748 [profileNameTextField_ setBezeled:YES]; | |
749 [profileNameTextField_ setAlignment:NSCenterTextAlignment]; | |
750 [[profileNameTextField_ cell] setWraps:NO]; | |
751 [[profileNameTextField_ cell] setLineBreakMode: | |
752 NSLineBreakByTruncatingTail]; | |
753 [[profileNameTextField_ cell] setUsesSingleLineMode:YES]; | |
754 [self addSubview:profileNameTextField_]; | |
755 [profileNameTextField_ setDelegate:self]; | |
756 | |
757 // Hide the textfield until the user clicks on the button. | |
758 [profileNameTextField_ setHidden:YES]; | |
759 | |
760 [[self cell] accessibilitySetOverrideValue:l10n_util::GetNSStringF( | |
761 IDS_PROFILES_NEW_AVATAR_MENU_EDIT_NAME_ACCESSIBLE_NAME, | |
762 base::SysNSStringToUTF16(profileName)) | |
763 forAttribute:NSAccessibilityTitleAttribute]; | |
764 | |
765 // Recompute the available width for the name since the icon takes space. | |
766 availableWidth -= [hoverImage size].width * 2; | |
767 // The profileNameTextField_ might size the text differently. | |
768 textSize = [profileName sizeWithAttributes:@{ | |
769 NSFontAttributeName : [profileNameTextField_ font] | |
770 }]; | |
771 } | |
772 | |
773 if (textSize.width > availableWidth) | |
774 [self setToolTip:profileName]; | |
775 | |
776 [[self cell] accessibilitySetOverrideValue:NSAccessibilityButtonRole | |
777 forAttribute:NSAccessibilityRoleAttribute]; | |
778 [[self cell] | |
779 accessibilitySetOverrideValue:NSAccessibilityRoleDescription( | |
780 NSAccessibilityButtonRole, nil) | |
781 forAttribute:NSAccessibilityRoleDescriptionAttribute]; | |
782 | |
783 [self setBordered:NO]; | |
784 [self setFont:[NSFont labelFontOfSize:kTitleFontSize]]; | |
785 [self setAlignment:NSCenterTextAlignment]; | |
786 [[self cell] setLineBreakMode:NSLineBreakByTruncatingTail]; | |
787 [self setTitle:profileName]; | |
788 } | |
789 return self; | |
790 } | |
791 | |
792 - (void)saveProfileName { | |
793 base::string16 newProfileName = | |
794 base::SysNSStringToUTF16([profileNameTextField_ stringValue]); | |
795 | |
796 // Empty profile names are not allowed, and do nothing. | |
797 base::TrimWhitespace(newProfileName, base::TRIM_ALL, &newProfileName); | |
798 if (!newProfileName.empty()) { | |
799 profiles::UpdateProfileName(profile_, newProfileName); | |
800 [controller_ | |
801 postActionPerformed:ProfileMetrics::PROFILE_DESKTOP_MENU_EDIT_NAME]; | |
802 [profileNameTextField_ setHidden:YES]; | |
803 } | |
804 } | |
805 | |
806 - (void)showEditableView:(id)sender { | |
807 [profileNameTextField_ setHidden:NO]; | |
808 [[self window] makeFirstResponder:profileNameTextField_]; | |
809 } | |
810 | |
811 - (BOOL)canBecomeKeyView { | |
812 return false; | |
813 } | |
814 | |
815 - (BOOL)control:(NSControl*)control | |
816 textView:(NSTextView*)textView | |
817 doCommandBySelector:(SEL)commandSelector { | |
818 if (commandSelector == @selector(insertTab:) || | |
819 commandSelector == @selector(insertNewline:)) { | |
820 [self saveProfileName]; | |
821 return YES; | |
822 } | |
823 return NO; | |
824 } | |
825 | |
826 @end | |
827 | |
828 // A custom button that allows for setting a background color when hovered over. | 558 // A custom button that allows for setting a background color when hovered over. |
829 @interface BackgroundColorHoverButton : HoverImageButton { | 559 @interface BackgroundColorHoverButton : HoverImageButton { |
830 @private | 560 @private |
831 base::scoped_nsobject<NSColor> backgroundColor_; | 561 base::scoped_nsobject<NSColor> backgroundColor_; |
832 base::scoped_nsobject<NSColor> hoverColor_; | 562 base::scoped_nsobject<NSColor> hoverColor_; |
833 } | 563 } |
834 | 564 |
835 - (void)setRightMarginSpacing:(int)rightMarginSpacing; | 565 - (void)setRightMarginSpacing:(int)rightMarginSpacing; |
836 @end | 566 @end |
837 | 567 |
(...skipping 1806 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2644 | 2374 |
2645 - (void)showWindow:(id)sender { | 2375 - (void)showWindow:(id)sender { |
2646 [super showWindow:sender]; | 2376 [super showWindow:sender]; |
2647 NSEvent *event = [[NSApplication sharedApplication] currentEvent]; | 2377 NSEvent *event = [[NSApplication sharedApplication] currentEvent]; |
2648 if (firstProfileView_ && [event type] == NSKeyDown) { | 2378 if (firstProfileView_ && [event type] == NSKeyDown) { |
2649 [[self window] makeFirstResponder:firstProfileView_]; | 2379 [[self window] makeFirstResponder:firstProfileView_]; |
2650 } | 2380 } |
2651 } | 2381 } |
2652 | 2382 |
2653 @end | 2383 @end |
OLD | NEW |