BookmarkManager: Fix 'new folder text field size changes on clicking it' issue.
[chromium-blink-merge.git] / chrome / browser / ui / cocoa / extensions / toolbar_actions_bar_bubble_mac.mm
blob5e01460438093685a2acff085841b3047dff5212
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;
22 CGFloat kMinWidth = 320.0;
25 @interface ToolbarActionsBarBubbleMac ()
27 // Handles the notification that the window will close.
28 - (void)windowWillClose:(NSNotification*)notification;
30 // Creates and returns an NSAttributed string with the specified size and
31 // alignment.
32 - (NSAttributedString*)attributedStringWithString:(const base::string16&)string
33                                          fontSize:(CGFloat)fontSize
34                                         alignment:(NSTextAlignment)alignment;
36 // Creates an NSTextField with the given string, size, and alignment, and adds
37 // it to the window.
38 - (NSTextField*)addTextFieldWithString:(const base::string16&)string
39                               fontSize:(CGFloat)fontSize
40                              alignment:(NSTextAlignment)alignment;
42 // Creates an ExtensionMessagebubbleButton the given string id, and adds it to
43 // the window.
44 - (NSButton*)addButtonWithString:(const base::string16&)string;
46 // Initializes the bubble's content.
47 - (void)layout;
49 // Handles a button being clicked.
50 - (void)onButtonClicked:(id)sender;
52 @end
54 @implementation ToolbarActionsBarBubbleMac
56 @synthesize actionButton = actionButton_;
57 @synthesize itemList = itemList_;
58 @synthesize dismissButton = dismissButton_;
59 @synthesize learnMoreButton = learnMoreButton_;
61 - (id)initWithParentWindow:(NSWindow*)parentWindow
62                anchorPoint:(NSPoint)anchorPoint
63                   delegate:(scoped_ptr<ToolbarActionsBarBubbleDelegate>)
64                                delegate {
65   base::scoped_nsobject<InfoBubbleWindow> window(
66       [[InfoBubbleWindow alloc]
67           initWithContentRect:ui::kWindowSizeDeterminedLater
68                     styleMask:NSBorderlessWindowMask
69                       backing:NSBackingStoreBuffered
70                         defer:NO]);
71   if ((self = [super initWithWindow:window
72                        parentWindow:parentWindow
73                          anchoredAt:anchorPoint])) {
74     acknowledged_ = 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];
89     [[self window] makeFirstResponder:
90         (actionButton_ ? actionButton_ : dismissButton_)];
91   }
92   return self;
95 + (void)setAnimationEnabledForTesting:(BOOL)enabled {
96   g_animations_enabled = enabled;
99 - (IBAction)showWindow:(id)sender {
100   delegate_->OnBubbleShown();
101   [super showWindow:sender];
104 // Private /////////////////////////////////////////////////////////////////////
106 - (void)windowWillClose:(NSNotification*)notification {
107   if (!acknowledged_) {
108     delegate_->OnBubbleClosed(
109         ToolbarActionsBarBubbleDelegate::CLOSE_DISMISS);
110     acknowledged_ = YES;
111   }
112   [super windowWillClose:notification];
115 - (NSAttributedString*)attributedStringWithString:(const base::string16&)string
116                                          fontSize:(CGFloat)fontSize
117                                         alignment:(NSTextAlignment)alignment {
118   NSString* cocoaString = base::SysUTF16ToNSString(string);
119   base::scoped_nsobject<NSMutableParagraphStyle> paragraphStyle(
120       [[NSMutableParagraphStyle alloc] init]);
121   [paragraphStyle setAlignment:alignment];
122   NSDictionary* attributes = @{
123     NSFontAttributeName : [NSFont systemFontOfSize:fontSize],
124     NSForegroundColorAttributeName :
125         [NSColor colorWithCalibratedWhite:0.2 alpha:1.0],
126     NSParagraphStyleAttributeName : paragraphStyle.get()
127   };
128   return [[[NSAttributedString alloc] initWithString:cocoaString
129                                           attributes:attributes] autorelease];
132 - (NSTextField*)addTextFieldWithString:(const base::string16&)string
133                               fontSize:(CGFloat)fontSize
134                              alignment:(NSTextAlignment)alignment {
135   NSAttributedString* attributedString =
136       [self attributedStringWithString:string
137                               fontSize:fontSize
138                              alignment:alignment];
140   base::scoped_nsobject<NSTextField> textField(
141       [[NSTextField alloc] initWithFrame:NSZeroRect]);
142   [textField setEditable:NO];
143   [textField setBordered:NO];
144   [textField setDrawsBackground:NO];
145   [textField setAttributedStringValue:attributedString];
146   [[[self window] contentView] addSubview:textField];
147   [textField sizeToFit];
148   return textField.autorelease();
151 - (NSButton*)addButtonWithString:(const base::string16&)string {
152   NSButton* button = [[NSButton alloc] initWithFrame:NSZeroRect];
153   NSAttributedString* buttonString =
154       [self attributedStringWithString:string
155                               fontSize:13.0
156                              alignment:NSCenterTextAlignment];
157   [button setAttributedTitle:buttonString];
158   [button setBezelStyle:NSRoundedBezelStyle];
159   [button setTarget:self];
160   [button setAction:@selector(onButtonClicked:)];
161   [[[self window] contentView] addSubview:button];
162   [button sizeToFit];
163   return button;
166 - (void)layout {
167   // First, construct the pieces of the bubble that have a fixed width: the
168   // heading, and the button strip (the learn more link, the action button, and
169   // the dismiss button).
170   NSTextField* heading =
171       [self addTextFieldWithString:delegate_->GetHeadingText()
172                           fontSize:13.0
173                          alignment:NSLeftTextAlignment];
174   NSSize headingSize = [heading frame].size;
176   base::string16 learnMore = delegate_->GetLearnMoreButtonText();
177   NSSize learnMoreSize = NSZeroSize;
178   if (!learnMore.empty()) {  // The "learn more" link is optional.
179     NSAttributedString* learnMoreString =
180         [self attributedStringWithString:learnMore
181                                 fontSize:13.0
182                                alignment:NSLeftTextAlignment];
183     learnMoreButton_ =
184         [[HyperlinkButtonCell buttonWithString:learnMoreString.string] retain];
185     [learnMoreButton_ setTarget:self];
186     [learnMoreButton_ setAction:@selector(onButtonClicked:)];
187     [[[self window] contentView] addSubview:learnMoreButton_];
188     [learnMoreButton_ sizeToFit];
189     learnMoreSize = NSMakeSize(NSWidth([learnMoreButton_ frame]),
190                                NSHeight([learnMoreButton_ frame]));
191   }
193   base::string16 cancelStr = delegate_->GetDismissButtonText();
194   NSSize dismissButtonSize = NSZeroSize;
195   if (!cancelStr.empty()) {  // A cancel/dismiss button is optional.
196     dismissButton_ = [self addButtonWithString:cancelStr];
197     dismissButtonSize =
198         NSMakeSize(NSWidth([dismissButton_ frame]),
199                    NSHeight([dismissButton_ frame]));
200   }
202   base::string16 actionStr = delegate_->GetActionButtonText();
203   NSSize actionButtonSize = NSZeroSize;
204   if (!actionStr.empty()) {  // The action button is optional.
205     actionButton_ = [self addButtonWithString:actionStr];
206     actionButtonSize =
207         NSMakeSize(NSWidth([actionButton_ frame]),
208                    NSHeight([actionButton_ frame]));
209   }
211   DCHECK(actionButton_ || dismissButton_);
212   CGFloat buttonStripHeight =
213       std::max(actionButtonSize.height, dismissButtonSize.height);
215   const CGFloat kButtonPadding = 5.0;
216   CGFloat buttonStripWidth = 0;
217   if (actionButton_)
218     buttonStripWidth += actionButtonSize.width + kButtonPadding;
219   if (dismissButton_)
220     buttonStripWidth += dismissButtonSize.width + kButtonPadding;
221   if (learnMoreButton_)
222     buttonStripWidth += learnMoreSize.width + kButtonPadding;
224   CGFloat headingWidth = headingSize.width;
225   CGFloat windowWidth =
226       std::max(std::max(kMinWidth, buttonStripWidth), headingWidth);
228   NSTextField* content =
229       [self addTextFieldWithString:delegate_->GetBodyText()
230                           fontSize:12.0
231                          alignment:NSLeftTextAlignment];
232   [content setFrame:NSMakeRect(0, 0, windowWidth, 0)];
233   // The content should have the same (max) width as the heading, which means
234   // the text will most likely wrap.
235   NSSize contentSize = NSMakeSize(windowWidth,
236                                   [GTMUILocalizerAndLayoutTweaker
237                                        sizeToFitFixedWidthTextField:content]);
239   const CGFloat kItemListIndentation = 10.0;
240   base::string16 itemListStr = delegate_->GetItemListText();
241   NSSize itemListSize;
242   if (!itemListStr.empty()) {
243     itemList_ =
244         [self addTextFieldWithString:itemListStr
245                             fontSize:12.0
246                            alignment:NSLeftTextAlignment];
247     CGFloat listWidth = windowWidth - kItemListIndentation;
248     [itemList_ setFrame:NSMakeRect(0, 0, listWidth, 0)];
249     itemListSize = NSMakeSize(listWidth,
250                               [GTMUILocalizerAndLayoutTweaker
251                                    sizeToFitFixedWidthTextField:itemList_]);
252   }
254   const CGFloat kHorizontalPadding = 15.0;
255   const CGFloat kVerticalPadding = 10.0;
257   // Next, we set frame for all the different pieces of the bubble, from bottom
258   // to top.
259   windowWidth += kHorizontalPadding * 2;
260   CGFloat currentHeight = kVerticalPadding;
261   CGFloat currentMaxWidth = windowWidth - kHorizontalPadding;
262   if (actionButton_) {
263     [actionButton_ setFrame:NSMakeRect(
264         currentMaxWidth - actionButtonSize.width,
265         currentHeight,
266         actionButtonSize.width,
267         actionButtonSize.height)];
268     currentMaxWidth -= (actionButtonSize.width + kButtonPadding);
269   }
270   if (dismissButton_) {
271     [dismissButton_ setFrame:NSMakeRect(
272         currentMaxWidth - dismissButtonSize.width,
273         currentHeight,
274         dismissButtonSize.width,
275         dismissButtonSize.height)];
276     currentMaxWidth -= (dismissButtonSize.width + kButtonPadding);
277   }
278   if (learnMoreButton_) {
279     CGFloat learnMoreHeight =
280         currentHeight + (buttonStripHeight - learnMoreSize.height) / 2.0;
281     [learnMoreButton_ setFrame:NSMakeRect(kHorizontalPadding,
282                                           learnMoreHeight,
283                                           learnMoreSize.width,
284                                           learnMoreSize.height)];
285   }
286   // Buttons have some inherit padding of their own, so we don't need quite as
287   // much space here.
288   currentHeight += buttonStripHeight + kVerticalPadding / 2;
290   if (itemList_) {
291     [itemList_ setFrame:NSMakeRect(kHorizontalPadding + kItemListIndentation,
292                                    currentHeight,
293                                    itemListSize.width,
294                                    itemListSize.height)];
295     currentHeight += itemListSize.height + kVerticalPadding;
296   }
298   [content setFrame:NSMakeRect(kHorizontalPadding,
299                                currentHeight,
300                                contentSize.width,
301                                contentSize.height)];
302   currentHeight += contentSize.height + kVerticalPadding;
303   [heading setFrame:NSMakeRect(kHorizontalPadding,
304                                currentHeight,
305                                headingSize.width,
306                                headingSize.height)];
308   // Update window frame.
309   NSRect windowFrame = [[self window] frame];
310   NSSize windowSize =
311       NSMakeSize(windowWidth,
312                  currentHeight + headingSize.height + kVerticalPadding * 2);
313   // We need to convert the size to be in the window's coordinate system. Since
314   // all we're doing is converting a size, and all views within a window share
315   // the same size metrics, it's okay that the size calculation came from
316   // multiple different views. Pick a view to convert it.
317   windowSize = [heading convertSize:windowSize toView:nil];
318   windowFrame.size = windowSize;
319   [[self window] setFrame:windowFrame display:YES];
322 - (void)onButtonClicked:(id)sender {
323   if (acknowledged_)
324     return;
325   ToolbarActionsBarBubbleDelegate::CloseAction action =
326       ToolbarActionsBarBubbleDelegate::CLOSE_EXECUTE;
327   if (learnMoreButton_ && sender == learnMoreButton_) {
328     action = ToolbarActionsBarBubbleDelegate::CLOSE_LEARN_MORE;
329   } else if (dismissButton_ && sender == dismissButton_) {
330     action = ToolbarActionsBarBubbleDelegate::CLOSE_DISMISS;
331   } else {
332     DCHECK_EQ(sender, actionButton_);
333     action = ToolbarActionsBarBubbleDelegate::CLOSE_EXECUTE;
334   }
335   acknowledged_ = YES;
336   delegate_->OnBubbleClosed(action);
337   [self close];
340 @end