Don't show supervised user as "already on this device" while they're being imported.
[chromium-blink-merge.git] / chrome / browser / ui / cocoa / panels / panel_window_controller_cocoa.mm
blob06ce789dc186277dc55d387db02d803e0aa504a0
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 #include "chrome/browser/ui/cocoa/panels/panel_window_controller_cocoa.h"
7 #import <Cocoa/Cocoa.h>
9 #include "base/auto_reset.h"
10 #include "base/logging.h"
11 #include "base/mac/bundle_locations.h"
12 #include "base/mac/foundation_util.h"
13 #include "base/mac/scoped_nsautorelease_pool.h"
14 #include "base/strings/sys_string_conversions.h"
15 #include "chrome/app/chrome_command_ids.h"  // IDC_*
16 #include "chrome/browser/chrome_browser_application_mac.h"
17 #include "chrome/browser/profiles/profile.h"
18 #import "chrome/browser/ui/cocoa/browser_command_executor.h"
19 #import "chrome/browser/ui/cocoa/browser_window_utils.h"
20 #import "chrome/browser/ui/cocoa/panels/mouse_drag_controller.h"
21 #import "chrome/browser/ui/cocoa/panels/panel_cocoa.h"
22 #import "chrome/browser/ui/cocoa/panels/panel_titlebar_view_cocoa.h"
23 #import "chrome/browser/ui/cocoa/panels/panel_utils_cocoa.h"
24 #import "chrome/browser/ui/cocoa/sprite_view.h"
25 #import "chrome/browser/ui/cocoa/tab_contents/favicon_util_mac.h"
26 #import "chrome/browser/ui/cocoa/tab_contents/tab_contents_controller.h"
27 #include "chrome/browser/ui/panels/panel_bounds_animation.h"
28 #include "chrome/browser/ui/panels/panel_collection.h"
29 #include "chrome/browser/ui/panels/panel_constants.h"
30 #include "chrome/browser/ui/panels/panel_manager.h"
31 #include "chrome/browser/ui/panels/stacked_panel_collection.h"
32 #include "chrome/browser/ui/tabs/tab_strip_model.h"
33 #include "chrome/browser/ui/toolbar/encoding_menu_controller.h"
34 #include "content/public/browser/render_widget_host_view.h"
35 #include "content/public/browser/web_contents.h"
36 #include "ui/base/resource/resource_bundle.h"
37 #include "ui/gfx/image/image.h"
38 #include "ui/resources/grit/ui_resources.h"
40 using content::WebContents;
42 const int kMinimumWindowSize = 1;
43 const double kBoundsAnimationSpeedPixelsPerSecond = 1000;
44 const double kBoundsAnimationMaxDurationSeconds = 0.18;
46 // Edge thickness to trigger user resizing via system, in screen pixels.
47 const double kWidthOfMouseResizeArea = 15.0;
49 // Notification observer to prevent panels becoming key when windows are closed.
50 @interface WindowCloseWatcher : NSObject
51 - (void)windowWillClose:(NSNotification*)notification;
52 @end
54 @interface PanelWindowControllerCocoa (PanelsCanBecomeKey)
55 // Internal helper method for extracting the total number of panel windows
56 // from the panel manager. Used to decide if panel can become the key window.
57 - (int)numPanels;
58 @end
60 @implementation PanelWindowCocoaImpl
61 // The panels cannot be reduced to 3-px windows on the edge of the screen
62 // active area (above Dock). Default constraining logic makes at least a height
63 // of the titlebar visible, so the user could still grab it. We do 'restore'
64 // differently, and minimize panels to 3 px. Hence the need to override the
65 // constraining logic.
66 - (NSRect)constrainFrameRect:(NSRect)frameRect toScreen:(NSScreen *)screen {
67   return frameRect;
70 // Prevent panel window from becoming key - for example when it is minimized.
71 // Panel windows use a higher priority NSWindowLevel to ensure they are always
72 // visible, causing the OS to prefer panel windows when selecting a window
73 // to make the key window. To counter this preference, we override
74 // -[NSWindow:canBecomeKeyWindow] to restrict when the panel can become the
75 // key window to a limited set of scenarios, such as when cycling through
76 // windows, when panels are the only remaining windows, when an event
77 // triggers window activation, etc. The panel may also be prevented from
78 // becoming the key window, regardless of the above scenarios, such as when
79 // a panel is minimized.
80 - (BOOL)canBecomeKeyWindow {
81   // Give precedence to controller preventing activation of the window.
82   PanelWindowControllerCocoa* controller =
83       base::mac::ObjCCast<PanelWindowControllerCocoa>([self windowController]);
84   if (![controller canBecomeKeyWindow])
85     return NO;
87   BrowserCrApplication* app = base::mac::ObjCCast<BrowserCrApplication>(
88       [BrowserCrApplication sharedApplication]);
90   // A Panel window can become the key window only in limited scenarios.
91   // This prevents the system from always preferring a Panel window due
92   // to its higher priority NSWindowLevel when selecting a window to make key.
93   return ([app isHandlingSendEvent]  && [[app currentEvent] window] == self) ||
94       [controller activationRequestedByPanel] ||
95       [app isCyclingWindows] ||
96       [self isKeyWindow] ||
97       [app previousKeyWindow] == self ||
98       [[app windows] count] == static_cast<NSUInteger>([controller numPanels]);
101 - (void)performMiniaturize:(id)sender {
102   [[self windowController] minimizeButtonClicked:0];
105 - (void)mouseMoved:(NSEvent*)event {
106   // Cocoa does not support letting the application determine the edges that
107   // can trigger the user resizing. To work around this, we track the mouse
108   // location. When it is close to the edge/corner where the user resizing
109   // is not desired, we force the min and max size of the window to be same
110   // as current window size. For all other cases, we restore the min and max
111   // size.
112   PanelWindowControllerCocoa* controller =
113       base::mac::ObjCCast<PanelWindowControllerCocoa>([self windowController]);
114   NSRect frame = [self frame];
115   if ([controller canResizeByMouseAtCurrentLocation]) {
116     // Mac window server limits window sizes to 10000.
117     NSSize maxSize = NSMakeSize(10000, 10000);
119     // If the user is resizing a stacked panel by its bottom edge, make sure its
120     // height cannot grow more than what the panel below it could offer. This is
121     // because growing a stacked panel by y amount will shrink the panel below
122     // it by same amount and we do not want the panel below it being shrunk to
123     // be smaller than the titlebar.
124     Panel* panel = [controller panel];
125     NSPoint point = [NSEvent mouseLocation];
126     if (point.y < NSMinY(frame) + kWidthOfMouseResizeArea && panel->stack()) {
127       Panel* belowPanel = panel->stack()->GetPanelBelow(panel);
128       if (belowPanel && !belowPanel->IsMinimized()) {
129         maxSize.height = panel->GetBounds().height() +
130             belowPanel->GetBounds().height() - panel::kTitlebarHeight;
131       }
132     }
134     // Enable the user-resizing by setting both min and max size to the right
135     // values.
136     [self setMinSize:NSMakeSize(panel::kPanelMinWidth,
137                                 panel::kPanelMinHeight)];
138     [self setMaxSize:maxSize];
139   } else {
140     // Disable the user-resizing by setting both min and max size to be same as
141     // current window size.
142     [self setMinSize:frame.size];
143     [self setMaxSize:frame.size];
144   }
146   [super mouseMoved:event];
148 @end
150 // ChromeEventProcessingWindow expects its controller to implement the
151 // BrowserCommandExecutor protocol.
152 @interface PanelWindowControllerCocoa (InternalAPI) <BrowserCommandExecutor>
154 // BrowserCommandExecutor methods.
155 - (void)executeCommand:(int)command;
157 @end
159 @implementation PanelWindowControllerCocoa (InternalAPI)
161 // This gets called whenever a browser-specific keyboard shortcut is performed
162 // in the Panel window. We simply swallow all those events.
163 - (void)executeCommand:(int)command {}
165 @end
167 @implementation PanelWindowControllerCocoa
169 - (id)initWithPanel:(PanelCocoa*)window {
170   NSString* nibpath =
171       [base::mac::FrameworkBundle() pathForResource:@"Panel" ofType:@"nib"];
172   if ((self = [super initWithWindowNibPath:nibpath owner:self])) {
173     windowShim_.reset(window);
174     animateOnBoundsChange_ = YES;
175     canBecomeKeyWindow_ = YES;
176     activationRequestedByPanel_ = NO;
178     // Leaky singleton. Initialized when the first panel is created.
179     static WindowCloseWatcher* watcher = [[WindowCloseWatcher alloc] init];
180     (void)watcher;  // Suppress the unused variable warning.
181   }
182   return self;
185 - (Panel*)panel {
186   return windowShim_->panel();
189 - (void)awakeFromNib {
190   NSWindow* window = [self window];
192   DCHECK(window);
193   DCHECK(titlebar_view_);
194   DCHECK_EQ(self, [window delegate]);
196   [self updateWindowLevel];
198   [self updateWindowCollectionBehavior];
200   [titlebar_view_ attach];
202   // Set initial size of the window to match the size of the panel to give
203   // the renderer the proper size to work with earlier, avoiding a resize
204   // after the window is revealed.
205   gfx::Rect panelBounds = windowShim_->panel()->GetBounds();
206   NSRect frame = [window frame];
207   frame.size.width = panelBounds.width();
208   frame.size.height = panelBounds.height();
209   [window setFrame:frame display:NO];
211   // MacOS will turn the user-resizing to the user-dragging if the direction of
212   // the dragging is orthogonal to the direction of the arrow cursor. We do not
213   // want this since it will bypass our dragging logic. The panel window is
214   // still draggable because we track and handle the dragging in our custom way.
215   [[self window] setMovable:NO];
217   [self updateTrackingArea];
220 - (void)updateWebContentsViewFrame {
221   content::WebContents* webContents = windowShim_->panel()->GetWebContents();
222   if (!webContents)
223     return;
225   // Compute the size of the web contents view. Don't assume it's similar to the
226   // size of the contentView, because the contentView is managed by the Cocoa
227   // to be (window - standard titlebar), while we have taller custom titlebar
228   // instead. In coordinate system of window's contentView.
229   NSRect contentFrame = [self contentRectForFrameRect:[[self window] frame]];
230   contentFrame.origin = NSZeroPoint;
232   NSView* contentView = webContents->GetNativeView();
233   if (!NSEqualRects([contentView frame], contentFrame))
234     [contentView setFrame:contentFrame];
237 - (void)disableWebContentsViewAutosizing {
238   [[[self window] contentView] setAutoresizesSubviews:NO];
241 - (void)enableWebContentsViewAutosizing {
242   [self updateWebContentsViewFrame];
243   [[[self window] contentView] setAutoresizesSubviews:YES];
246 - (void)revealAnimatedWithFrame:(const NSRect&)frame {
247   NSWindow* window = [self window];  // This ensures loading the nib.
249   // Disable subview resizing while resizing the window to avoid renderer
250   // resizes during intermediate stages of animation.
251   [self disableWebContentsViewAutosizing];
253   // We grow the window from the bottom up to produce a 'reveal' animation.
254   NSRect startFrame = NSMakeRect(NSMinX(frame), NSMinY(frame),
255                                  NSWidth(frame), kMinimumWindowSize);
256   [window setFrame:startFrame display:NO animate:NO];
257   // Shows the window without making it key, on top of its layer, even if
258   // Chromium is not an active app.
259   [window orderFrontRegardless];
260   // TODO(dcheng): Temporary hack to work around the fact that
261   // orderFrontRegardless causes us to become the first responder. The usual
262   // Chrome assumption is that becoming the first responder = you have focus, so
263   // we always deactivate the controls here. If we're created as an active
264   // panel, we'll get a NSWindowDidBecomeKeyNotification and reactivate the web
265   // view properly. See crbug.com/97831 for more details.
266   WebContents* web_contents = windowShim_->panel()->GetWebContents();
267   // RWHV may be NULL in unit tests.
268   if (web_contents && web_contents->GetRenderWidgetHostView())
269     web_contents->GetRenderWidgetHostView()->SetActive(false);
271   // This will re-enable the content resizing after it finishes.
272   [self setPanelFrame:frame animate:YES];
275 - (void)updateTitleBar {
276   NSString* newTitle = base::SysUTF16ToNSString(
277       windowShim_->panel()->GetWindowTitle());
278   pendingWindowTitle_.reset(
279       [BrowserWindowUtils scheduleReplaceOldTitle:pendingWindowTitle_.get()
280                                      withNewTitle:newTitle
281                                         forWindow:[self window]]);
282   [titlebar_view_ setTitle:newTitle];
283   [self updateIcon];
286 - (void)updateIcon {
287   base::scoped_nsobject<NSView> iconView;
288   if (throbberShouldSpin_) {
289     // If the throbber is spinning now, no need to replace it.
290     if ([[titlebar_view_ icon] isKindOfClass:[SpriteView class]])
291       return;
293     NSImage* iconImage =
294         ResourceBundle::GetSharedInstance().GetNativeImageNamed(
295             IDR_THROBBER).ToNSImage();
296     SpriteView* spriteView = [[SpriteView alloc] init];
297     [spriteView setImage:iconImage];
298     iconView.reset(spriteView);
299   } else {
300     const gfx::Image& page_icon = windowShim_->panel()->GetCurrentPageIcon();
301     ResourceBundle& rb = ResourceBundle::GetSharedInstance();
302     NSRect iconFrame = [[titlebar_view_ icon] frame];
303     NSImage* iconImage = page_icon.IsEmpty() ?
304         rb.GetNativeImageNamed(IDR_DEFAULT_FAVICON).ToNSImage() :
305         page_icon.ToNSImage();
306     NSImageView* imageView = [[NSImageView alloc] initWithFrame:iconFrame];
307     [imageView setImage:iconImage];
308     iconView.reset(imageView);
309   }
310   [titlebar_view_ setIcon:iconView];
313 - (void)updateThrobber:(BOOL)shouldSpin {
314   if (throbberShouldSpin_ == shouldSpin)
315     return;
316   throbberShouldSpin_ = shouldSpin;
318   // If the titlebar view has not been attached, bail out.
319   if (!titlebar_view_)
320     return;
322   [self updateIcon];
325 - (void)updateTitleBarMinimizeRestoreButtonVisibility {
326   Panel* panel = windowShim_->panel();
327   [titlebar_view_ setMinimizeButtonVisibility:panel->CanShowMinimizeButton()];
328   [titlebar_view_ setRestoreButtonVisibility:panel->CanShowRestoreButton()];
331 - (void)webContentsInserted:(WebContents*)contents {
332   NSView* view = contents->GetNativeView();
333   [[[self window] contentView] addSubview:view];
334   [view setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
336   [self enableWebContentsViewAutosizing];
339 - (void)webContentsDetached:(WebContents*)contents {
340   [contents->GetNativeView() removeFromSuperview];
343 - (PanelTitlebarViewCocoa*)titlebarView {
344   return titlebar_view_;
347 // Called to validate menu and toolbar items when this window is key. All the
348 // items we care about have been set with the |-commandDispatch:|
349 // action and a target of FirstResponder in IB.
350 // Delegate to the NSApp delegate if Panel does not care about the command or
351 // shortcut, to make sure the global items in Chrome main app menu still work.
352 - (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item {
353   if ([item action] == @selector(commandDispatch:)) {
354     NSInteger tag = [item tag];
355     CommandUpdater* command_updater = windowShim_->panel()->command_updater();
356     if (command_updater->SupportsCommand(tag))
357       return command_updater->IsCommandEnabled(tag);
358     else
359       return [[NSApp delegate] validateUserInterfaceItem:item];
360   }
361   return NO;
364 // Called when the user picks a menu or toolbar item when this window is key.
365 // Calls through to the panel object to execute the command or delegates up.
366 - (void)commandDispatch:(id)sender {
367   DCHECK(sender);
368   NSInteger tag = [sender tag];
369   CommandUpdater* command_updater = windowShim_->panel()->command_updater();
370   if (command_updater->SupportsCommand(tag))
371     windowShim_->panel()->ExecuteCommandIfEnabled(tag);
372   else
373     [[NSApp delegate] commandDispatch:sender];
376 // Handler for the custom Close button.
377 - (void)closePanel {
378   windowShim_->panel()->Close();
381 // Handler for the custom Minimize button.
382 - (void)minimizeButtonClicked:(int)modifierFlags {
383   Panel* panel = windowShim_->panel();
384   panel->OnMinimizeButtonClicked((modifierFlags & NSShiftKeyMask) ?
385                                  panel::APPLY_TO_ALL : panel::NO_MODIFIER);
388 // Handler for the custom Restore button.
389 - (void)restoreButtonClicked:(int)modifierFlags {
390   Panel* panel = windowShim_->panel();
391   panel->OnRestoreButtonClicked((modifierFlags & NSShiftKeyMask) ?
392                                 panel::APPLY_TO_ALL : panel::NO_MODIFIER);
395 // Called when the user wants to close the panel or from the shutdown process.
396 // The Panel object is in control of whether or not we're allowed to close. It
397 // may defer closing due to several states, such as onbeforeUnload handlers
398 // needing to be fired. If closing is deferred, the Panel will handle the
399 // processing required to get us to the closing state and (by watching for
400 // the web content going away) will again call to close the window when it's
401 // finally ready.
402 - (BOOL)windowShouldClose:(id)sender {
403   Panel* panel = windowShim_->panel();
404   // Give beforeunload handlers the chance to cancel the close before we hide
405   // the window below.
406   if (!panel->ShouldCloseWindow())
407     return NO;
409   if (panel->GetWebContents()) {
410     // Terminate any playing animations.
411     [self terminateBoundsAnimation];
412     animateOnBoundsChange_ = NO;
413     // Make panel close the web content, allowing the renderer to shut down
414     // and call us back again.
415     panel->OnWindowClosing();
416     return NO;
417   }
419   // No web content; it's ok to close the window.
420   return YES;
423 // When windowShouldClose returns YES (or if controller receives direct 'close'
424 // signal), window will be unconditionally closed. Clean up.
425 - (void)windowWillClose:(NSNotification*)notification {
426   DCHECK(!windowShim_->panel()->GetWebContents());
427   // Avoid callbacks from a nonblocking animation in progress, if any.
428   [self terminateBoundsAnimation];
429   windowShim_->DidCloseNativeWindow();
430   // Call |-autorelease| after a zero-length delay to avoid deadlock from
431   // code in the current run loop that waits on PANEL_CLOSED notification.
432   // The notification is sent when this object is freed, but this object
433   // cannot be freed until the current run loop completes.
434   [self performSelector:@selector(autorelease)
435              withObject:nil
436              afterDelay:0];
439 - (void)startDrag:(NSPoint)mouseLocation {
440   // Convert from Cocoa's screen coordinates to platform-indepedent screen
441   // coordinates because PanelManager method takes platform-indepedent screen
442   // coordinates.
443   windowShim_->panel()->manager()->StartDragging(
444       windowShim_->panel(),
445       cocoa_utils::ConvertPointFromCocoaCoordinates(mouseLocation));
448 - (void)endDrag:(BOOL)cancelled {
449   windowShim_->panel()->manager()->EndDragging(cancelled);
452 - (void)drag:(NSPoint)mouseLocation {
453   // Convert from Cocoa's screen coordinates to platform-indepedent screen
454   // coordinates because PanelManager method takes platform-indepedent screen
455   // coordinates.
456   windowShim_->panel()->manager()->Drag(
457       cocoa_utils::ConvertPointFromCocoaCoordinates(mouseLocation));
460 - (void)setPanelFrame:(NSRect)frame
461               animate:(BOOL)animate {
462   BOOL jumpToDestination = (!animateOnBoundsChange_ || !animate);
464   // If no animation is in progress, apply bounds change instantly.
465   if (jumpToDestination && ![self isAnimatingBounds]) {
466     [[self window] setFrame:frame display:YES animate:NO];
467     return;
468   }
470   NSDictionary *windowResize = [NSDictionary dictionaryWithObjectsAndKeys:
471       [self window], NSViewAnimationTargetKey,
472       [NSValue valueWithRect:frame], NSViewAnimationEndFrameKey, nil];
473   NSArray *animations = [NSArray arrayWithObjects:windowResize, nil];
475   // If an animation is in progress, update the animation with new target
476   // bounds. Also, set the destination frame bounds to the new value.
477   if (jumpToDestination && [self isAnimatingBounds]) {
478     [boundsAnimation_ setViewAnimations:animations];
479     [[self window] setFrame:frame display:YES animate:NO];
480     return;
481   }
483   // Will be enabled back in animationDidEnd callback.
484   [self disableWebContentsViewAutosizing];
486   // Terminate previous animation, if it is still playing.
487   [self terminateBoundsAnimation];
489   boundsAnimation_ =
490       [[NSViewAnimation alloc] initWithViewAnimations:animations];
491   [boundsAnimation_ setDelegate:self];
493   NSRect currentFrame = [[self window] frame];
494   // Compute duration. We use constant speed of animation, however if the change
495   // is too large, we clip the duration (effectively increasing speed) to
496   // limit total duration of animation. This makes 'small' transitions fast.
497   // 'distance' is the max travel between 4 potentially traveling corners.
498   double distanceX = std::max(std::abs(NSMinX(currentFrame) - NSMinX(frame)),
499                               std::abs(NSMaxX(currentFrame) - NSMaxX(frame)));
500   double distanceY = std::max(std::abs(NSMinY(currentFrame) - NSMinY(frame)),
501                               std::abs(NSMaxY(currentFrame) - NSMaxY(frame)));
502   double distance = std::max(distanceX, distanceY);
503   double duration = std::min(distance / kBoundsAnimationSpeedPixelsPerSecond,
504                              kBoundsAnimationMaxDurationSeconds);
505   // Detect animation that happens when expansion state is set to MINIMIZED
506   // and there is relatively big portion of the panel to hide from view.
507   // Initialize animation differently in this case, using fast-pause-slow
508   // method, see below for more details.
509   if (distanceY > 0 &&
510       windowShim_->panel()->expansion_state() == Panel::MINIMIZED) {
511     animationStopToShowTitlebarOnly_ = 1.0 -
512         (windowShim_->panel()->TitleOnlyHeight() - NSHeight(frame)) / distanceY;
513     if (animationStopToShowTitlebarOnly_ > 0.7) {  // Relatively big movement.
514       playingMinimizeAnimation_ = YES;
515       duration = 1.5;
516     }
517   }
518   [boundsAnimation_ setDuration: PanelManager::AdjustTimeInterval(duration)];
519   [boundsAnimation_ setFrameRate:0.0];
520   [boundsAnimation_ setAnimationBlockingMode: NSAnimationNonblocking];
521   [boundsAnimation_ startAnimation];
524 - (float)animation:(NSAnimation*)animation
525   valueForProgress:(NSAnimationProgress)progress {
526   return PanelBoundsAnimation::ComputeAnimationValue(
527       progress, playingMinimizeAnimation_, animationStopToShowTitlebarOnly_);
530 - (void)cleanupAfterAnimation {
531   playingMinimizeAnimation_ = NO;
532   if (!windowShim_->panel()->IsMinimized())
533     [self enableWebContentsViewAutosizing];
536 - (void)animationDidEnd:(NSAnimation*)animation {
537   [self cleanupAfterAnimation];
539   // Only invoke this callback from animationDidEnd, since animationDidStop can
540   // be called when we interrupt/restart animation which is in progress.
541   // We only need this notification when animation indeed finished moving
542   // the panel bounds.
543   Panel* panel = windowShim_->panel();
544   panel->manager()->OnPanelAnimationEnded(panel);
547 - (void)animationDidStop:(NSAnimation*)animation {
548   [self cleanupAfterAnimation];
551 - (void)terminateBoundsAnimation {
552   if (!boundsAnimation_)
553     return;
554   [boundsAnimation_ stopAnimation];
555   [boundsAnimation_ setDelegate:nil];
556   [boundsAnimation_ release];
557   boundsAnimation_ = nil;
560 - (BOOL)isAnimatingBounds {
561   return boundsAnimation_ && [boundsAnimation_ isAnimating];
564 - (void)onTitlebarMouseClicked:(int)modifierFlags {
565   Panel* panel = windowShim_->panel();
566   panel->OnTitlebarClicked((modifierFlags & NSShiftKeyMask) ?
567                            panel::APPLY_TO_ALL : panel::NO_MODIFIER);
570 - (void)onTitlebarDoubleClicked:(int)modifierFlags {
571   // Double-clicking is only allowed to minimize docked panels.
572   Panel* panel = windowShim_->panel();
573   if (panel->collection()->type() != PanelCollection::DOCKED ||
574       panel->IsMinimized())
575     return;
576   [self minimizeButtonClicked:modifierFlags];
579 - (int)titlebarHeightInScreenCoordinates {
580   NSView* titlebar = [self titlebarView];
581   return NSHeight([titlebar convertRect:[titlebar bounds] toView:nil]);
584 // TODO(dcheng): These two selectors are almost copy-and-paste from
585 // BrowserWindowController. Figure out the appropriate way of code sharing,
586 // whether it's refactoring more things into BrowserWindowUtils or making a
587 // common base controller for browser windows.
588 - (void)windowDidBecomeKey:(NSNotification*)notification {
589   // We need to activate the controls (in the "WebView"). To do this, get the
590   // selected WebContents's RenderWidgetHostView and tell it to activate.
591   if (WebContents* contents = windowShim_->panel()->GetWebContents()) {
592     if (content::RenderWidgetHostView* rwhv =
593         contents->GetRenderWidgetHostView())
594       rwhv->SetActive(true);
595   }
597   windowShim_->panel()->OnActiveStateChanged(true);
599   // Make the window user-resizable when it gains the focus.
600   [[self window] setStyleMask:
601       [[self window] styleMask] | NSResizableWindowMask];
604 - (void)windowDidResignKey:(NSNotification*)notification {
605   // If our app is still active and we're still the key window, ignore this
606   // message, since it just means that a menu extra (on the "system status bar")
607   // was activated; we'll get another |-windowDidResignKey| if we ever really
608   // lose key window status.
609   if ([NSApp isActive] && ([NSApp keyWindow] == [self window]))
610     return;
612   [self onWindowDidResignKey];
615 - (void)windowWillStartLiveResize:(NSNotification*)notification {
616   // Check if the user-resizing is allowed for the triggering edge/corner.
617   // This is an extra safe guard because we are not able to track the mouse
618   // movement outside the window and Cocoa could trigger the user-resizing
619   // when the mouse moves a bit outside the edge/corner.
620   if (![self canResizeByMouseAtCurrentLocation])
621     return;
622   userResizing_ = YES;
623   windowShim_->panel()->OnPanelStartUserResizing();
626 - (void)windowDidEndLiveResize:(NSNotification*)notification {
627   if (!userResizing_)
628     return;
629   userResizing_ = NO;
631   Panel* panel = windowShim_->panel();
632   panel->OnPanelEndUserResizing();
634   gfx::Rect newBounds =
635       cocoa_utils::ConvertRectFromCocoaCoordinates([[self window] frame]);
636   if (windowShim_->panel()->GetBounds() == newBounds)
637     return;
638   windowShim_->set_cached_bounds_directly(newBounds);
640   panel->IncreaseMaxSize(newBounds.size());
641   panel->set_full_size(newBounds.size());
643   panel->collection()->RefreshLayout();
646 - (NSSize)windowWillResize:(NSWindow*)sender toSize:(NSSize)newSize {
647   // As an extra safe guard, we avoid the user resizing if it is deemed not to
648   // be allowed (see comment in windowWillStartLiveResize).
649   if ([[self window] inLiveResize] && !userResizing_)
650     return [[self window] frame].size;
651   return newSize;
654 - (void)windowDidResize:(NSNotification*)notification {
655   Panel* panel = windowShim_->panel();
656   if (userResizing_) {
657     panel->collection()->OnPanelResizedByMouse(
658         panel,
659         cocoa_utils::ConvertRectFromCocoaCoordinates([[self window] frame]));
660   }
662   [self updateTrackingArea];
664   if (![self isAnimatingBounds] ||
665       panel->collection()->type() != PanelCollection::DOCKED)
666     return;
668   // Remove the web contents view from the view hierarchy when the panel is not
669   // taller than the titlebar. Put it back when the panel grows taller than
670   // the titlebar. Note that RenderWidgetHostViewMac works for the case that
671   // the web contents view does not exist in the view hierarchy (i.e. the tab
672   // is not the main one), but it does not work well, like causing occasional
673   // crashes (http://crbug.com/265932), if the web contents view is made hidden.
674   //
675   // This is needed when the docked panels are being animated. When the
676   // animation starts, the contents view autosizing is disabled. After the
677   // animation ends, the contents view autosizing is reenabled and the frame
678   // of contents view is updated. Thus it is likely that the contents view will
679   // overlap with the titlebar view when the panel shrinks to be very small.
680   // The implementation of the web contents view assumes that it will never
681   // overlap with another view in order to paint the web contents view directly.
682   content::WebContents* webContents = panel->GetWebContents();
683   if (!webContents)
684     return;
685   NSView* contentView = webContents->GetNativeView();
686   if (NSHeight([self contentRectForFrameRect:[[self window] frame]]) <= 0) {
687     // No need to retain the view before it is removed from its superview
688     // because WebContentsView keeps a reference to this view.
689     if ([contentView superview])
690       [contentView removeFromSuperview];
691   } else {
692     if (![contentView superview]) {
693       [[[self window] contentView] addSubview:contentView];
695       // When the web contents view is put back, we need to tell its render
696       // widget host view to accept focus.
697       content::RenderWidgetHostView* rwhv =
698           webContents->GetRenderWidgetHostView();
699       if (rwhv) {
700         [[self window] makeFirstResponder:rwhv->GetNativeView()];
701         rwhv->SetActive([[self window] isMainWindow]);
702       }
703     }
704   }
707 - (void)activate {
708   // Activate the window. -|windowDidBecomeKey:| will be called when
709   // window becomes active.
710   base::AutoReset<BOOL> pin(&activationRequestedByPanel_, true);
711   [BrowserWindowUtils activateWindowForController:self];
714 - (void)deactivate {
715   if (![[self window] isMainWindow])
716     return;
718   [NSApp deactivate];
720   // Cocoa does not support deactivating a NSWindow explicitly. To work around
721   // this, we call orderOut and orderFront to force the window to lose its key
722   // window state.
724   // Before doing this, we need to disable screen updates to prevent flickering.
725   NSDisableScreenUpdates();
727   // If a panel is in stacked mode, the window has a background parent window.
728   // We need to detach it from its parent window before applying the ordering
729   // change and then put it back because otherwise tha background parent window
730   // might show up.
731   NSWindow* parentWindow = [[self window] parentWindow];
732   if (parentWindow)
733     [parentWindow removeChildWindow:[self window]];
735   [[self window] orderOut:nil];
736   [[self window] orderFront:nil];
738   if (parentWindow)
739     [parentWindow addChildWindow:[self window] ordered:NSWindowAbove];
741   NSEnableScreenUpdates();
743   // Though the above workaround causes the window to lose its key window state,
744   // it does not trigger the system to call windowDidResignKey.
745   [self onWindowDidResignKey];
748 - (void)onWindowDidResignKey {
749   // We need to deactivate the controls (in the "WebView"). To do this, get the
750   // selected WebContents's RenderWidgetHostView and tell it to deactivate.
751   if (WebContents* contents = windowShim_->panel()->GetWebContents()) {
752     if (content::RenderWidgetHostView* rwhv =
753         contents->GetRenderWidgetHostView())
754       rwhv->SetActive(false);
755   }
757   windowShim_->panel()->OnActiveStateChanged(false);
759   // Make the window not user-resizable when it loses the focus. This is to
760   // solve the problem that the bottom edge of the active panel does not
761   // trigger the user-resizing if this panel stacks with another inactive
762   // panel at the bottom.
763   [[self window] setStyleMask:
764       [[self window] styleMask] & ~NSResizableWindowMask];
767 - (void)preventBecomingKeyWindow:(BOOL)prevent {
768   canBecomeKeyWindow_ = !prevent;
771 - (void)fullScreenModeChanged:(bool)isFullScreen {
772   [self updateWindowLevel];
774   // If the panel is not always on top, its z-order should not be affected if
775   // some other window enters fullscreen mode.
776   if (!windowShim_->panel()->IsAlwaysOnTop())
777     return;
779   // The full-screen window is in normal level and changing the panel window
780   // to same normal level will not move it below the full-screen window. Thus
781   // we need to reorder the panel window.
782   if (isFullScreen)
783     [[self window] orderOut:nil];
784   else
785     [[self window] orderFrontRegardless];
788 - (BOOL)canBecomeKeyWindow {
789   // Panel can only gain focus if it is expanded. Minimized panels do not
790   // participate in Cmd-~ rotation.
791   // TODO(dimich): If it will be ever desired to expand/focus the Panel on
792   // keyboard navigation or via main menu, the care should be taken to avoid
793   // cases when minimized Panel is getting keyboard input, invisibly.
794   return canBecomeKeyWindow_;
797 - (int)numPanels {
798   return windowShim_->panel()->manager()->num_panels();
801 - (BOOL)activationRequestedByPanel {
802   return activationRequestedByPanel_;
805 - (void)updateWindowLevel {
806   [self updateWindowLevel:windowShim_->panel()->IsMinimized()];
809 - (void)updateWindowLevel:(BOOL)panelIsMinimized {
810   if (![self isWindowLoaded])
811     return;
812   Panel* panel = windowShim_->panel();
813   if (!panel->IsAlwaysOnTop()) {
814     [[self window] setLevel:NSNormalWindowLevel];
815     return;
816   }
817   // If we simply use NSStatusWindowLevel (25) for all docked panel windows,
818   // IME composition windows for things like CJK languages appear behind panels.
819   // Pre 10.7, IME composition windows have a window level of 19, which is
820   // lower than the dock at level 20. Since we want panels to appear on top of
821   // the dock, it is impossible to enforce an ordering where IME > panel > dock,
822   // since IME < dock.
823   // On 10.7, IME composition windows and the dock both live at level 20, so we
824   // use the same window level for panels. Since newly created windows appear at
825   // the top of their window level, panels are typically on top of the dock, and
826   // the IME composition window correctly draws over the panel.
827   // An autohide dock causes problems though: since it's constantly being
828   // revealed, it ends up drawing on top of other windows at the same level.
829   // While this is OK for expanded panels, it makes minimized panels impossible
830   // to activate. As a result, we still use NSStatusWindowLevel for minimized
831   // panels, since it's impossible to compose IME text in them anyway.
832   if (panelIsMinimized) {
833     [[self window] setLevel:NSStatusWindowLevel];
834     return;
835   }
836   [[self window] setLevel:NSDockWindowLevel];
839 - (void)updateWindowCollectionBehavior {
840   if (![self isWindowLoaded])
841     return;
842   NSWindowCollectionBehavior collectionBehavior =
843       NSWindowCollectionBehaviorParticipatesInCycle;
844   if (windowShim_->panel()->IsAlwaysOnTop())
845     collectionBehavior |= NSWindowCollectionBehaviorCanJoinAllSpaces;
846   [[self window] setCollectionBehavior:collectionBehavior];
849 - (void)updateTrackingArea {
850   NSView* superview = [[[self window] contentView] superview];
852   if (trackingArea_.get())
853     [superview removeTrackingArea:trackingArea_.get()];
855   trackingArea_.reset(
856           [[CrTrackingArea alloc] initWithRect:[superview bounds]
857                                        options:NSTrackingInVisibleRect |
858                                                NSTrackingMouseMoved |
859                                                NSTrackingActiveInKeyWindow
860                                          owner:superview
861                                       userInfo:nil]);
862   [superview addTrackingArea:trackingArea_.get()];
865 - (void)showShadow:(BOOL)show {
866   if (![self isWindowLoaded])
867     return;
868   [[self window] setHasShadow:show];
871 - (void)miniaturize {
872   [[self window] miniaturize:nil];
875 - (BOOL)isMiniaturized {
876   return [[self window] isMiniaturized];
879 - (BOOL)canResizeByMouseAtCurrentLocation {
880   panel::Resizability resizability = windowShim_->panel()->CanResizeByMouse();
881   NSRect frame = [[self window] frame];
882   NSPoint point = [NSEvent mouseLocation];
884   if (point.y < NSMinY(frame) + kWidthOfMouseResizeArea) {
885     if (point.x < NSMinX(frame) + kWidthOfMouseResizeArea &&
886         (resizability & panel::RESIZABLE_BOTTOM_LEFT) == 0) {
887       return NO;
888     }
889     if (point.x > NSMaxX(frame) - kWidthOfMouseResizeArea &&
890         (resizability & panel::RESIZABLE_BOTTOM_RIGHT) == 0) {
891       return NO;
892     }
893     if ((resizability & panel::RESIZABLE_BOTTOM) == 0)
894       return NO;
895   } else if (point.y > NSMaxY(frame) - kWidthOfMouseResizeArea) {
896     if (point.x < NSMinX(frame) + kWidthOfMouseResizeArea &&
897         (resizability & panel::RESIZABLE_TOP_LEFT) == 0) {
898       return NO;
899     }
900     if (point.x > NSMaxX(frame) - kWidthOfMouseResizeArea &&
901         (resizability & panel::RESIZABLE_TOP_RIGHT) == 0) {
902       return NO;
903     }
904     if ((resizability & panel::RESIZABLE_TOP) == 0)
905       return NO;
906   } else {
907     if (point.x < NSMinX(frame) + kWidthOfMouseResizeArea &&
908         (resizability & panel::RESIZABLE_LEFT) == 0) {
909       return NO;
910     }
911     if (point.x > NSMaxX(frame) - kWidthOfMouseResizeArea &&
912         (resizability & panel::RESIZABLE_RIGHT) == 0) {
913       return NO;
914     }
915   }
916   return YES;
919 // We have custom implementation of these because our titlebar height is custom
920 // and does not match the standard one.
921 - (NSRect)frameRectForContentRect:(NSRect)contentRect {
922   // contentRect is in contentView coord system. We should add a titlebar on top
923   // and then convert to the windows coord system.
924   contentRect.size.height += panel::kTitlebarHeight;
925   NSRect frameRect = [[[self window] contentView] convertRect:contentRect
926                                                        toView:nil];
927   return frameRect;
930 - (NSRect)contentRectForFrameRect:(NSRect)frameRect {
931   NSRect contentRect = [[[self window] contentView] convertRect:frameRect
932                                                        fromView:nil];
933   contentRect.size.height -= panel::kTitlebarHeight;
934   if (contentRect.size.height < 0)
935     contentRect.size.height = 0;
936   return contentRect;
939 @end
941 @implementation WindowCloseWatcher
943 - (id)init {
944   if ((self = [super init])) {
945     [[NSNotificationCenter defaultCenter]
946         addObserver:self
947            selector:@selector(windowWillClose:)
948                name:NSWindowWillCloseNotification
949              object:nil];
950   }
951   return self;
954 - (void)windowWillClose:(NSNotification*)notification {
955   // If it looks like a panel may (refuse to) become key after this window is
956   // closed, then explicitly set the topmost browser window on the active space
957   // to be key (if there is one). Otherwise AppKit will stop looking for windows
958   // to make key once it encounters the panel.
959   id closingWindow = [notification object];
960   BOOL orderNext = NO;
961   for (NSWindow* window : [NSApp orderedWindows]) {
962     if ([window isEqual:closingWindow] || ![window isOnActiveSpace])
963       continue;
965     if ([window isKindOfClass:[PanelWindowCocoaImpl class]] &&
966         ![window canBecomeKeyWindow]) {
967       orderNext = YES;
968       continue;
969     }
971     if (orderNext) {
972       if (![window canBecomeKeyWindow])
973         continue;
975       [window makeKeyWindow];
976     }
977     return;
978   }
981 @end