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