BookmarkManager: Fix 'new folder text field size changes on clicking it' issue.
[chromium-blink-merge.git] / chrome / browser / ui / cocoa / exclusive_access_bubble_window_controller.mm
blob6d68353840d803a863a4127552b0e80f4fc7986e
1 // Copyright (c) 2012 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 <Cocoa/Cocoa.h>
7 #include "base/logging.h"  // for NOTREACHED()
8 #include "base/mac/bundle_locations.h"
9 #include "base/strings/sys_string_conversions.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "chrome/app/chrome_command_ids.h"
12 #include "chrome/browser/profiles/profile.h"
13 #include "chrome/browser/ui/browser_commands.h"
14 #import "chrome/browser/ui/cocoa/exclusive_access_bubble_window_controller.h"
15 #import "chrome/browser/ui/cocoa/info_bubble_view.h"
16 #import "chrome/browser/ui/cocoa/info_bubble_window.h"
17 #import "chrome/browser/ui/cocoa/tabs/tab_window_controller.h"
18 #include "chrome/browser/ui/exclusive_access/exclusive_access_bubble_type.h"
19 #include "chrome/browser/ui/exclusive_access/exclusive_access_manager.h"
20 #include "chrome/browser/ui/exclusive_access/fullscreen_controller.h"
21 #include "chrome/grit/generated_resources.h"
22 #include "extensions/browser/extension_registry.h"
23 #import "third_party/google_toolbox_for_mac/src/AppKit/GTMNSAnimation+Duration.h"
24 #include "third_party/google_toolbox_for_mac/src/AppKit/GTMUILocalizerAndLayoutTweaker.h"
25 #import "third_party/google_toolbox_for_mac/src/AppKit/GTMUILocalizerAndLayoutTweaker.h"
26 #include "ui/base/accelerators/platform_accelerator_cocoa.h"
27 #import "ui/base/cocoa/controls/hyperlink_text_view.h"
28 #include "ui/base/l10n/l10n_util.h"
29 #include "ui/base/l10n/l10n_util_mac.h"
30 #include "ui/strings/grit/ui_strings.h"
32 namespace {
33 const float kInitialDelay = 3.8;
34 const float kHideDuration = 0.7;
35 }  // namespace
37 @interface OneClickHyperlinkTextView : HyperlinkTextView
38 @end
39 @implementation OneClickHyperlinkTextView
40 - (BOOL)acceptsFirstMouse:(NSEvent*)event {
41   return YES;
43 @end
45 @interface ExclusiveAccessBubbleWindowController (PrivateMethods)
46 // Sets |exitLabel_| based on |exitLabelPlaceholder_|,
47 // sets |exitLabelPlaceholder_| to nil,
48 // sets |denyButton_| text based on |bubbleType_|.
49 - (void)initializeLabelAndButton;
51 - (NSString*)getLabelText;
53 - (void)hideSoon;
55 // Returns the Accelerator for the Toggle Fullscreen menu item.
56 + (scoped_ptr<ui::PlatformAcceleratorCocoa>)acceleratorForToggleFullscreen;
58 // Returns a string representation fit for display of
59 // +acceleratorForToggleFullscreen.
60 + (NSString*)keyCommandString;
62 + (NSString*)keyCombinationForAccelerator:
63         (const ui::PlatformAcceleratorCocoa&)item;
64 @end
66 @implementation ExclusiveAccessBubbleWindowController
68 - (id)initWithOwner:(NSWindowController*)owner
69     exclusive_access_manager:(ExclusiveAccessManager*)exclusive_access_manager
70                      profile:(Profile*)profile
71                          url:(const GURL&)url
72                   bubbleType:(ExclusiveAccessBubbleType)bubbleType {
73   NSString* nibPath =
74       [base::mac::FrameworkBundle() pathForResource:@"ExclusiveAccessBubble"
75                                              ofType:@"nib"];
76   if ((self = [super initWithWindowNibPath:nibPath owner:self])) {
77     exclusive_access_manager_ = exclusive_access_manager;
78     profile_ = profile;
79     owner_ = owner;
80     url_ = url;
81     bubbleType_ = bubbleType;
82     // Mouse lock expects mouse events to reach the main window immediately.
83     // Make the bubble transparent for mouse events if mouse lock is enabled.
84     if (bubbleType_ ==
85             EXCLUSIVE_ACCESS_BUBBLE_TYPE_FULLSCREEN_MOUSELOCK_EXIT_INSTRUCTION ||
86         bubbleType_ == EXCLUSIVE_ACCESS_BUBBLE_TYPE_MOUSELOCK_EXIT_INSTRUCTION)
87       [[self window] setIgnoresMouseEvents:YES];
88   }
89   return self;
92 - (void)allow:(id)sender {
93   // The mouselock code expects that mouse events reach the main window
94   // immediately, but the cursor is still over the bubble, which eats the
95   // mouse events. Make the bubble transparent for mouse events.
96   if (bubbleType_ ==
97           EXCLUSIVE_ACCESS_BUBBLE_TYPE_FULLSCREEN_MOUSELOCK_BUTTONS ||
98       bubbleType_ == EXCLUSIVE_ACCESS_BUBBLE_TYPE_MOUSELOCK_BUTTONS)
99     [[self window] setIgnoresMouseEvents:YES];
101   DCHECK(exclusive_access_bubble::ShowButtonsForType(bubbleType_));
102   exclusive_access_manager_->OnAcceptExclusiveAccessPermission();
105 - (void)deny:(id)sender {
106   DCHECK(exclusive_access_bubble::ShowButtonsForType(bubbleType_));
107   exclusive_access_manager_->OnDenyExclusiveAccessPermission();
110 - (void)showButtons:(BOOL)show {
111   [allowButton_ setHidden:!show];
112   [denyButton_ setHidden:!show];
113   [exitLabel_ setHidden:show];
116 // We want this to be a child of a browser window.  addChildWindow:
117 // (called from this function) will bring the window on-screen;
118 // unfortunately, [NSWindowController showWindow:] will also bring it
119 // on-screen (but will cause unexpected changes to the window's
120 // position).  We cannot have an addChildWindow: and a subsequent
121 // showWindow:. Thus, we have our own version.
122 - (void)showWindow {
123   // Completes nib load.
124   InfoBubbleWindow* info_bubble = static_cast<InfoBubbleWindow*>([self window]);
125   [info_bubble setInfoBubbleCanBecomeKeyWindow:NO];
126   if (!exclusive_access_bubble::ShowButtonsForType(bubbleType_)) {
127     [self hideSoon];
128   }
129   [tweaker_ tweakUI:info_bubble];
130   [[owner_ window] addChildWindow:info_bubble ordered:NSWindowAbove];
132   if ([owner_ respondsToSelector:@selector(layoutSubviews)])
133     [(id)owner_ layoutSubviews];
135   [info_bubble orderFront:self];
138 - (void)awakeFromNib {
139   DCHECK([[self window] isKindOfClass:[InfoBubbleWindow class]]);
140   [messageLabel_ setStringValue:[self getLabelText]];
141   [self initializeLabelAndButton];
144 - (void)positionInWindowAtTop:(CGFloat)maxY width:(CGFloat)maxWidth {
145   NSRect windowFrame = [self window].frame;
146   NSRect ownerWindowFrame = [owner_ window].frame;
147   NSPoint origin;
148   origin.x = ownerWindowFrame.origin.x +
149              (int)(NSWidth(ownerWindowFrame) / 2 - NSWidth(windowFrame) / 2);
150   origin.y = ownerWindowFrame.origin.y + maxY - NSHeight(windowFrame);
151   [[self window] setFrameOrigin:origin];
154 // Called when someone clicks on the embedded link.
155 - (BOOL)textView:(NSTextView*)textView
156     clickedOnLink:(id)link
157           atIndex:(NSUInteger)charIndex {
158   exclusive_access_manager_->fullscreen_controller()
159       ->ExitExclusiveAccessToPreviousState();
160   return YES;
163 - (void)hideTimerFired:(NSTimer*)timer {
164   // This might fire racily for buttoned bubbles, even though the timer is
165   // cancelled for them. Explicitly check for this case.
166   if (exclusive_access_bubble::ShowButtonsForType(bubbleType_))
167     return;
169   [NSAnimationContext beginGrouping];
170   [[NSAnimationContext currentContext]
171       gtm_setDuration:kHideDuration
172             eventMask:NSLeftMouseUpMask | NSLeftMouseDownMask];
173   [[[self window] animator] setAlphaValue:0.0];
174   [NSAnimationContext endGrouping];
177 - (void)animationDidEnd:(NSAnimation*)animation {
178   if (animation == hideAnimation_.get()) {
179     hideAnimation_.reset();
180   }
183 - (void)closeImmediately {
184   // Without this, quitting fullscreen with esc will let the bubble reappear
185   // once the "exit fullscreen" animation is done on lion.
186   InfoBubbleWindow* infoBubble = static_cast<InfoBubbleWindow*>([self window]);
187   [[infoBubble parentWindow] removeChildWindow:infoBubble];
188   [hideAnimation_.get() stopAnimation];
189   [hideTimer_ invalidate];
190   [infoBubble setAllowedAnimations:info_bubble::kAnimateNone];
191   [self close];
194 - (void)dealloc {
195   [hideAnimation_.get() stopAnimation];
196   [hideTimer_ invalidate];
197   [super dealloc];
200 @end
202 @implementation ExclusiveAccessBubbleWindowController (PrivateMethods)
204 - (void)initializeLabelAndButton {
205   // Replace the label placeholder NSTextField with the real label NSTextView.
206   // The former doesn't show links in a nice way, but the latter can't be added
207   // in IB without a containing scroll view, so create the NSTextView
208   // programmatically.
209   exitLabel_.reset([[OneClickHyperlinkTextView alloc]
210       initWithFrame:[exitLabelPlaceholder_ frame]]);
211   [exitLabel_.get()
212       setAutoresizingMask:[exitLabelPlaceholder_ autoresizingMask]];
213   [exitLabel_.get() setHidden:[exitLabelPlaceholder_ isHidden]];
214   [[exitLabelPlaceholder_ superview] replaceSubview:exitLabelPlaceholder_
215                                                with:exitLabel_.get()];
216   exitLabelPlaceholder_ = nil;  // Now released.
217   [exitLabel_.get() setDelegate:self];
219   NSString* exitLinkText;
220   NSString* exitLinkedText;
221   if (bubbleType_ ==
222           EXCLUSIVE_ACCESS_BUBBLE_TYPE_FULLSCREEN_MOUSELOCK_EXIT_INSTRUCTION ||
223       bubbleType_ == EXCLUSIVE_ACCESS_BUBBLE_TYPE_MOUSELOCK_EXIT_INSTRUCTION) {
224     exitLinkText = @"";
225     exitLinkedText =
226         [@" " stringByAppendingString:l10n_util::GetNSStringF(
227                                           IDS_FULLSCREEN_PRESS_ESC_TO_EXIT,
228                                           l10n_util::GetStringUTF16(
229                                               IDS_APP_ESC_KEY))];
230   } else {
231     exitLinkText = l10n_util::GetNSString(IDS_EXIT_FULLSCREEN_MODE);
232     NSString* messageText = l10n_util::GetNSStringF(
233                                    IDS_EXIT_FULLSCREEN_MODE_ACCELERATOR,
234                                    l10n_util::GetStringUTF16(IDS_APP_ESC_KEY));
235     exitLinkedText =
236         [NSString stringWithFormat:@"%@ %@", exitLinkText, messageText];
237   }
239   NSFont* font = [NSFont
240       systemFontOfSize:[NSFont
241                            systemFontSizeForControlSize:NSRegularControlSize]];
242   [exitLabel_.get() setMessage:exitLinkedText
243                       withFont:font
244                   messageColor:[NSColor blackColor]];
245   if ([exitLinkText length] != 0) {
246     [exitLabel_.get() addLinkRange:NSMakeRange(0, [exitLinkText length])
247                           withName:@""
248                          linkColor:[NSColor blueColor]];
249   }
250   [exitLabel_.get() setAlignment:NSRightTextAlignment];
252   NSRect labelFrame = [exitLabel_ frame];
254   // NSTextView's sizeToFit: method seems to enjoy wrapping lines. Temporarily
255   // set the size large to force it not to.
256   NSRect windowFrame = [[self window] frame];
257   [exitLabel_ setFrameSize:windowFrame.size];
258   NSLayoutManager* layoutManager = [exitLabel_ layoutManager];
259   NSTextContainer* textContainer = [exitLabel_ textContainer];
260   [layoutManager ensureLayoutForTextContainer:textContainer];
261   NSRect textFrame = [layoutManager usedRectForTextContainer:textContainer];
263   textFrame.size.width = ceil(NSWidth(textFrame));
264   labelFrame.origin.x += NSWidth(labelFrame) - NSWidth(textFrame);
265   labelFrame.size = textFrame.size;
266   [exitLabel_ setFrame:labelFrame];
268   // Update the title of |allowButton_| and |denyButton_| according to the
269   // current |bubbleType_|, or show no button at all.
270   if (exclusive_access_bubble::ShowButtonsForType(bubbleType_)) {
271     NSString* denyButtonText =
272       SysUTF16ToNSString(
273         exclusive_access_bubble::GetDenyButtonTextForType(bubbleType_));
274     [denyButton_ setTitle:denyButtonText];
275     NSString* allowButtonText = SysUTF16ToNSString(
276         exclusive_access_bubble::GetAllowButtonTextForType(bubbleType_, url_));
277     [allowButton_ setTitle:allowButtonText];
278   } else {
279     [self showButtons:NO];
280   }
283 - (NSString*)getLabelText {
284   if (bubbleType_ == EXCLUSIVE_ACCESS_BUBBLE_TYPE_NONE)
285     return @"";
286   extensions::ExtensionRegistry* registry =
287       extensions::ExtensionRegistry::Get(profile_);
288   return SysUTF16ToNSString(exclusive_access_bubble::GetLabelTextForType(
289       bubbleType_, url_, registry));
292 // This looks at the Main Menu and determines what the user has set as the
293 // key combination for quit. It then gets the modifiers and builds an object
294 // to hold the data.
295 + (scoped_ptr<ui::PlatformAcceleratorCocoa>)acceleratorForToggleFullscreen {
296   NSMenu* mainMenu = [NSApp mainMenu];
297   // Get the application menu (i.e. Chromium).
298   for (NSMenuItem* menu in [mainMenu itemArray]) {
299     for (NSMenuItem* item in [[menu submenu] itemArray]) {
300       // Find the toggle presentation mode item.
301       if ([item tag] == IDC_PRESENTATION_MODE) {
302         return scoped_ptr<ui::PlatformAcceleratorCocoa>(
303             new ui::PlatformAcceleratorCocoa([item keyEquivalent],
304                                              [item keyEquivalentModifierMask]));
305       }
306     }
307   }
308   // Default to Cmd+Shift+F.
309   return scoped_ptr<ui::PlatformAcceleratorCocoa>(
310       new ui::PlatformAcceleratorCocoa(@"f",
311                                        NSCommandKeyMask | NSShiftKeyMask));
314 // This looks at the Main Menu and determines what the user has set as the
315 // key combination for quit. It then gets the modifiers and builds a string
316 // to display them.
317 + (NSString*)keyCommandString {
318   scoped_ptr<ui::PlatformAcceleratorCocoa> accelerator(
319       [[self class] acceleratorForToggleFullscreen]);
320   return [[self class] keyCombinationForAccelerator:*accelerator];
323 + (NSString*)keyCombinationForAccelerator:
324         (const ui::PlatformAcceleratorCocoa&)item {
325   NSMutableString* string = [NSMutableString string];
326   NSUInteger modifiers = item.modifier_mask();
328   if (modifiers & NSCommandKeyMask)
329     [string appendString:@"\u2318"];
330   if (modifiers & NSControlKeyMask)
331     [string appendString:@"\u2303"];
332   if (modifiers & NSAlternateKeyMask)
333     [string appendString:@"\u2325"];
334   BOOL isUpperCase = [[NSCharacterSet uppercaseLetterCharacterSet]
335       characterIsMember:[item.characters() characterAtIndex:0]];
336   if (modifiers & NSShiftKeyMask || isUpperCase)
337     [string appendString:@"\u21E7"];
339   [string appendString:[item.characters() uppercaseString]];
340   return string;
343 - (void)hideSoon {
344   hideTimer_.reset(
345       [[NSTimer scheduledTimerWithTimeInterval:kInitialDelay
346                                         target:self
347                                       selector:@selector(hideTimerFired:)
348                                       userInfo:nil
349                                        repeats:NO] retain]);
352 @end