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