Don't show supervised user as "already on this device" while they're being imported.
[chromium-blink-merge.git] / chrome / browser / ui / cocoa / extensions / toolbar_actions_bar_bubble_mac.mm
blob8ea677543ff6400d9fba382e9c3876c2f0d05b6d
1 // Copyright 2015 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/extensions/toolbar_actions_bar_bubble_mac.h"
7 #include "base/mac/foundation_util.h"
8 #include "base/strings/sys_string_conversions.h"
9 #import "chrome/browser/ui/cocoa/info_bubble_view.h"
10 #import "chrome/browser/ui/cocoa/info_bubble_window.h"
11 #include "chrome/browser/ui/toolbar/toolbar_actions_bar_bubble_delegate.h"
12 #include "skia/ext/skia_utils_mac.h"
13 #import "third_party/google_toolbox_for_mac/src/AppKit/GTMUILocalizerAndLayoutTweaker.h"
14 #include "third_party/skia/include/core/SkColor.h"
15 #import "ui/base/cocoa/controls/hyperlink_button_cell.h"
16 #import "ui/base/cocoa/hover_button.h"
17 #import "ui/base/cocoa/window_size_constants.h"
18 #include "ui/native_theme/native_theme.h"
20 namespace {
21 BOOL g_animations_enabled = false;
24 @interface ToolbarActionsBarBubbleMac ()
26 // Handles the notification that the window will close.
27 - (void)windowWillClose:(NSNotification*)notification;
29 // Creates and returns an NSAttributed string with the specified size and
30 // alignment.
31 - (NSAttributedString*)attributedStringWithString:(const base::string16&)string
32                                          fontSize:(CGFloat)fontSize
33                                         alignment:(NSTextAlignment)alignment;
35 // Creates an NSTextField with the given string, size, and alignment, and adds
36 // it to the window.
37 - (NSTextField*)addTextFieldWithString:(const base::string16&)string
38                               fontSize:(CGFloat)fontSize
39                              alignment:(NSTextAlignment)alignment;
41 // Creates an ExtensionMessagebubbleButton the given string id, and adds it to
42 // the window.
43 - (NSButton*)addButtonWithString:(const base::string16&)string;
45 // Initializes the bubble's content.
46 - (void)layout;
48 // Handles a button being clicked.
49 - (void)onButtonClicked:(id)sender;
51 @end
53 @implementation ToolbarActionsBarBubbleMac
55 @synthesize actionButton = actionButton_;
56 @synthesize itemList = itemList_;
57 @synthesize dismissButton = dismissButton_;
58 @synthesize learnMoreButton = learnMoreButton_;
60 - (id)initWithParentWindow:(NSWindow*)parentWindow
61                anchorPoint:(NSPoint)anchorPoint
62                   delegate:(scoped_ptr<ToolbarActionsBarBubbleDelegate>)
63                                delegate {
64   base::scoped_nsobject<InfoBubbleWindow> window(
65       [[InfoBubbleWindow alloc]
66           initWithContentRect:ui::kWindowSizeDeterminedLater
67                     styleMask:NSBorderlessWindowMask
68                       backing:NSBackingStoreBuffered
69                         defer:NO]);
70   if ((self = [super initWithWindow:window
71                        parentWindow:parentWindow
72                          anchoredAt:anchorPoint])) {
73     acknowledged_ = NO;
74     [window setInfoBubbleCanBecomeKeyWindow:NO];
75     delegate_ = delegate.Pass();
77     ui::NativeTheme* nativeTheme = ui::NativeTheme::instance();
78     [[self bubble] setAlignment:info_bubble::kAlignArrowToAnchor];
79     [[self bubble] setArrowLocation:info_bubble::kTopRight];
80     [[self bubble] setBackgroundColor:
81         gfx::SkColorToCalibratedNSColor(nativeTheme->GetSystemColor(
82             ui::NativeTheme::kColorId_DialogBackground))];
84     if (!g_animations_enabled)
85       [window setAllowedAnimations:info_bubble::kAnimateNone];
87     [self layout];
88   }
89   return self;
92 + (void)setAnimationEnabledForTesting:(BOOL)enabled {
93   g_animations_enabled = enabled;
96 - (IBAction)showWindow:(id)sender {
97   delegate_->OnBubbleShown();
98   [super showWindow:sender];
101 // Private /////////////////////////////////////////////////////////////////////
103 - (void)windowWillClose:(NSNotification*)notification {
104   if (!acknowledged_) {
105     delegate_->OnBubbleClosed(
106         ToolbarActionsBarBubbleDelegate::CLOSE_DISMISS);
107     acknowledged_ = YES;
108   }
109   [super windowWillClose:notification];
112 - (NSAttributedString*)attributedStringWithString:(const base::string16&)string
113                                          fontSize:(CGFloat)fontSize
114                                         alignment:(NSTextAlignment)alignment {
115   NSString* cocoaString = base::SysUTF16ToNSString(string);
116   base::scoped_nsobject<NSMutableParagraphStyle> paragraphStyle(
117       [[NSMutableParagraphStyle alloc] init]);
118   [paragraphStyle setAlignment:alignment];
119   NSDictionary* attributes = @{
120     NSFontAttributeName : [NSFont systemFontOfSize:fontSize],
121     NSForegroundColorAttributeName :
122         [NSColor colorWithCalibratedWhite:0.2 alpha:1.0],
123     NSParagraphStyleAttributeName : paragraphStyle.get()
124   };
125   return [[[NSAttributedString alloc] initWithString:cocoaString
126                                           attributes:attributes] autorelease];
129 - (NSTextField*)addTextFieldWithString:(const base::string16&)string
130                               fontSize:(CGFloat)fontSize
131                              alignment:(NSTextAlignment)alignment {
132   NSAttributedString* attributedString =
133       [self attributedStringWithString:string
134                               fontSize:fontSize
135                              alignment:alignment];
137   base::scoped_nsobject<NSTextField> textField(
138       [[NSTextField alloc] initWithFrame:NSZeroRect]);
139   [textField setEditable:NO];
140   [textField setBordered:NO];
141   [textField setDrawsBackground:NO];
142   [textField setAttributedStringValue:attributedString];
143   [[[self window] contentView] addSubview:textField];
144   [textField sizeToFit];
145   return textField.autorelease();
148 - (NSButton*)addButtonWithString:(const base::string16&)string {
149   NSButton* button = [[NSButton alloc] initWithFrame:NSZeroRect];
150   NSAttributedString* buttonString =
151       [self attributedStringWithString:string
152                               fontSize:13.0
153                              alignment:NSCenterTextAlignment];
154   [button setAttributedTitle:buttonString];
155   [button setBezelStyle:NSRoundedBezelStyle];
156   [button setTarget:self];
157   [button setAction:@selector(onButtonClicked:)];
158   [[[self window] contentView] addSubview:button];
159   [button sizeToFit];
160   return button;
163 - (void)layout {
164   // First, construct the pieces of the bubble that have a fixed width: the
165   // heading, and the button strip (the learn more link, the action button, and
166   // the dismiss button).
167   NSTextField* heading =
168       [self addTextFieldWithString:delegate_->GetHeadingText()
169                           fontSize:13.0
170                          alignment:NSLeftTextAlignment];
171   NSSize headingSize = [heading frame].size;
173   base::string16 learnMore = delegate_->GetLearnMoreButtonText();
174   NSSize learnMoreSize = NSZeroSize;
175   if (!learnMore.empty()) {  // The "learn more" link is optional.
176     NSAttributedString* learnMoreString =
177         [self attributedStringWithString:learnMore
178                                 fontSize:13.0
179                                alignment:NSLeftTextAlignment];
180     learnMoreButton_ =
181         [[HyperlinkButtonCell buttonWithString:learnMoreString.string] retain];
182     [learnMoreButton_ setTarget:self];
183     [learnMoreButton_ setAction:@selector(onButtonClicked:)];
184     [[[self window] contentView] addSubview:learnMoreButton_];
185     [learnMoreButton_ sizeToFit];
186     learnMoreSize = NSMakeSize(NSWidth([learnMoreButton_ frame]),
187                                NSHeight([learnMoreButton_ frame]));
188   }
190   base::string16 cancelStr = delegate_->GetDismissButtonText();
191   NSSize dismissButtonSize = NSZeroSize;
192   if (!cancelStr.empty()) {  // A cancel/dismiss button is optional.
193     dismissButton_ = [self addButtonWithString:cancelStr];
194     dismissButtonSize =
195         NSMakeSize(NSWidth([dismissButton_ frame]),
196                    NSHeight([dismissButton_ frame]));
197   }
199   base::string16 actionStr = delegate_->GetActionButtonText();
200   NSSize actionButtonSize = NSZeroSize;
201   if (!actionStr.empty()) {  // The action button is optional.
202     actionButton_ = [self addButtonWithString:actionStr];
203     actionButtonSize =
204         NSMakeSize(NSWidth([actionButton_ frame]),
205                    NSHeight([actionButton_ frame]));
206   }
208   DCHECK(actionButton_ || dismissButton_);
209   CGFloat buttonStripHeight =
210       std::max(actionButtonSize.height, dismissButtonSize.height);
212   const CGFloat kButtonPadding = 5.0;
213   CGFloat buttonStripWidth = 0;
214   if (actionButton_)
215     buttonStripWidth += actionButtonSize.width + kButtonPadding;
216   if (dismissButton_)
217     buttonStripWidth += dismissButtonSize.width + kButtonPadding;
218   if (learnMoreButton_)
219     buttonStripWidth += learnMoreSize.width + kButtonPadding;
221   CGFloat headingWidth = headingSize.width + 50.0;
223   CGFloat windowWidth = std::max(buttonStripWidth, headingWidth);
225   NSTextField* content =
226       [self addTextFieldWithString:delegate_->GetBodyText()
227                           fontSize:12.0
228                          alignment:NSLeftTextAlignment];
229   [content setFrame:NSMakeRect(0, 0, windowWidth, 0)];
230   // The content should have the same (max) width as the heading, which means
231   // the text will most likely wrap.
232   NSSize contentSize = NSMakeSize(windowWidth,
233                                   [GTMUILocalizerAndLayoutTweaker
234                                        sizeToFitFixedWidthTextField:content]);
236   const CGFloat kItemListIndentation = 10.0;
237   base::string16 itemListStr = delegate_->GetItemListText();
238   NSSize itemListSize;
239   if (!itemListStr.empty()) {
240     itemList_ =
241         [self addTextFieldWithString:itemListStr
242                             fontSize:12.0
243                            alignment:NSLeftTextAlignment];
244     CGFloat listWidth = windowWidth - kItemListIndentation;
245     [itemList_ setFrame:NSMakeRect(0, 0, listWidth, 0)];
246     itemListSize = NSMakeSize(listWidth,
247                               [GTMUILocalizerAndLayoutTweaker
248                                    sizeToFitFixedWidthTextField:itemList_]);
249   }
251   const CGFloat kHorizontalPadding = 15.0;
252   const CGFloat kVerticalPadding = 10.0;
254   // Next, we set frame for all the different pieces of the bubble, from bottom
255   // to top.
256   windowWidth += kHorizontalPadding * 2;
257   CGFloat currentHeight = kVerticalPadding;
258   CGFloat currentMaxWidth = windowWidth - kHorizontalPadding;
259   if (actionButton_) {
260     [actionButton_ setFrame:NSMakeRect(
261         currentMaxWidth - actionButtonSize.width,
262         currentHeight,
263         actionButtonSize.width,
264         actionButtonSize.height)];
265     currentMaxWidth -= (actionButtonSize.width + kButtonPadding);
266   }
267   if (dismissButton_) {
268     [dismissButton_ setFrame:NSMakeRect(
269         currentMaxWidth - dismissButtonSize.width,
270         currentHeight,
271         dismissButtonSize.width,
272         dismissButtonSize.height)];
273     currentMaxWidth -= (dismissButtonSize.width + kButtonPadding);
274   }
275   if (learnMoreButton_) {
276     CGFloat learnMoreHeight =
277         currentHeight + (buttonStripHeight - learnMoreSize.height) / 2.0;
278     [learnMoreButton_ setFrame:NSMakeRect(kHorizontalPadding,
279                                           learnMoreHeight,
280                                           learnMoreSize.width,
281                                           learnMoreSize.height)];
282   }
283   // Buttons have some inherit padding of their own, so we don't need quite as
284   // much space here.
285   currentHeight += buttonStripHeight + kVerticalPadding / 2;
287   if (itemList_) {
288     [itemList_ setFrame:NSMakeRect(kHorizontalPadding + kItemListIndentation,
289                                    currentHeight,
290                                    itemListSize.width,
291                                    itemListSize.height)];
292     currentHeight += itemListSize.height + kVerticalPadding;
293   }
295   [content setFrame:NSMakeRect(kHorizontalPadding,
296                                currentHeight,
297                                contentSize.width,
298                                contentSize.height)];
299   currentHeight += contentSize.height + kVerticalPadding;
300   [heading setFrame:NSMakeRect(kHorizontalPadding,
301                                currentHeight,
302                                headingSize.width,
303                                headingSize.height)];
305   // Update window frame.
306   NSRect windowFrame = [[self window] frame];
307   NSSize windowSize =
308       NSMakeSize(windowWidth,
309                  currentHeight + headingSize.height + kVerticalPadding * 2);
310   // We need to convert the size to be in the window's coordinate system. Since
311   // all we're doing is converting a size, and all views within a window share
312   // the same size metrics, it's okay that the size calculation came from
313   // multiple different views. Pick a view to convert it.
314   windowSize = [heading convertSize:windowSize toView:nil];
315   windowFrame.size = windowSize;
316   [[self window] setFrame:windowFrame display:YES];
319 - (void)onButtonClicked:(id)sender {
320   if (acknowledged_)
321     return;
322   ToolbarActionsBarBubbleDelegate::CloseAction action =
323       ToolbarActionsBarBubbleDelegate::CLOSE_EXECUTE;
324   if (learnMoreButton_ && sender == learnMoreButton_) {
325     action = ToolbarActionsBarBubbleDelegate::CLOSE_LEARN_MORE;
326   } else if (dismissButton_ && sender == dismissButton_) {
327     action = ToolbarActionsBarBubbleDelegate::CLOSE_DISMISS;
328   } else {
329     DCHECK_EQ(sender, actionButton_);
330     action = ToolbarActionsBarBubbleDelegate::CLOSE_EXECUTE;
331   }
332   acknowledged_ = YES;
333   delegate_->OnBubbleClosed(action);
334   [self close];
337 @end