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/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/tab_contents/favicon_util_mac.h"
25 #import "chrome/browser/ui/cocoa/tab_contents/tab_contents_controller.h"
26 #import "chrome/browser/ui/cocoa/sprite_view.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 "grit/ui_resources.h"
37 #include "ui/base/resource/resource_bundle.h"
38 #include "ui/gfx/image/image.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 @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.
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 {
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])
82 BrowserCrApplication* app = base::mac::ObjCCast<BrowserCrApplication>(
83 [BrowserCrApplication sharedApplication]);
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]);
95 - (void)performMiniaturize:(id)sender {
96 [[self windowController] minimizeButtonClicked:0];
99 // Ignore key events if window cannot become key window to fix problem
100 // where keyboard input is still going into a minimized panel even though
101 // the app has been deactivated in -[PanelWindowControllerCocoa deactivate:].
102 - (void)sendEvent:(NSEvent*)anEvent {
103 NSEventType eventType = [anEvent type];
104 if ((eventType == NSKeyDown || eventType == NSKeyUp) &&
105 ![self canBecomeKeyWindow])
107 [super sendEvent:anEvent];
110 - (void)mouseMoved:(NSEvent*)event {
111 // Cocoa does not support letting the application determine the edges that
112 // can trigger the user resizing. To work around this, we track the mouse
113 // location. When it is close to the edge/corner where the user resizing
114 // is not desired, we force the min and max size of the window to be same
115 // as current window size. For all other cases, we restore the min and max
117 PanelWindowControllerCocoa* controller =
118 base::mac::ObjCCast<PanelWindowControllerCocoa>([self windowController]);
119 NSRect frame = [self frame];
120 if ([controller canResizeByMouseAtCurrentLocation]) {
121 // Mac window server limits window sizes to 10000.
122 NSSize maxSize = NSMakeSize(10000, 10000);
124 // If the user is resizing a stacked panel by its bottom edge, make sure its
125 // height cannot grow more than what the panel below it could offer. This is
126 // because growing a stacked panel by y amount will shrink the panel below
127 // it by same amount and we do not want the panel below it being shrunk to
128 // be smaller than the titlebar.
129 Panel* panel = [controller panel];
130 NSPoint point = [NSEvent mouseLocation];
131 if (point.y < NSMinY(frame) + kWidthOfMouseResizeArea && panel->stack()) {
132 Panel* belowPanel = panel->stack()->GetPanelBelow(panel);
133 if (belowPanel && !belowPanel->IsMinimized()) {
134 maxSize.height = panel->GetBounds().height() +
135 belowPanel->GetBounds().height() - panel::kTitlebarHeight;
139 // Enable the user-resizing by setting both min and max size to the right
141 [self setMinSize:NSMakeSize(panel::kPanelMinWidth,
142 panel::kPanelMinHeight)];
143 [self setMaxSize:maxSize];
145 // Disable the user-resizing by setting both min and max size to be same as
146 // current window size.
147 [self setMinSize:frame.size];
148 [self setMaxSize:frame.size];
151 [super mouseMoved:event];
155 // ChromeEventProcessingWindow expects its controller to implement the
156 // BrowserCommandExecutor protocol.
157 @interface PanelWindowControllerCocoa (InternalAPI) <BrowserCommandExecutor>
159 // BrowserCommandExecutor methods.
160 - (void)executeCommand:(int)command;
164 @implementation PanelWindowControllerCocoa (InternalAPI)
166 // This gets called whenever a browser-specific keyboard shortcut is performed
167 // in the Panel window. We simply swallow all those events.
168 - (void)executeCommand:(int)command {}
172 @implementation PanelWindowControllerCocoa
174 - (id)initWithPanel:(PanelCocoa*)window {
176 [base::mac::FrameworkBundle() pathForResource:@"Panel" ofType:@"nib"];
177 if ((self = [super initWithWindowNibPath:nibpath owner:self])) {
178 windowShim_.reset(window);
179 animateOnBoundsChange_ = YES;
180 canBecomeKeyWindow_ = YES;
181 activationRequestedByPanel_ = NO;
187 return windowShim_->panel();
190 - (void)awakeFromNib {
191 NSWindow* window = [self window];
194 DCHECK(titlebar_view_);
195 DCHECK_EQ(self, [window delegate]);
197 [self updateWindowLevel];
199 [self updateWindowCollectionBehavior];
201 [titlebar_view_ attach];
203 // Set initial size of the window to match the size of the panel to give
204 // the renderer the proper size to work with earlier, avoiding a resize
205 // after the window is revealed.
206 gfx::Rect panelBounds = windowShim_->panel()->GetBounds();
207 NSRect frame = [window frame];
208 frame.size.width = panelBounds.width();
209 frame.size.height = panelBounds.height();
210 [window setFrame:frame display:NO];
212 // MacOS will turn the user-resizing to the user-dragging if the direction of
213 // the dragging is orthogonal to the direction of the arrow cursor. We do not
214 // want this since it will bypass our dragging logic. The panel window is
215 // still draggable because we track and handle the dragging in our custom way.
216 [[self window] setMovable:NO];
218 [self updateTrackingArea];
221 - (void)updateWebContentsViewFrame {
222 content::WebContents* webContents = windowShim_->panel()->GetWebContents();
226 // Compute the size of the web contents view. Don't assume it's similar to the
227 // size of the contentView, because the contentView is managed by the Cocoa
228 // to be (window - standard titlebar), while we have taller custom titlebar
229 // instead. In coordinate system of window's contentView.
230 NSRect contentFrame = [self contentRectForFrameRect:[[self window] frame]];
231 contentFrame.origin = NSZeroPoint;
233 NSView* contentView = webContents->GetNativeView();
234 if (!NSEqualRects([contentView frame], contentFrame))
235 [contentView setFrame:contentFrame];
238 - (void)disableWebContentsViewAutosizing {
239 [[[self window] contentView] setAutoresizesSubviews:NO];
242 - (void)enableWebContentsViewAutosizing {
243 [self updateWebContentsViewFrame];
244 [[[self window] contentView] setAutoresizesSubviews:YES];
247 - (void)revealAnimatedWithFrame:(const NSRect&)frame {
248 NSWindow* window = [self window]; // This ensures loading the nib.
250 // Disable subview resizing while resizing the window to avoid renderer
251 // resizes during intermediate stages of animation.
252 [self disableWebContentsViewAutosizing];
254 // We grow the window from the bottom up to produce a 'reveal' animation.
255 NSRect startFrame = NSMakeRect(NSMinX(frame), NSMinY(frame),
256 NSWidth(frame), kMinimumWindowSize);
257 [window setFrame:startFrame display:NO animate:NO];
258 // Shows the window without making it key, on top of its layer, even if
259 // Chromium is not an active app.
260 [window orderFrontRegardless];
261 // TODO(dcheng): Temporary hack to work around the fact that
262 // orderFrontRegardless causes us to become the first responder. The usual
263 // Chrome assumption is that becoming the first responder = you have focus, so
264 // we always deactivate the controls here. If we're created as an active
265 // panel, we'll get a NSWindowDidBecomeKeyNotification and reactivate the web
266 // view properly. See crbug.com/97831 for more details.
267 WebContents* web_contents = windowShim_->panel()->GetWebContents();
268 // RWHV may be NULL in unit tests.
269 if (web_contents && web_contents->GetRenderWidgetHostView())
270 web_contents->GetRenderWidgetHostView()->SetActive(false);
272 // This will re-enable the content resizing after it finishes.
273 [self setPanelFrame:frame animate:YES];
276 - (void)updateTitleBar {
277 NSString* newTitle = base::SysUTF16ToNSString(
278 windowShim_->panel()->GetWindowTitle());
279 pendingWindowTitle_.reset(
280 [BrowserWindowUtils scheduleReplaceOldTitle:pendingWindowTitle_.get()
281 withNewTitle:newTitle
282 forWindow:[self window]]);
283 [titlebar_view_ setTitle:newTitle];
288 base::scoped_nsobject<NSView> iconView;
289 if (throbberShouldSpin_) {
290 // If the throbber is spinning now, no need to replace it.
291 if ([[titlebar_view_ icon] isKindOfClass:[SpriteView class]])
295 ResourceBundle::GetSharedInstance().GetNativeImageNamed(
296 IDR_THROBBER).ToNSImage();
297 SpriteView* spriteView = [[SpriteView alloc] init];
298 [spriteView setImage:iconImage];
299 iconView.reset(spriteView);
301 const gfx::Image& page_icon = windowShim_->panel()->GetCurrentPageIcon();
302 ResourceBundle& rb = ResourceBundle::GetSharedInstance();
303 NSRect iconFrame = [[titlebar_view_ icon] frame];
304 NSImage* iconImage = page_icon.IsEmpty() ?
305 rb.GetNativeImageNamed(IDR_DEFAULT_FAVICON).ToNSImage() :
306 page_icon.ToNSImage();
307 NSImageView* imageView = [[NSImageView alloc] initWithFrame:iconFrame];
308 [imageView setImage:iconImage];
309 iconView.reset(imageView);
311 [titlebar_view_ setIcon:iconView];
314 - (void)updateThrobber:(BOOL)shouldSpin {
315 if (throbberShouldSpin_ == shouldSpin)
317 throbberShouldSpin_ = shouldSpin;
319 // If the titlebar view has not been attached, bail out.
326 - (void)updateTitleBarMinimizeRestoreButtonVisibility {
327 Panel* panel = windowShim_->panel();
328 [titlebar_view_ setMinimizeButtonVisibility:panel->CanShowMinimizeButton()];
329 [titlebar_view_ setRestoreButtonVisibility:panel->CanShowRestoreButton()];
332 - (void)webContentsInserted:(WebContents*)contents {
333 NSView* view = contents->GetNativeView();
334 [[[self window] contentView] addSubview:view];
335 [view setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
337 [self enableWebContentsViewAutosizing];
340 - (void)webContentsDetached:(WebContents*)contents {
341 [contents->GetNativeView() removeFromSuperview];
344 - (PanelTitlebarViewCocoa*)titlebarView {
345 return titlebar_view_;
348 // Called to validate menu and toolbar items when this window is key. All the
349 // items we care about have been set with the |-commandDispatch:|
350 // action and a target of FirstResponder in IB.
351 // Delegate to the NSApp delegate if Panel does not care about the command or
352 // shortcut, to make sure the global items in Chrome main app menu still work.
353 - (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item {
354 if ([item action] == @selector(commandDispatch:)) {
355 NSInteger tag = [item tag];
356 CommandUpdater* command_updater = windowShim_->panel()->command_updater();
357 if (command_updater->SupportsCommand(tag))
358 return command_updater->IsCommandEnabled(tag);
360 return [[NSApp delegate] validateUserInterfaceItem:item];
365 // Called when the user picks a menu or toolbar item when this window is key.
366 // Calls through to the panel object to execute the command or delegates up.
367 - (void)commandDispatch:(id)sender {
369 NSInteger tag = [sender tag];
370 CommandUpdater* command_updater = windowShim_->panel()->command_updater();
371 if (command_updater->SupportsCommand(tag))
372 windowShim_->panel()->ExecuteCommandIfEnabled(tag);
374 [[NSApp delegate] commandDispatch:sender];
377 // Handler for the custom Close button.
379 windowShim_->panel()->Close();
382 // Handler for the custom Minimize button.
383 - (void)minimizeButtonClicked:(int)modifierFlags {
384 Panel* panel = windowShim_->panel();
385 panel->OnMinimizeButtonClicked((modifierFlags & NSShiftKeyMask) ?
386 panel::APPLY_TO_ALL : panel::NO_MODIFIER);
389 // Handler for the custom Restore button.
390 - (void)restoreButtonClicked:(int)modifierFlags {
391 Panel* panel = windowShim_->panel();
392 panel->OnRestoreButtonClicked((modifierFlags & NSShiftKeyMask) ?
393 panel::APPLY_TO_ALL : panel::NO_MODIFIER);
396 // Called when the user wants to close the panel or from the shutdown process.
397 // The Panel object is in control of whether or not we're allowed to close. It
398 // may defer closing due to several states, such as onbeforeUnload handlers
399 // needing to be fired. If closing is deferred, the Panel will handle the
400 // processing required to get us to the closing state and (by watching for
401 // the web content going away) will again call to close the window when it's
403 - (BOOL)windowShouldClose:(id)sender {
404 Panel* panel = windowShim_->panel();
405 // Give beforeunload handlers the chance to cancel the close before we hide
407 if (!panel->ShouldCloseWindow())
410 if (panel->GetWebContents()) {
411 // Terminate any playing animations.
412 [self terminateBoundsAnimation];
413 animateOnBoundsChange_ = NO;
414 // Make panel close the web content, allowing the renderer to shut down
415 // and call us back again.
416 panel->OnWindowClosing();
420 // No web content; it's ok to close the window.
424 // When windowShouldClose returns YES (or if controller receives direct 'close'
425 // signal), window will be unconditionally closed. Clean up.
426 - (void)windowWillClose:(NSNotification*)notification {
427 DCHECK(!windowShim_->panel()->GetWebContents());
428 // Avoid callbacks from a nonblocking animation in progress, if any.
429 [self terminateBoundsAnimation];
430 windowShim_->DidCloseNativeWindow();
431 // Call |-autorelease| after a zero-length delay to avoid deadlock from
432 // code in the current run loop that waits on PANEL_CLOSED notification.
433 // The notification is sent when this object is freed, but this object
434 // cannot be freed until the current run loop completes.
435 [self performSelector:@selector(autorelease)
440 - (void)startDrag:(NSPoint)mouseLocation {
441 // Convert from Cocoa's screen coordinates to platform-indepedent screen
442 // coordinates because PanelManager method takes platform-indepedent screen
444 windowShim_->panel()->manager()->StartDragging(
445 windowShim_->panel(),
446 cocoa_utils::ConvertPointFromCocoaCoordinates(mouseLocation));
449 - (void)endDrag:(BOOL)cancelled {
450 windowShim_->panel()->manager()->EndDragging(cancelled);
453 - (void)drag:(NSPoint)mouseLocation {
454 // Convert from Cocoa's screen coordinates to platform-indepedent screen
455 // coordinates because PanelManager method takes platform-indepedent screen
457 windowShim_->panel()->manager()->Drag(
458 cocoa_utils::ConvertPointFromCocoaCoordinates(mouseLocation));
461 - (void)setPanelFrame:(NSRect)frame
462 animate:(BOOL)animate {
463 BOOL jumpToDestination = (!animateOnBoundsChange_ || !animate);
465 // If no animation is in progress, apply bounds change instantly.
466 if (jumpToDestination && ![self isAnimatingBounds]) {
467 [[self window] setFrame:frame display:YES animate:NO];
471 NSDictionary *windowResize = [NSDictionary dictionaryWithObjectsAndKeys:
472 [self window], NSViewAnimationTargetKey,
473 [NSValue valueWithRect:frame], NSViewAnimationEndFrameKey, nil];
474 NSArray *animations = [NSArray arrayWithObjects:windowResize, nil];
476 // If an animation is in progress, update the animation with new target
477 // bounds. Also, set the destination frame bounds to the new value.
478 if (jumpToDestination && [self isAnimatingBounds]) {
479 [boundsAnimation_ setViewAnimations:animations];
480 [[self window] setFrame:frame display:YES animate:NO];
484 // Will be enabled back in animationDidEnd callback.
485 [self disableWebContentsViewAutosizing];
487 // Terminate previous animation, if it is still playing.
488 [self terminateBoundsAnimation];
491 [[NSViewAnimation alloc] initWithViewAnimations:animations];
492 [boundsAnimation_ setDelegate:self];
494 NSRect currentFrame = [[self window] frame];
495 // Compute duration. We use constant speed of animation, however if the change
496 // is too large, we clip the duration (effectively increasing speed) to
497 // limit total duration of animation. This makes 'small' transitions fast.
498 // 'distance' is the max travel between 4 potentially traveling corners.
499 double distanceX = std::max(std::abs(NSMinX(currentFrame) - NSMinX(frame)),
500 std::abs(NSMaxX(currentFrame) - NSMaxX(frame)));
501 double distanceY = std::max(std::abs(NSMinY(currentFrame) - NSMinY(frame)),
502 std::abs(NSMaxY(currentFrame) - NSMaxY(frame)));
503 double distance = std::max(distanceX, distanceY);
504 double duration = std::min(distance / kBoundsAnimationSpeedPixelsPerSecond,
505 kBoundsAnimationMaxDurationSeconds);
506 // Detect animation that happens when expansion state is set to MINIMIZED
507 // and there is relatively big portion of the panel to hide from view.
508 // Initialize animation differently in this case, using fast-pause-slow
509 // method, see below for more details.
511 windowShim_->panel()->expansion_state() == Panel::MINIMIZED) {
512 animationStopToShowTitlebarOnly_ = 1.0 -
513 (windowShim_->panel()->TitleOnlyHeight() - NSHeight(frame)) / distanceY;
514 if (animationStopToShowTitlebarOnly_ > 0.7) { // Relatively big movement.
515 playingMinimizeAnimation_ = YES;
519 [boundsAnimation_ setDuration: PanelManager::AdjustTimeInterval(duration)];
520 [boundsAnimation_ setFrameRate:0.0];
521 [boundsAnimation_ setAnimationBlockingMode: NSAnimationNonblocking];
522 [boundsAnimation_ startAnimation];
525 - (float)animation:(NSAnimation*)animation
526 valueForProgress:(NSAnimationProgress)progress {
527 return PanelBoundsAnimation::ComputeAnimationValue(
528 progress, playingMinimizeAnimation_, animationStopToShowTitlebarOnly_);
531 - (void)cleanupAfterAnimation {
532 playingMinimizeAnimation_ = NO;
533 if (!windowShim_->panel()->IsMinimized())
534 [self enableWebContentsViewAutosizing];
537 - (void)animationDidEnd:(NSAnimation*)animation {
538 [self cleanupAfterAnimation];
540 // Only invoke this callback from animationDidEnd, since animationDidStop can
541 // be called when we interrupt/restart animation which is in progress.
542 // We only need this notification when animation indeed finished moving
544 Panel* panel = windowShim_->panel();
545 panel->manager()->OnPanelAnimationEnded(panel);
548 - (void)animationDidStop:(NSAnimation*)animation {
549 [self cleanupAfterAnimation];
552 - (void)terminateBoundsAnimation {
553 if (!boundsAnimation_)
555 [boundsAnimation_ stopAnimation];
556 [boundsAnimation_ setDelegate:nil];
557 [boundsAnimation_ release];
558 boundsAnimation_ = nil;
561 - (BOOL)isAnimatingBounds {
562 return boundsAnimation_ && [boundsAnimation_ isAnimating];
565 - (void)onTitlebarMouseClicked:(int)modifierFlags {
566 Panel* panel = windowShim_->panel();
567 panel->OnTitlebarClicked((modifierFlags & NSShiftKeyMask) ?
568 panel::APPLY_TO_ALL : panel::NO_MODIFIER);
571 - (void)onTitlebarDoubleClicked:(int)modifierFlags {
572 // Double-clicking is only allowed to minimize docked panels.
573 Panel* panel = windowShim_->panel();
574 if (panel->collection()->type() != PanelCollection::DOCKED ||
575 panel->IsMinimized())
577 [self minimizeButtonClicked:modifierFlags];
580 - (int)titlebarHeightInScreenCoordinates {
581 NSView* titlebar = [self titlebarView];
582 return NSHeight([titlebar convertRect:[titlebar bounds] toView:nil]);
585 // TODO(dcheng): These two selectors are almost copy-and-paste from
586 // BrowserWindowController. Figure out the appropriate way of code sharing,
587 // whether it's refactoring more things into BrowserWindowUtils or making a
588 // common base controller for browser windows.
589 - (void)windowDidBecomeKey:(NSNotification*)notification {
590 // We need to activate the controls (in the "WebView"). To do this, get the
591 // selected WebContents's RenderWidgetHostView and tell it to activate.
592 if (WebContents* contents = windowShim_->panel()->GetWebContents()) {
593 if (content::RenderWidgetHostView* rwhv =
594 contents->GetRenderWidgetHostView())
595 rwhv->SetActive(true);
598 windowShim_->panel()->OnActiveStateChanged(true);
600 // Make the window user-resizable when it gains the focus.
601 [[self window] setStyleMask:
602 [[self window] styleMask] | NSResizableWindowMask];
605 - (void)windowDidResignKey:(NSNotification*)notification {
606 // If our app is still active and we're still the key window, ignore this
607 // message, since it just means that a menu extra (on the "system status bar")
608 // was activated; we'll get another |-windowDidResignKey| if we ever really
609 // lose key window status.
610 if ([NSApp isActive] && ([NSApp keyWindow] == [self window]))
613 [self onWindowDidResignKey];
615 // Make the window not user-resizable when it loses the focus. This is to
616 // solve the problem that the bottom edge of the active panel does not
617 // trigger the user-resizing if this panel stacks with another inactive
618 // panel at the bottom.
619 [[self window] setStyleMask:
620 [[self window] styleMask] & ~NSResizableWindowMask];
623 - (void)windowWillStartLiveResize:(NSNotification*)notification {
624 // Check if the user-resizing is allowed for the triggering edge/corner.
625 // This is an extra safe guard because we are not able to track the mouse
626 // movement outside the window and Cocoa could trigger the user-resizing
627 // when the mouse moves a bit outside the edge/corner.
628 if (![self canResizeByMouseAtCurrentLocation])
631 windowShim_->panel()->OnPanelStartUserResizing();
634 - (void)windowDidEndLiveResize:(NSNotification*)notification {
639 Panel* panel = windowShim_->panel();
640 panel->OnPanelEndUserResizing();
642 gfx::Rect newBounds =
643 cocoa_utils::ConvertRectFromCocoaCoordinates([[self window] frame]);
644 if (windowShim_->panel()->GetBounds() == newBounds)
646 windowShim_->set_cached_bounds_directly(newBounds);
648 panel->IncreaseMaxSize(newBounds.size());
649 panel->set_full_size(newBounds.size());
651 panel->collection()->RefreshLayout();
654 - (NSSize)windowWillResize:(NSWindow*)sender toSize:(NSSize)newSize {
655 // As an extra safe guard, we avoid the user resizing if it is deemed not to
656 // be allowed (see comment in windowWillStartLiveResize).
657 if ([[self window] inLiveResize] && !userResizing_)
658 return [[self window] frame].size;
662 - (void)windowDidResize:(NSNotification*)notification {
663 Panel* panel = windowShim_->panel();
665 panel->collection()->OnPanelResizedByMouse(
667 cocoa_utils::ConvertRectFromCocoaCoordinates([[self window] frame]));
670 [self updateTrackingArea];
672 if (![self isAnimatingBounds] ||
673 panel->collection()->type() != PanelCollection::DOCKED)
676 // Remove the web contents view from the view hierarchy when the panel is not
677 // taller than the titlebar. Put it back when the panel grows taller than
678 // the titlebar. Note that RenderWidgetHostViewMac works for the case that
679 // the web contents view does not exist in the view hierarchy (i.e. the tab
680 // is not the main one), but it does not work well, like causing occasional
681 // crashes (http://crbug.com/265932), if the web contents view is made hidden.
683 // This is needed when the docked panels are being animated. When the
684 // animation starts, the contents view autosizing is disabled. After the
685 // animation ends, the contents view autosizing is reenabled and the frame
686 // of contents view is updated. Thus it is likely that the contents view will
687 // overlap with the titlebar view when the panel shrinks to be very small.
688 // The implementation of the web contents view assumes that it will never
689 // overlap with another view in order to paint the web contents view directly.
690 content::WebContents* webContents = panel->GetWebContents();
693 NSView* contentView = webContents->GetNativeView();
694 if (NSHeight([self contentRectForFrameRect:[[self window] frame]]) <= 0) {
695 // No need to retain the view before it is removed from its superview
696 // because WebContentsView keeps a reference to this view.
697 if ([contentView superview])
698 [contentView removeFromSuperview];
700 if (![contentView superview]) {
701 [[[self window] contentView] addSubview:contentView];
703 // When the web contents view is put back, we need to tell its render
704 // widget host view to accept focus.
705 content::RenderWidgetHostView* rwhv =
706 webContents->GetRenderWidgetHostView();
708 [[self window] makeFirstResponder:rwhv->GetNativeView()];
709 rwhv->SetActive([[self window] isMainWindow]);
716 // Activate the window. -|windowDidBecomeKey:| will be called when
717 // window becomes active.
718 base::AutoReset<BOOL> pin(&activationRequestedByPanel_, true);
719 [BrowserWindowUtils activateWindowForController:self];
723 if (![[self window] isMainWindow])
726 // Cocoa does not support deactivating a window, so we deactivate the app.
729 // Deactivating the app does not trigger windowDidResignKey. Do it manually.
730 [self onWindowDidResignKey];
733 - (void)onWindowDidResignKey {
734 // We need to deactivate the controls (in the "WebView"). To do this, get the
735 // selected WebContents's RenderWidgetHostView and tell it to deactivate.
736 if (WebContents* contents = windowShim_->panel()->GetWebContents()) {
737 if (content::RenderWidgetHostView* rwhv =
738 contents->GetRenderWidgetHostView())
739 rwhv->SetActive(false);
742 windowShim_->panel()->OnActiveStateChanged(false);
745 - (void)preventBecomingKeyWindow:(BOOL)prevent {
746 canBecomeKeyWindow_ = !prevent;
749 - (void)fullScreenModeChanged:(bool)isFullScreen {
750 [self updateWindowLevel];
752 // If the panel is not always on top, its z-order should not be affected if
753 // some other window enters fullscreen mode.
754 if (!windowShim_->panel()->IsAlwaysOnTop())
757 // The full-screen window is in normal level and changing the panel window
758 // to same normal level will not move it below the full-screen window. Thus
759 // we need to reorder the panel window.
761 [[self window] orderOut:nil];
763 [[self window] orderFrontRegardless];
766 - (BOOL)canBecomeKeyWindow {
767 // Panel can only gain focus if it is expanded. Minimized panels do not
768 // participate in Cmd-~ rotation.
769 // TODO(dimich): If it will be ever desired to expand/focus the Panel on
770 // keyboard navigation or via main menu, the care should be taken to avoid
771 // cases when minimized Panel is getting keyboard input, invisibly.
772 return canBecomeKeyWindow_;
776 return windowShim_->panel()->manager()->num_panels();
779 - (BOOL)activationRequestedByPanel {
780 return activationRequestedByPanel_;
783 - (void)updateWindowLevel {
784 [self updateWindowLevel:windowShim_->panel()->IsMinimized()];
787 - (void)updateWindowLevel:(BOOL)panelIsMinimized {
788 if (![self isWindowLoaded])
790 Panel* panel = windowShim_->panel();
791 if (!panel->IsAlwaysOnTop()) {
792 [[self window] setLevel:NSNormalWindowLevel];
795 // If we simply use NSStatusWindowLevel (25) for all docked panel windows,
796 // IME composition windows for things like CJK languages appear behind panels.
797 // Pre 10.7, IME composition windows have a window level of 19, which is
798 // lower than the dock at level 20. Since we want panels to appear on top of
799 // the dock, it is impossible to enforce an ordering where IME > panel > dock,
801 // On 10.7, IME composition windows and the dock both live at level 20, so we
802 // use the same window level for panels. Since newly created windows appear at
803 // the top of their window level, panels are typically on top of the dock, and
804 // the IME composition window correctly draws over the panel.
805 // An autohide dock causes problems though: since it's constantly being
806 // revealed, it ends up drawing on top of other windows at the same level.
807 // While this is OK for expanded panels, it makes minimized panels impossible
808 // to activate. As a result, we still use NSStatusWindowLevel for minimized
809 // panels, since it's impossible to compose IME text in them anyway.
810 if (panelIsMinimized) {
811 [[self window] setLevel:NSStatusWindowLevel];
814 [[self window] setLevel:NSDockWindowLevel];
817 - (void)updateWindowCollectionBehavior {
818 if (![self isWindowLoaded])
820 NSWindowCollectionBehavior collectionBehavior =
821 NSWindowCollectionBehaviorParticipatesInCycle;
822 if (windowShim_->panel()->IsAlwaysOnTop())
823 collectionBehavior |= NSWindowCollectionBehaviorCanJoinAllSpaces;
824 [[self window] setCollectionBehavior:collectionBehavior];
827 - (void)updateTrackingArea {
828 NSView* superview = [[[self window] contentView] superview];
830 if (trackingArea_.get())
831 [superview removeTrackingArea:trackingArea_.get()];
834 [[CrTrackingArea alloc] initWithRect:[superview bounds]
835 options:NSTrackingInVisibleRect |
836 NSTrackingMouseMoved |
837 NSTrackingActiveInKeyWindow
840 [superview addTrackingArea:trackingArea_.get()];
843 - (void)showShadow:(BOOL)show {
844 if (![self isWindowLoaded])
846 [[self window] setHasShadow:show];
849 - (void)miniaturize {
850 [[self window] miniaturize:nil];
853 - (BOOL)isMiniaturized {
854 return [[self window] isMiniaturized];
857 - (BOOL)canResizeByMouseAtCurrentLocation {
858 panel::Resizability resizability = windowShim_->panel()->CanResizeByMouse();
859 NSRect frame = [[self window] frame];
860 NSPoint point = [NSEvent mouseLocation];
862 if (point.y < NSMinY(frame) + kWidthOfMouseResizeArea) {
863 if (point.x < NSMinX(frame) + kWidthOfMouseResizeArea &&
864 (resizability & panel::RESIZABLE_BOTTOM_LEFT) == 0) {
867 if (point.x > NSMaxX(frame) - kWidthOfMouseResizeArea &&
868 (resizability & panel::RESIZABLE_BOTTOM_RIGHT) == 0) {
871 if ((resizability & panel::RESIZABLE_BOTTOM) == 0)
873 } else if (point.y > NSMaxY(frame) - kWidthOfMouseResizeArea) {
874 if (point.x < NSMinX(frame) + kWidthOfMouseResizeArea &&
875 (resizability & panel::RESIZABLE_TOP_LEFT) == 0) {
878 if (point.x > NSMaxX(frame) - kWidthOfMouseResizeArea &&
879 (resizability & panel::RESIZABLE_TOP_RIGHT) == 0) {
882 if ((resizability & panel::RESIZABLE_TOP) == 0)
885 if (point.x < NSMinX(frame) + kWidthOfMouseResizeArea &&
886 (resizability & panel::RESIZABLE_LEFT) == 0) {
889 if (point.x > NSMaxX(frame) - kWidthOfMouseResizeArea &&
890 (resizability & panel::RESIZABLE_RIGHT) == 0) {
897 // We have custom implementation of these because our titlebar height is custom
898 // and does not match the standard one.
899 - (NSRect)frameRectForContentRect:(NSRect)contentRect {
900 // contentRect is in contentView coord system. We should add a titlebar on top
901 // and then convert to the windows coord system.
902 contentRect.size.height += panel::kTitlebarHeight;
903 NSRect frameRect = [[[self window] contentView] convertRect:contentRect
908 - (NSRect)contentRectForFrameRect:(NSRect)frameRect {
909 NSRect contentRect = [[[self window] contentView] convertRect:frameRect
911 contentRect.size.height -= panel::kTitlebarHeight;
912 if (contentRect.size.height < 0)
913 contentRect.size.height = 0;