1 // Copyright (c) 2011 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 #import "chrome/browser/ui/cocoa/browser_window_controller.h" 6 7 #include <Carbon/Carbon.h> 8 9 #include "app/mac/scoped_nsdisable_screen_updates.h" 10 #include "app/mac/nsimage_cache.h" 11 #include "base/mac/mac_util.h" 12 #import "base/memory/scoped_nsobject.h" 13 #include "base/sys_string_conversions.h" 14 #include "chrome/app/chrome_command_ids.h" // IDC_* 15 #include "chrome/browser/bookmarks/bookmark_editor.h" 16 #include "chrome/browser/google/google_util.h" 17 #include "chrome/browser/instant/instant_controller.h" 18 #include "chrome/browser/profiles/profile.h" 19 #include "chrome/browser/sync/profile_sync_service.h" 20 #include "chrome/browser/sync/sync_ui_util_mac.h" 21 #include "chrome/browser/tab_contents/tab_contents_view_mac.h" 22 #include "chrome/browser/tabs/tab_strip_model.h" 23 #include "chrome/browser/themes/theme_service.h" 24 #include "chrome/browser/themes/theme_service_factory.h" 25 #include "chrome/browser/ui/browser.h" 26 #include "chrome/browser/ui/browser_list.h" 27 #import "chrome/browser/ui/cocoa/background_gradient_view.h" 28 #import "chrome/browser/ui/cocoa/bookmarks/bookmark_bar_controller.h" 29 #import "chrome/browser/ui/cocoa/bookmarks/bookmark_editor_controller.h" 30 #import "chrome/browser/ui/cocoa/browser_window_cocoa.h" 31 #import "chrome/browser/ui/cocoa/browser_window_controller_private.h" 32 #import "chrome/browser/ui/cocoa/dev_tools_controller.h" 33 #import "chrome/browser/ui/cocoa/download/download_shelf_controller.h" 34 #import "chrome/browser/ui/cocoa/event_utils.h" 35 #import "chrome/browser/ui/cocoa/fast_resize_view.h" 36 #import "chrome/browser/ui/cocoa/find_bar/find_bar_bridge.h" 37 #import "chrome/browser/ui/cocoa/find_bar/find_bar_cocoa_controller.h" 38 #import "chrome/browser/ui/cocoa/focus_tracker.h" 39 #import "chrome/browser/ui/cocoa/fullscreen_controller.h" 40 #import "chrome/browser/ui/cocoa/fullscreen_window.h" 41 #import "chrome/browser/ui/cocoa/image_utils.h" 42 #import "chrome/browser/ui/cocoa/infobars/infobar_container_controller.h" 43 #import "chrome/browser/ui/cocoa/location_bar/autocomplete_text_field_editor.h" 44 #import "chrome/browser/ui/cocoa/sidebar_controller.h" 45 #import "chrome/browser/ui/cocoa/status_bubble_mac.h" 46 #import "chrome/browser/ui/cocoa/tab_contents/previewable_contents_controller.h" 47 #import "chrome/browser/ui/cocoa/tab_contents/sad_tab_controller.h" 48 #import "chrome/browser/ui/cocoa/tab_contents/tab_contents_controller.h" 49 #import "chrome/browser/ui/cocoa/tabs/tab_strip_controller.h" 50 #import "chrome/browser/ui/cocoa/tabs/tab_strip_view.h" 51 #import "chrome/browser/ui/cocoa/tabs/tab_view.h" 52 #import "chrome/browser/ui/cocoa/tabpose_window.h" 53 #import "chrome/browser/ui/cocoa/toolbar/toolbar_controller.h" 54 #include "chrome/browser/ui/omnibox/location_bar.h" 55 #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h" 56 #include "chrome/browser/ui/tabs/dock_info.h" 57 #include "chrome/browser/ui/toolbar/encoding_menu_controller.h" 58 #include "chrome/browser/ui/window_sizer.h" 59 #include "chrome/common/url_constants.h" 60 #include "content/browser/renderer_host/render_widget_host_view.h" 61 #include "content/browser/tab_contents/tab_contents.h" 62 #include "grit/generated_resources.h" 63 #include "grit/locale_settings.h" 64 #include "ui/base/l10n/l10n_util.h" 65 #include "ui/base/l10n/l10n_util_mac.h" 66 67 68 // ORGANIZATION: This is a big file. It is (in principle) organized as follows 69 // (in order): 70 // 1. Interfaces. Very short, one-time-use classes may include an implementation 71 // immediately after their interface. 72 // 2. The general implementation section, ordered as follows: 73 // i. Public methods and overrides. 74 // ii. Overrides/implementations of undocumented methods. 75 // iii. Delegate methods for various protocols, formal and informal, to which 76 // |BrowserWindowController| conforms. 77 // 3. (temporary) Implementation sections for various categories. 78 // 79 // Private methods are defined and implemented separately in 80 // browser_window_controller_private.{h,mm}. 81 // 82 // Not all of the above guidelines are followed and more (re-)organization is 83 // needed. BUT PLEASE TRY TO KEEP THIS FILE ORGANIZED. I'd rather re-organize as 84 // little as possible, since doing so messes up the file's history. 85 // 86 // TODO(viettrungluu): [crbug.com/35543] on-going re-organization, splitting 87 // things into multiple files -- the plan is as follows: 88 // - in general, everything stays in browser_window_controller.h, but is split 89 // off into categories (see below) 90 // - core stuff stays in browser_window_controller.mm 91 // - ... overrides also stay (without going into a category, in particular) 92 // - private stuff which everyone needs goes into 93 // browser_window_controller_private.{h,mm}; if no one else needs them, they 94 // can go in individual files (see below) 95 // - area/task-specific stuff go in browser_window_controller_<area>.mm 96 // - ... in categories called "(<Area>)" or "(<PrivateArea>)" 97 // Plan of action: 98 // - first re-organize into categories 99 // - then split into files 100 101 // Notes on self-inflicted (not user-inflicted) window resizing and moving: 102 // 103 // When the bookmark bar goes from hidden to shown (on a non-NTP) page, or when 104 // the download shelf goes from hidden to shown, we grow the window downwards in 105 // order to maintain a constant content area size. When either goes from shown 106 // to hidden, we consequently shrink the window from the bottom, also to keep 107 // the content area size constant. To keep things simple, if the window is not 108 // entirely on-screen, we don't grow/shrink the window. 109 // 110 // The complications come in when there isn't enough room (on screen) below the 111 // window to accomodate the growth. In this case, we grow the window first 112 // downwards, and then upwards. So, when it comes to shrinking, we do the 113 // opposite: shrink from the top by the amount by which we grew at the top, and 114 // then from the bottom -- unless the user moved/resized/zoomed the window, in 115 // which case we "reset state" and just shrink from the bottom. 116 // 117 // A further complication arises due to the way in which "zoom" ("maximize") 118 // works on Mac OS X. Basically, for our purposes, a window is "zoomed" whenever 119 // it occupies the full available vertical space. (Note that the green zoom 120 // button does not track zoom/unzoomed state per se, but basically relies on 121 // this heuristic.) We don't, in general, want to shrink the window if the 122 // window is zoomed (scenario: window is zoomed, download shelf opens -- which 123 // doesn't cause window growth, download shelf closes -- shouldn't cause the 124 // window to become unzoomed!). However, if we grew the window 125 // (upwards/downwards) to become zoomed in the first place, we *should* shrink 126 // the window by the amounts by which we grew (scenario: window occupies *most* 127 // of vertical space, download shelf opens causing growth so that window 128 // occupies all of vertical space -- i.e., window is effectively zoomed, 129 // download shelf closes -- should return the window to its previous state). 130 // 131 // A major complication is caused by the way grows/shrinks are handled and 132 // animated. Basically, the BWC doesn't see the global picture, but it sees 133 // grows and shrinks in small increments (as dictated by the animation). Thus 134 // window growth/shrinkage (at the top/bottom) have to be tracked incrementally. 135 // Allowing shrinking from the zoomed state also requires tracking: We check on 136 // any shrink whether we're both zoomed and have previously grown -- if so, we 137 // set a flag, and constrain any resize by the allowed amounts. On further 138 // shrinks, we check the flag (since the size/position of the window will no 139 // longer indicate that the window is shrinking from an apparent zoomed state) 140 // and if it's set we continue to constrain the resize. 141 142 143 @interface NSWindow (NSPrivateApis) 144 // Note: These functions are private, use -[NSObject respondsToSelector:] 145 // before calling them. 146 147 - (void)setBottomCornerRounded:(BOOL)rounded; 148 149 - (NSRect)_growBoxRect; 150 151 @end 152 153 // Provide the forward-declarations of new 10.7 SDK symbols so they can be 154 // called when building with the 10.5 SDK. 155 #if !defined(MAC_OS_X_VERSION_10_7) || \ 156 MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7 157 158 @interface NSWindow (LionSDKDeclarations) 159 - (void)setRestorable:(BOOL)flag; 160 @end 161 162 #endif // MAC_OS_X_VERSION_10_7 163 164 // IncognitoImageView subclasses NSView to allow mouse events to pass through it 165 // so you can drag the window by dragging on the spy guy. 166 @interface IncognitoImageView : NSView { 167 @private 168 scoped_nsobject<NSImage> image_; 169 } 170 171 - (void)setImage:(NSImage*)image; 172 173 @end 174 175 @implementation IncognitoImageView 176 177 - (BOOL)mouseDownCanMoveWindow { 178 return YES; 179 } 180 181 - (void)drawRect:(NSRect)rect { 182 [NSGraphicsContext saveGraphicsState]; 183 184 scoped_nsobject<NSShadow> shadow([[NSShadow alloc] init]); 185 [shadow.get() setShadowColor:[NSColor colorWithCalibratedWhite:0.0 186 alpha:0.75]]; 187 [shadow.get() setShadowOffset:NSMakeSize(0, 0)]; 188 [shadow.get() setShadowBlurRadius:3.0]; 189 [shadow.get() set]; 190 191 [image_.get() drawInRect:[self bounds] 192 fromRect:NSZeroRect 193 operation:NSCompositeSourceOver 194 fraction:1.0 195 neverFlipped:YES]; 196 [NSGraphicsContext restoreGraphicsState]; 197 } 198 199 - (void)setImage:(NSImage*)image { 200 image_.reset([image retain]); 201 } 202 203 @end 204 205 206 @implementation BrowserWindowController 207 208 + (BrowserWindowController*)browserWindowControllerForWindow:(NSWindow*)window { 209 while (window) { 210 id controller = [window windowController]; 211 if ([controller isKindOfClass:[BrowserWindowController class]]) 212 return (BrowserWindowController*)controller; 213 window = [window parentWindow]; 214 } 215 return nil; 216 } 217 218 + (BrowserWindowController*)browserWindowControllerForView:(NSView*)view { 219 NSWindow* window = [view window]; 220 return [BrowserWindowController browserWindowControllerForWindow:window]; 221 } 222 223 // Load the browser window nib and do any Cocoa-specific initialization. 224 // Takes ownership of |browser|. Note that the nib also sets this controller 225 // up as the window's delegate. 226 - (id)initWithBrowser:(Browser*)browser { 227 return [self initWithBrowser:browser takeOwnership:YES]; 228 } 229 230 // Private(TestingAPI) init routine with testing options. 231 - (id)initWithBrowser:(Browser*)browser takeOwnership:(BOOL)ownIt { 232 // Use initWithWindowNibPath:: instead of initWithWindowNibName: so we 233 // can override it in a unit test. 234 NSString* nibpath = [base::mac::MainAppBundle() 235 pathForResource:@"BrowserWindow" 236 ofType:@"nib"]; 237 if ((self = [super initWithWindowNibPath:nibpath owner:self])) { 238 DCHECK(browser); 239 initializing_ = YES; 240 browser_.reset(browser); 241 ownsBrowser_ = ownIt; 242 NSWindow* window = [self window]; 243 windowShim_.reset(new BrowserWindowCocoa(browser, self, window)); 244 245 // Create the bar visibility lock set; 10 is arbitrary, but should hopefully 246 // be big enough to hold all locks that'll ever be needed. 247 barVisibilityLocks_.reset([[NSMutableSet setWithCapacity:10] retain]); 248 249 // Sets the window to not have rounded corners, which prevents 250 // the resize control from being inset slightly and looking ugly. 251 if ([window respondsToSelector:@selector(setBottomCornerRounded:)]) 252 [window setBottomCornerRounded:NO]; 253 254 // Lion will attempt to automagically save and restore the UI. This 255 // functionality appears to be leaky (or at least interacts badly with our 256 // architecture) and thus BrowserWindowController never gets released. This 257 // prevents the browser from being able to quit <http://crbug.com/79113>. 258 if ([window respondsToSelector:@selector(setRestorable:)]) 259 [window setRestorable:NO]; 260 261 // Get the most appropriate size for the window, then enforce the 262 // minimum width and height. The window shim will handle flipping 263 // the coordinates for us so we can use it to save some code. 264 // Note that this may leave a significant portion of the window 265 // offscreen, but there will always be enough window onscreen to 266 // drag the whole window back into view. 267 NSSize minSize = [[self window] minSize]; 268 gfx::Rect desiredContentRect = browser_->GetSavedWindowBounds(); 269 gfx::Rect windowRect = desiredContentRect; 270 if (windowRect.width() < minSize.width) 271 windowRect.set_width(minSize.width); 272 if (windowRect.height() < minSize.height) 273 windowRect.set_height(minSize.height); 274 275 // When we are given x/y coordinates of 0 on a created popup window, assume 276 // none were given by the window.open() command. 277 if (browser_->type() & Browser::TYPE_POPUP && 278 windowRect.x() == 0 && windowRect.y() == 0) { 279 gfx::Size size = windowRect.size(); 280 windowRect.set_origin(WindowSizer::GetDefaultPopupOrigin(size)); 281 } 282 283 // Size and position the window. Note that it is not yet onscreen. Popup 284 // windows may get resized later on in this function, once the actual size 285 // of the toolbar/tabstrip is known. 286 windowShim_->SetBounds(windowRect); 287 288 // Puts the incognito badge on the window frame, if necessary. 289 [self installIncognitoBadge]; 290 291 // Create a sub-controller for the docked devTools and add its view to the 292 // hierarchy. This must happen before the sidebar controller is 293 // instantiated. 294 devToolsController_.reset( 295 [[DevToolsController alloc] initWithDelegate:self]); 296 [[devToolsController_ view] setFrame:[[self tabContentArea] bounds]]; 297 [[self tabContentArea] addSubview:[devToolsController_ view]]; 298 299 // Create a sub-controller for the docked sidebar and add its view to the 300 // hierarchy. This must happen before the previewable contents controller 301 // is instantiated. 302 sidebarController_.reset([[SidebarController alloc] initWithDelegate:self]); 303 [[sidebarController_ view] setFrame:[[devToolsController_ view] bounds]]; 304 [[devToolsController_ view] addSubview:[sidebarController_ view]]; 305 306 // Create the previewable contents controller. This provides the switch 307 // view that TabStripController needs. 308 previewableContentsController_.reset( 309 [[PreviewableContentsController alloc] init]); 310 [[previewableContentsController_ view] 311 setFrame:[[sidebarController_ view] bounds]]; 312 [[sidebarController_ view] 313 addSubview:[previewableContentsController_ view]]; 314 315 // Create a controller for the tab strip, giving it the model object for 316 // this window's Browser and the tab strip view. The controller will handle 317 // registering for the appropriate tab notifications from the back-end and 318 // managing the creation of new tabs. 319 [self createTabStripController]; 320 321 // Create a controller for the toolbar, giving it the toolbar model object 322 // and the toolbar view from the nib. The controller will handle 323 // registering for the appropriate command state changes from the back-end. 324 // Adds the toolbar to the content area. 325 toolbarController_.reset([[ToolbarController alloc] 326 initWithModel:browser->toolbar_model() 327 commands:browser->command_updater() 328 profile:browser->profile() 329 browser:browser 330 resizeDelegate:self]); 331 [toolbarController_ setHasToolbar:[self hasToolbar] 332 hasLocationBar:[self hasLocationBar]]; 333 [[[self window] contentView] addSubview:[toolbarController_ view]]; 334 335 // Create a sub-controller for the bookmark bar. 336 bookmarkBarController_.reset( 337 [[BookmarkBarController alloc] 338 initWithBrowser:browser_.get() 339 initialWidth:NSWidth([[[self window] contentView] frame]) 340 delegate:self 341 resizeDelegate:self]); 342 343 // Add bookmark bar to the view hierarchy, which also triggers the nib load. 344 // The bookmark bar is defined (in the nib) to be bottom-aligned to its 345 // parent view (among other things), so position and resize properties don't 346 // need to be set. 347 [[[self window] contentView] addSubview:[bookmarkBarController_ view] 348 positioned:NSWindowBelow 349 relativeTo:[toolbarController_ view]]; 350 [bookmarkBarController_ setBookmarkBarEnabled:[self supportsBookmarkBar]]; 351 352 // Create the infobar container view, so we can pass it to the 353 // ToolbarController. 354 infoBarContainerController_.reset( 355 [[InfoBarContainerController alloc] initWithResizeDelegate:self]); 356 [[[self window] contentView] addSubview:[infoBarContainerController_ view]]; 357 358 // We don't want to try and show the bar before it gets placed in its parent 359 // view, so this step shoudn't be inside the bookmark bar controller's 360 // |-awakeFromNib|. 361 [self updateBookmarkBarVisibilityWithAnimation:NO]; 362 363 // Allow bar visibility to be changed. 364 [self enableBarVisibilityUpdates]; 365 366 // Force a relayout of all the various bars. 367 [self layoutSubviews]; 368 369 // For a popup window, |desiredContentRect| contains the desired height of 370 // the content, not of the whole window. Now that all the views are laid 371 // out, measure the current content area size and grow if needed. The 372 // window has not been placed onscreen yet, so this extra resize will not 373 // cause visible jank. 374 if (browser_->type() & Browser::TYPE_POPUP) { 375 CGFloat deltaH = desiredContentRect.height() - 376 NSHeight([[self tabContentArea] frame]); 377 // Do not shrink the window, as that may break minimum size invariants. 378 if (deltaH > 0) { 379 // Convert from tabContentArea coordinates to window coordinates. 380 NSSize convertedSize = 381 [[self tabContentArea] convertSize:NSMakeSize(0, deltaH) 382 toView:nil]; 383 NSRect frame = [[self window] frame]; 384 frame.size.height += convertedSize.height; 385 frame.origin.y -= convertedSize.height; 386 [[self window] setFrame:frame display:NO]; 387 } 388 } 389 390 // Create the bridge for the status bubble. 391 statusBubble_ = new StatusBubbleMac([self window], self); 392 393 // Register for application hide/unhide notifications. 394 [[NSNotificationCenter defaultCenter] 395 addObserver:self 396 selector:@selector(applicationDidHide:) 397 name:NSApplicationDidHideNotification 398 object:nil]; 399 [[NSNotificationCenter defaultCenter] 400 addObserver:self 401 selector:@selector(applicationDidUnhide:) 402 name:NSApplicationDidUnhideNotification 403 object:nil]; 404 405 // This must be done after the view is added to the window since it relies 406 // on the window bounds to determine whether to show buttons or not. 407 if ([self hasToolbar]) // Do not create the buttons in popups. 408 [toolbarController_ createBrowserActionButtons]; 409 410 [self setUpOSFullScreenButton]; 411 412 // We are done initializing now. 413 initializing_ = NO; 414 } 415 return self; 416 } 417 418 - (void)dealloc { 419 browser_->CloseAllTabs(); 420 [downloadShelfController_ exiting]; 421 422 // Explicitly release |fullscreenController_| here, as it may call back to 423 // this BWC in |-dealloc|. We are required to call |-exitFullscreen| before 424 // releasing the controller. 425 [fullscreenController_ exitFullscreen]; 426 fullscreenController_.reset(); 427 428 // Under certain testing configurations we may not actually own the browser. 429 if (ownsBrowser_ == NO) 430 ignore_result(browser_.release()); 431 432 [[NSNotificationCenter defaultCenter] removeObserver:self]; 433 434 [super dealloc]; 435 } 436 437 - (BrowserWindow*)browserWindow { 438 return windowShim_.get(); 439 } 440 441 - (ToolbarController*)toolbarController { 442 return toolbarController_.get(); 443 } 444 445 - (TabStripController*)tabStripController { 446 return tabStripController_.get(); 447 } 448 449 - (InfoBarContainerController*)infoBarContainerController { 450 return infoBarContainerController_.get(); 451 } 452 453 - (StatusBubbleMac*)statusBubble { 454 return statusBubble_; 455 } 456 457 - (LocationBarViewMac*)locationBarBridge { 458 return [toolbarController_ locationBarBridge]; 459 } 460 461 - (void)destroyBrowser { 462 [NSApp removeWindowsItem:[self window]]; 463 464 // We need the window to go away now. 465 // We can't actually use |-autorelease| here because there's an embedded 466 // run loop in the |-performClose:| which contains its own autorelease pool. 467 // Instead call it after a zero-length delay, which gets us back to the main 468 // event loop. 469 [self performSelector:@selector(autorelease) 470 withObject:nil 471 afterDelay:0]; 472 } 473 474 // Called when the window meets the criteria to be closed (ie, 475 // |-windowShouldClose:| returns YES). We must be careful to preserve the 476 // semantics of BrowserWindow::Close() and not call the Browser's dtor directly 477 // from this method. 478 - (void)windowWillClose:(NSNotification*)notification { 479 DCHECK_EQ([notification object], [self window]); 480 DCHECK(browser_->tabstrip_model()->empty()); 481 [savedRegularWindow_ close]; 482 // We delete statusBubble here because we need to kill off the dependency 483 // that its window has on our window before our window goes away. 484 delete statusBubble_; 485 statusBubble_ = NULL; 486 // We can't actually use |-autorelease| here because there's an embedded 487 // run loop in the |-performClose:| which contains its own autorelease pool. 488 // Instead call it after a zero-length delay, which gets us back to the main 489 // event loop. 490 [self performSelector:@selector(autorelease) 491 withObject:nil 492 afterDelay:0]; 493 } 494 495 - (void)attachConstrainedWindow:(ConstrainedWindowMac*)window { 496 [tabStripController_ attachConstrainedWindow:window]; 497 } 498 499 - (void)removeConstrainedWindow:(ConstrainedWindowMac*)window { 500 [tabStripController_ removeConstrainedWindow:window]; 501 } 502 503 - (BOOL)canAttachConstrainedWindow { 504 return ![previewableContentsController_ isShowingPreview]; 505 } 506 507 - (void)updateDevToolsForContents:(TabContents*)contents { 508 [devToolsController_ updateDevToolsForTabContents:contents 509 withProfile:browser_->profile()]; 510 [devToolsController_ ensureContentsVisible]; 511 } 512 513 - (void)updateSidebarForContents:(TabContents*)contents { 514 [sidebarController_ updateSidebarForTabContents:contents]; 515 [sidebarController_ ensureContentsVisible]; 516 } 517 518 // Called when the user wants to close a window or from the shutdown process. 519 // The Browser object is in control of whether or not we're allowed to close. It 520 // may defer closing due to several states, such as onUnload handlers needing to 521 // be fired. If closing is deferred, the Browser will handle the processing 522 // required to get us to the closing state and (by watching for all the tabs 523 // going away) will again call to close the window when it's finally ready. 524 - (BOOL)windowShouldClose:(id)sender { 525 // Disable updates while closing all tabs to avoid flickering. 526 app::mac::ScopedNSDisableScreenUpdates disabler; 527 // Give beforeunload handlers the chance to cancel the close before we hide 528 // the window below. 529 if (!browser_->ShouldCloseWindow()) 530 return NO; 531 532 // saveWindowPositionIfNeeded: only works if we are the last active 533 // window, but orderOut: ends up activating another window, so we 534 // have to save the window position before we call orderOut:. 535 [self saveWindowPositionIfNeeded]; 536 537 if (!browser_->tabstrip_model()->empty()) { 538 // Tab strip isn't empty. Hide the frame (so it appears to have closed 539 // immediately) and close all the tabs, allowing the renderers to shut 540 // down. When the tab strip is empty we'll be called back again. 541 [[self window] orderOut:self]; 542 browser_->OnWindowClosing(); 543 return NO; 544 } 545 546 // the tab strip is empty, it's ok to close the window 547 return YES; 548 } 549 550 // Called right after our window became the main window. 551 - (void)windowDidBecomeMain:(NSNotification*)notification { 552 BrowserList::SetLastActive(browser_.get()); 553 [self saveWindowPositionIfNeeded]; 554 555 // TODO(dmaclach): Instead of redrawing the whole window, views that care 556 // about the active window state should be registering for notifications. 557 [[self window] setViewsNeedDisplay:YES]; 558 559 // TODO(viettrungluu): For some reason, the above doesn't suffice. 560 if ([self isFullscreen]) 561 [floatingBarBackingView_ setNeedsDisplay:YES]; // Okay even if nil. 562 } 563 564 - (void)windowDidResignMain:(NSNotification*)notification { 565 // TODO(dmaclach): Instead of redrawing the whole window, views that care 566 // about the active window state should be registering for notifications. 567 [[self window] setViewsNeedDisplay:YES]; 568 569 // TODO(viettrungluu): For some reason, the above doesn't suffice. 570 if ([self isFullscreen]) 571 [floatingBarBackingView_ setNeedsDisplay:YES]; // Okay even if nil. 572 } 573 574 // Called when we are activated (when we gain focus). 575 - (void)windowDidBecomeKey:(NSNotification*)notification { 576 // We need to activate the controls (in the "WebView"). To do this, get the 577 // selected TabContents's RenderWidgetHostViewMac and tell it to activate. 578 if (TabContents* contents = browser_->GetSelectedTabContents()) { 579 if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView()) 580 rwhv->SetActive(true); 581 } 582 } 583 584 // Called when we are deactivated (when we lose focus). 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 // We need to deactivate the controls (in the "WebView"). To do this, get the 594 // selected TabContents's RenderWidgetHostView and tell it to deactivate. 595 if (TabContents* contents = browser_->GetSelectedTabContents()) { 596 if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView()) 597 rwhv->SetActive(false); 598 } 599 } 600 601 // Called when we have been minimized. 602 - (void)windowDidMiniaturize:(NSNotification *)notification { 603 // Let the selected RenderWidgetHostView know, so that it can tell plugins. 604 if (TabContents* contents = browser_->GetSelectedTabContents()) { 605 if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView()) 606 rwhv->SetWindowVisibility(false); 607 } 608 } 609 610 // Called when we have been unminimized. 611 - (void)windowDidDeminiaturize:(NSNotification *)notification { 612 // Let the selected RenderWidgetHostView know, so that it can tell plugins. 613 if (TabContents* contents = browser_->GetSelectedTabContents()) { 614 if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView()) 615 rwhv->SetWindowVisibility(true); 616 } 617 } 618 619 // Called when the application has been hidden. 620 - (void)applicationDidHide:(NSNotification *)notification { 621 // Let the selected RenderWidgetHostView know, so that it can tell plugins 622 // (unless we are minimized, in which case nothing has really changed). 623 if (![[self window] isMiniaturized]) { 624 if (TabContents* contents = browser_->GetSelectedTabContents()) { 625 if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView()) 626 rwhv->SetWindowVisibility(false); 627 } 628 } 629 } 630 631 // Called when the application has been unhidden. 632 - (void)applicationDidUnhide:(NSNotification *)notification { 633 // Let the selected RenderWidgetHostView know, so that it can tell plugins 634 // (unless we are minimized, in which case nothing has really changed). 635 if (![[self window] isMiniaturized]) { 636 if (TabContents* contents = browser_->GetSelectedTabContents()) { 637 if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView()) 638 rwhv->SetWindowVisibility(true); 639 } 640 } 641 } 642 643 // Called when the user clicks the zoom button (or selects it from the Window 644 // menu) to determine the "standard size" of the window, based on the content 645 // and other factors. If the current size/location differs nontrivally from the 646 // standard size, Cocoa resizes the window to the standard size, and saves the 647 // current size as the "user size". If the current size/location is the same (up 648 // to a fudge factor) as the standard size, Cocoa resizes the window to the 649 // saved user size. (It is possible for the two to coincide.) In this way, the 650 // zoom button acts as a toggle. We determine the standard size based on the 651 // content, but enforce a minimum width (calculated using the dimensions of the 652 // screen) to ensure websites with small intrinsic width (such as google.com) 653 // don't end up with a wee window. Moreover, we always declare the standard 654 // width to be at least as big as the current width, i.e., we never want zooming 655 // to the standard width to shrink the window. This is consistent with other 656 // browsers' behaviour, and is desirable in multi-tab situations. Note, however, 657 // that the "toggle" behaviour means that the window can still be "unzoomed" to 658 // the user size. 659 - (NSRect)windowWillUseStandardFrame:(NSWindow*)window 660 defaultFrame:(NSRect)frame { 661 // Forget that we grew the window up (if we in fact did). 662 [self resetWindowGrowthState]; 663 664 // |frame| already fills the current screen. Never touch y and height since we 665 // always want to fill vertically. 666 667 // If the shift key is down, maximize. Hopefully this should make the 668 // "switchers" happy. 669 if ([[NSApp currentEvent] modifierFlags] & NSShiftKeyMask) { 670 return frame; 671 } 672 673 // To prevent strange results on portrait displays, the basic minimum zoomed 674 // width is the larger of: 60% of available width, 60% of available height 675 // (bounded by available width). 676 const CGFloat kProportion = 0.6; 677 CGFloat zoomedWidth = 678 std::max(kProportion * frame.size.width, 679 std::min(kProportion * frame.size.height, frame.size.width)); 680 681 TabContents* contents = browser_->GetSelectedTabContents(); 682 if (contents) { 683 // If the intrinsic width is bigger, then make it the zoomed width. 684 const int kScrollbarWidth = 16; // TODO(viettrungluu): ugh. 685 TabContentsViewMac* tab_contents_view = 686 static_cast<TabContentsViewMac*>(contents->view()); 687 CGFloat intrinsicWidth = static_cast<CGFloat>( 688 tab_contents_view->preferred_width() + kScrollbarWidth); 689 zoomedWidth = std::max(zoomedWidth, 690 std::min(intrinsicWidth, frame.size.width)); 691 } 692 693 // Never shrink from the current size on zoom (see above). 694 NSRect currentFrame = [[self window] frame]; 695 zoomedWidth = std::max(zoomedWidth, currentFrame.size.width); 696 697 // |frame| determines our maximum extents. We need to set the origin of the 698 // frame -- and only move it left if necessary. 699 if (currentFrame.origin.x + zoomedWidth > frame.origin.x + frame.size.width) 700 frame.origin.x = frame.origin.x + frame.size.width - zoomedWidth; 701 else 702 frame.origin.x = currentFrame.origin.x; 703 704 // Set the width. Don't touch y or height. 705 frame.size.width = zoomedWidth; 706 707 return frame; 708 } 709 710 - (void)activate { 711 [[self window] makeKeyAndOrderFront:self]; 712 ProcessSerialNumber psn; 713 GetCurrentProcess(&psn); 714 SetFrontProcessWithOptions(&psn, kSetFrontProcessFrontWindowOnly); 715 } 716 717 // Determine whether we should let a window zoom/unzoom to the given |newFrame|. 718 // We avoid letting unzoom move windows between screens, because it's really 719 // strange and unintuitive. 720 - (BOOL)windowShouldZoom:(NSWindow*)window toFrame:(NSRect)newFrame { 721 // Figure out which screen |newFrame| is on. 722 NSScreen* newScreen = nil; 723 CGFloat newScreenOverlapArea = 0.0; 724 for (NSScreen* screen in [NSScreen screens]) { 725 NSRect overlap = NSIntersectionRect(newFrame, [screen frame]); 726 CGFloat overlapArea = overlap.size.width * overlap.size.height; 727 if (overlapArea > newScreenOverlapArea) { 728 newScreen = screen; 729 newScreenOverlapArea = overlapArea; 730 } 731 } 732 // If we're somehow not on any screen, allow the zoom. 733 if (!newScreen) 734 return YES; 735 736 // If the new screen is the current screen, we can return a definitive YES. 737 // Note: This check is not strictly necessary, but just short-circuits in the 738 // "no-brainer" case. To test the complicated logic below, comment this out! 739 NSScreen* curScreen = [window screen]; 740 if (newScreen == curScreen) 741 return YES; 742 743 // Worry a little: What happens when a window is on two (or more) screens? 744 // E.g., what happens in a 50-50 scenario? Cocoa may reasonably elect to zoom 745 // to the other screen rather than staying on the officially current one. So 746 // we compare overlaps with the current window frame, and see if Cocoa's 747 // choice was reasonable (allowing a small rounding error). This should 748 // hopefully avoid us ever erroneously denying a zoom when a window is on 749 // multiple screens. 750 NSRect curFrame = [window frame]; 751 NSRect newScrIntersectCurFr = NSIntersectionRect([newScreen frame], curFrame); 752 NSRect curScrIntersectCurFr = NSIntersectionRect([curScreen frame], curFrame); 753 if (newScrIntersectCurFr.size.width*newScrIntersectCurFr.size.height >= 754 (curScrIntersectCurFr.size.width*curScrIntersectCurFr.size.height - 1.0)) 755 return YES; 756 757 // If it wasn't reasonable, return NO. 758 return NO; 759 } 760 761 // Adjusts the window height by the given amount. 762 - (void)adjustWindowHeightBy:(CGFloat)deltaH { 763 // By not adjusting the window height when initializing, we can ensure that 764 // the window opens with the same size that was saved on close. 765 if (initializing_ || [self isFullscreen] || deltaH == 0) 766 return; 767 768 NSWindow* window = [self window]; 769 NSRect windowFrame = [window frame]; 770 NSRect workarea = [[window screen] visibleFrame]; 771 772 // If the window is not already fully in the workarea, do not adjust its frame 773 // at all. 774 if (!NSContainsRect(workarea, windowFrame)) 775 return; 776 777 // Record the position of the top/bottom of the window, so we can easily check 778 // whether we grew the window upwards/downwards. 779 CGFloat oldWindowMaxY = NSMaxY(windowFrame); 780 CGFloat oldWindowMinY = NSMinY(windowFrame); 781 782 // We are "zoomed" if we occupy the full vertical space. 783 bool isZoomed = (windowFrame.origin.y == workarea.origin.y && 784 windowFrame.size.height == workarea.size.height); 785 786 // If we're shrinking the window.... 787 if (deltaH < 0) { 788 bool didChange = false; 789 790 // Don't reset if not currently zoomed since shrinking can take several 791 // steps! 792 if (isZoomed) 793 isShrinkingFromZoomed_ = YES; 794 795 // If we previously grew at the top, shrink as much as allowed at the top 796 // first. 797 if (windowTopGrowth_ > 0) { 798 CGFloat shrinkAtTopBy = MIN(-deltaH, windowTopGrowth_); 799 windowFrame.size.height -= shrinkAtTopBy; // Shrink the window. 800 deltaH += shrinkAtTopBy; // Update the amount left to shrink. 801 windowTopGrowth_ -= shrinkAtTopBy; // Update the growth state. 802 didChange = true; 803 } 804 805 // Similarly for the bottom (not an "else if" since we may have to 806 // simultaneously shrink at both the top and at the bottom). Note that 807 // |deltaH| may no longer be nonzero due to the above. 808 if (deltaH < 0 && windowBottomGrowth_ > 0) { 809 CGFloat shrinkAtBottomBy = MIN(-deltaH, windowBottomGrowth_); 810 windowFrame.origin.y += shrinkAtBottomBy; // Move the window up. 811 windowFrame.size.height -= shrinkAtBottomBy; // Shrink the window. 812 deltaH += shrinkAtBottomBy; // Update the amount left.... 813 windowBottomGrowth_ -= shrinkAtBottomBy; // Update the growth state. 814 didChange = true; 815 } 816 817 // If we're shrinking from zoomed but we didn't change the top or bottom 818 // (since we've reached the limits imposed by |window...Growth_|), then stop 819 // here. Don't reset |isShrinkingFromZoomed_| since we might get called 820 // again for the same shrink. 821 if (isShrinkingFromZoomed_ && !didChange) 822 return; 823 } else { 824 isShrinkingFromZoomed_ = NO; 825 826 // Don't bother with anything else. 827 if (isZoomed) 828 return; 829 } 830 831 // Shrinking from zoomed is handled above (and is constrained by 832 // |window...Growth_|). 833 if (!isShrinkingFromZoomed_) { 834 // Resize the window down until it hits the bottom of the workarea, then if 835 // needed continue resizing upwards. Do not resize the window to be taller 836 // than the current workarea. 837 // Resize the window as requested, keeping the top left corner fixed. 838 windowFrame.origin.y -= deltaH; 839 windowFrame.size.height += deltaH; 840 841 // If the bottom left corner is now outside the visible frame, move the 842 // window up to make it fit, but make sure not to move the top left corner 843 // out of the visible frame. 844 if (windowFrame.origin.y < workarea.origin.y) { 845 windowFrame.origin.y = workarea.origin.y; 846 windowFrame.size.height = 847 std::min(windowFrame.size.height, workarea.size.height); 848 } 849 850 // Record (if applicable) how much we grew the window in either direction. 851 // (N.B.: These only record growth, not shrinkage.) 852 if (NSMaxY(windowFrame) > oldWindowMaxY) 853 windowTopGrowth_ += NSMaxY(windowFrame) - oldWindowMaxY; 854 if (NSMinY(windowFrame) < oldWindowMinY) 855 windowBottomGrowth_ += oldWindowMinY - NSMinY(windowFrame); 856 } 857 858 // Disable subview resizing while resizing the window, or else we will get 859 // unwanted renderer resizes. The calling code must call layoutSubviews to 860 // make things right again. 861 NSView* contentView = [window contentView]; 862 [contentView setAutoresizesSubviews:NO]; 863 [window setFrame:windowFrame display:NO]; 864 [contentView setAutoresizesSubviews:YES]; 865 } 866 867 // Main method to resize browser window subviews. This method should be called 868 // when resizing any child of the content view, rather than resizing the views 869 // directly. If the view is already the correct height, does not force a 870 // relayout. 871 - (void)resizeView:(NSView*)view newHeight:(CGFloat)height { 872 // We should only ever be called for one of the following four views. 873 // |downloadShelfController_| may be nil. If we are asked to size the bookmark 874 // bar directly, its superview must be this controller's content view. 875 DCHECK(view); 876 DCHECK(view == [toolbarController_ view] || 877 view == [infoBarContainerController_ view] || 878 view == [downloadShelfController_ view] || 879 view == [bookmarkBarController_ view]); 880 881 // Change the height of the view and call |-layoutSubViews|. We set the height 882 // here without regard to where the view is on the screen or whether it needs 883 // to "grow up" or "grow down." The below call to |-layoutSubviews| will 884 // position each view correctly. 885 NSRect frame = [view frame]; 886 if (NSHeight(frame) == height) 887 return; 888 889 // Grow or shrink the window by the amount of the height change. We adjust 890 // the window height only in two cases: 891 // 1) We are adjusting the height of the bookmark bar and it is currently 892 // animating either open or closed. 893 // 2) We are adjusting the height of the download shelf. 894 // 895 // We do not adjust the window height for bookmark bar changes on the NTP. 896 BOOL shouldAdjustBookmarkHeight = 897 [bookmarkBarController_ isAnimatingBetweenState:bookmarks::kHiddenState 898 andState:bookmarks::kShowingState]; 899 if ((shouldAdjustBookmarkHeight && view == [bookmarkBarController_ view]) || 900 view == [downloadShelfController_ view]) { 901 [[self window] disableScreenUpdatesUntilFlush]; 902 CGFloat deltaH = height - frame.size.height; 903 [self adjustWindowHeightBy:deltaH]; 904 } 905 906 frame.size.height = height; 907 // TODO(rohitrao): Determine if calling setFrame: twice is bad. 908 [view setFrame:frame]; 909 [self layoutSubviews]; 910 } 911 912 - (void)setAnimationInProgress:(BOOL)inProgress { 913 [[self tabContentArea] setFastResizeMode:inProgress]; 914 } 915 916 // Update a toggle state for an NSMenuItem if modified. 917 // Take care to ensure |item| looks like a NSMenuItem. 918 // Called by validateUserInterfaceItem:. 919 - (void)updateToggleStateWithTag:(NSInteger)tag forItem:(id)item { 920 if (![item respondsToSelector:@selector(state)] || 921 ![item respondsToSelector:@selector(setState:)]) 922 return; 923 924 // On Windows this logic happens in bookmark_bar_view.cc. On the 925 // Mac we're a lot more MVC happy so we've moved it into a 926 // controller. To be clear, this simply updates the menu item; it 927 // does not display the bookmark bar itself. 928 if (tag == IDC_SHOW_BOOKMARK_BAR) { 929 bool toggled = windowShim_->IsBookmarkBarVisible(); 930 NSInteger oldState = [item state]; 931 NSInteger newState = toggled ? NSOnState : NSOffState; 932 if (oldState != newState) 933 [item setState:newState]; 934 } 935 936 // Update the checked/Unchecked state of items in the encoding menu. 937 // On Windows, this logic is part of |EncodingMenuModel| in 938 // browser/ui/views/toolbar_view.h. 939 EncodingMenuController encoding_controller; 940 if (encoding_controller.DoesCommandBelongToEncodingMenu(tag)) { 941 DCHECK(browser_.get()); 942 Profile* profile = browser_->profile(); 943 DCHECK(profile); 944 TabContents* current_tab = browser_->GetSelectedTabContents(); 945 if (!current_tab) { 946 return; 947 } 948 const std::string encoding = current_tab->encoding(); 949 950 bool toggled = encoding_controller.IsItemChecked(profile, encoding, tag); 951 NSInteger oldState = [item state]; 952 NSInteger newState = toggled ? NSOnState : NSOffState; 953 if (oldState != newState) 954 [item setState:newState]; 955 } 956 } 957 958 - (BOOL)supportsFullscreen { 959 // TODO(avi, thakis): GTMWindowSheetController has no api to move 960 // tabsheets between windows. Until then, we have to prevent having to 961 // move a tabsheet between windows, e.g. no fullscreen toggling 962 NSArray* a = [[tabStripController_ sheetController] viewsWithAttachedSheets]; 963 return [a count] == 0; 964 } 965 966 // Called to validate menu and toolbar items when this window is key. All the 967 // items we care about have been set with the |-commandDispatch:| or 968 // |-commandDispatchUsingKeyModifiers:| actions and a target of FirstResponder 969 // in IB. If it's not one of those, let it continue up the responder chain to be 970 // handled elsewhere. We pull out the tag as the cross-platform constant to 971 // differentiate and dispatch the various commands. 972 // NOTE: we might have to handle state for app-wide menu items, 973 // although we could cheat and directly ask the app controller if our 974 // command_updater doesn't support the command. This may or may not be an issue, 975 // too early to tell. 976 - (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item { 977 SEL action = [item action]; 978 BOOL enable = NO; 979 if (action == @selector(commandDispatch:) || 980 action == @selector(commandDispatchUsingKeyModifiers:)) { 981 NSInteger tag = [item tag]; 982 if (browser_->command_updater()->SupportsCommand(tag)) { 983 // Generate return value (enabled state) 984 enable = browser_->command_updater()->IsCommandEnabled(tag); 985 switch (tag) { 986 case IDC_CLOSE_TAB: 987 // Disable "close tab" if we're not the key window or if there's only 988 // one tab. 989 enable &= [self numberOfTabs] > 1 && [[self window] isKeyWindow]; 990 break; 991 case IDC_FULLSCREEN: { 992 enable &= [self supportsFullscreen]; 993 if ([static_cast<NSObject*>(item) isKindOfClass:[NSMenuItem class]]) { 994 NSString* menuTitle = l10n_util::GetNSString( 995 [self isFullscreen] ? IDS_EXIT_FULLSCREEN_MAC : 996 IDS_ENTER_FULLSCREEN_MAC); 997 [static_cast<NSMenuItem*>(item) setTitle:menuTitle]; 998 } 999 break; 1000 } 1001 case IDC_SYNC_BOOKMARKS: 1002 enable &= browser_->profile()->IsSyncAccessible(); 1003 sync_ui_util::UpdateSyncItem(item, enable, browser_->profile()); 1004 break; 1005 default: 1006 // Special handling for the contents of the Text Encoding submenu. On 1007 // Mac OS, instead of enabling/disabling the top-level menu item, we 1008 // enable/disable the submenu's contents (per Apple's HIG). 1009 EncodingMenuController encoding_controller; 1010 if (encoding_controller.DoesCommandBelongToEncodingMenu(tag)) { 1011 enable &= browser_->command_updater()->IsCommandEnabled( 1012 IDC_ENCODING_MENU) ? YES : NO; 1013 } 1014 } 1015 1016 // If the item is toggleable, find its toggle state and 1017 // try to update it. This is a little awkward, but the alternative is 1018 // to check after a commandDispatch, which seems worse. 1019 [self updateToggleStateWithTag:tag forItem:item]; 1020 } 1021 } 1022 return enable; 1023 } 1024 1025 // Called when the user picks a menu or toolbar item when this window is key. 1026 // Calls through to the browser object to execute the command. This assumes that 1027 // the command is supported and doesn't check, otherwise it would have been 1028 // disabled in the UI in validateUserInterfaceItem:. 1029 - (void)commandDispatch:(id)sender { 1030 DCHECK(sender); 1031 // Identify the actual BWC to which the command should be dispatched. It might 1032 // belong to a background window, yet this controller gets it because it is 1033 // the foreground window's controller and thus in the responder chain. Some 1034 // senders don't have this problem (for example, menus only operate on the 1035 // foreground window), so this is only an issue for senders that are part of 1036 // windows. 1037 BrowserWindowController* targetController = self; 1038 if ([sender respondsToSelector:@selector(window)]) 1039 targetController = [[sender window] windowController]; 1040 DCHECK([targetController isKindOfClass:[BrowserWindowController class]]); 1041 DCHECK(targetController->browser_.get()); 1042 targetController->browser_->ExecuteCommand([sender tag]); 1043 } 1044 1045 // Same as |-commandDispatch:|, but executes commands using a disposition 1046 // determined by the key flags. If the window is in the background and the 1047 // command key is down, ignore the command key, but process any other modifiers. 1048 - (void)commandDispatchUsingKeyModifiers:(id)sender { 1049 DCHECK(sender); 1050 // See comment above for why we do this. 1051 BrowserWindowController* targetController = self; 1052 if ([sender respondsToSelector:@selector(window)]) 1053 targetController = [[sender window] windowController]; 1054 DCHECK([targetController isKindOfClass:[BrowserWindowController class]]); 1055 NSInteger command = [sender tag]; 1056 NSUInteger modifierFlags = [[NSApp currentEvent] modifierFlags]; 1057 if ((command == IDC_RELOAD) && 1058 (modifierFlags & (NSShiftKeyMask | NSControlKeyMask))) { 1059 command = IDC_RELOAD_IGNORING_CACHE; 1060 // Mask off Shift and Control so they don't affect the disposition below. 1061 modifierFlags &= ~(NSShiftKeyMask | NSControlKeyMask); 1062 } 1063 if (![[sender window] isMainWindow]) { 1064 // Remove the command key from the flags, it means "keep the window in 1065 // the background" in this case. 1066 modifierFlags &= ~NSCommandKeyMask; 1067 } 1068 WindowOpenDisposition disposition = 1069 event_utils::WindowOpenDispositionFromNSEventWithFlags( 1070 [NSApp currentEvent], modifierFlags); 1071 switch (command) { 1072 case IDC_BACK: 1073 case IDC_FORWARD: 1074 case IDC_RELOAD: 1075 case IDC_RELOAD_IGNORING_CACHE: 1076 if (disposition == CURRENT_TAB) { 1077 // Forcibly reset the location bar, since otherwise it won't discard any 1078 // ongoing user edits, since it doesn't realize this is a user-initiated 1079 // action. 1080 [targetController locationBarBridge]->Revert(); 1081 } 1082 } 1083 DCHECK(targetController->browser_.get()); 1084 targetController->browser_->ExecuteCommandWithDisposition(command, 1085 disposition); 1086 } 1087 1088 // Called when another part of the internal codebase needs to execute a 1089 // command. 1090 - (void)executeCommand:(int)command { 1091 browser_->ExecuteCommandIfEnabled(command); 1092 } 1093 1094 // StatusBubble delegate method: tell the status bubble the frame it should 1095 // position itself in. 1096 - (NSRect)statusBubbleBaseFrame { 1097 NSView* view = [previewableContentsController_ view]; 1098 return [view convertRect:[view bounds] toView:nil]; 1099 } 1100 1101 - (GTMWindowSheetController*)sheetController { 1102 return [tabStripController_ sheetController]; 1103 } 1104 1105 - (void)updateToolbarWithContents:(TabContents*)tab 1106 shouldRestoreState:(BOOL)shouldRestore { 1107 [toolbarController_ updateToolbarWithContents:tab 1108 shouldRestoreState:shouldRestore]; 1109 } 1110 1111 - (void)setStarredState:(BOOL)isStarred { 1112 [toolbarController_ setStarredState:isStarred]; 1113 } 1114 1115 // Accept tabs from a BrowserWindowController with the same Profile. 1116 - (BOOL)canReceiveFrom:(TabWindowController*)source { 1117 if (![source isKindOfClass:[BrowserWindowController class]]) { 1118 return NO; 1119 } 1120 1121 BrowserWindowController* realSource = 1122 static_cast<BrowserWindowController*>(source); 1123 if (browser_->profile() != realSource->browser_->profile()) { 1124 return NO; 1125 } 1126 1127 // Can't drag a tab from a normal browser to a pop-up 1128 if (browser_->type() != realSource->browser_->type()) { 1129 return NO; 1130 } 1131 1132 return YES; 1133 } 1134 1135 // Move a given tab view to the location of the current placeholder. If there is 1136 // no placeholder, it will go at the end. |controller| is the window controller 1137 // of a tab being dropped from a different window. It will be nil if the drag is 1138 // within the window, otherwise the tab is removed from that window before being 1139 // placed into this one. The implementation will call |-removePlaceholder| since 1140 // the drag is now complete. This also calls |-layoutTabs| internally so 1141 // clients do not need to call it again. 1142 - (void)moveTabView:(NSView*)view 1143 fromController:(TabWindowController*)dragController { 1144 if (dragController) { 1145 // Moving between windows. Figure out the TabContents to drop into our tab 1146 // model from the source window's model. 1147 BOOL isBrowser = 1148 [dragController isKindOfClass:[BrowserWindowController class]]; 1149 DCHECK(isBrowser); 1150 if (!isBrowser) return; 1151 BrowserWindowController* dragBWC = (BrowserWindowController*)dragController; 1152 int index = [dragBWC->tabStripController_ modelIndexForTabView:view]; 1153 TabContentsWrapper* contents = 1154 dragBWC->browser_->GetTabContentsWrapperAt(index); 1155 // The tab contents may have gone away if given a window.close() while it 1156 // is being dragged. If so, bail, we've got nothing to drop. 1157 if (!contents) 1158 return; 1159 1160 // Convert |view|'s frame (which starts in the source tab strip's coordinate 1161 // system) to the coordinate system of the destination tab strip. This needs 1162 // to be done before being detached so the window transforms can be 1163 // performed. 1164 NSRect destinationFrame = [view frame]; 1165 NSPoint tabOrigin = destinationFrame.origin; 1166 tabOrigin = [[dragController tabStripView] convertPoint:tabOrigin 1167 toView:nil]; 1168 tabOrigin = [[view window] convertBaseToScreen:tabOrigin]; 1169 tabOrigin = [[self window] convertScreenToBase:tabOrigin]; 1170 tabOrigin = [[self tabStripView] convertPoint:tabOrigin fromView:nil]; 1171 destinationFrame.origin = tabOrigin; 1172 1173 // Before the tab is detached from its originating tab strip, store the 1174 // pinned state so that it can be maintained between the windows. 1175 bool isPinned = dragBWC->browser_->tabstrip_model()->IsTabPinned(index); 1176 1177 // Now that we have enough information about the tab, we can remove it from 1178 // the dragging window. We need to do this *before* we add it to the new 1179 // window as this will remove the TabContents' delegate. 1180 [dragController detachTabView:view]; 1181 1182 // Deposit it into our model at the appropriate location (it already knows 1183 // where it should go from tracking the drag). Doing this sets the tab's 1184 // delegate to be the Browser. 1185 [tabStripController_ dropTabContents:contents 1186 withFrame:destinationFrame 1187 asPinnedTab:isPinned]; 1188 } else { 1189 // Moving within a window. 1190 int index = [tabStripController_ modelIndexForTabView:view]; 1191 [tabStripController_ moveTabFromIndex:index]; 1192 } 1193 1194 // Remove the placeholder since the drag is now complete. 1195 [self removePlaceholder]; 1196 } 1197 1198 // Tells the tab strip to forget about this tab in preparation for it being 1199 // put into a different tab strip, such as during a drop on another window. 1200 - (void)detachTabView:(NSView*)view { 1201 int index = [tabStripController_ modelIndexForTabView:view]; 1202 browser_->tabstrip_model()->DetachTabContentsAt(index); 1203 } 1204 1205 - (NSView*)selectedTabView { 1206 return [tabStripController_ selectedTabView]; 1207 } 1208 1209 - (void)setIsLoading:(BOOL)isLoading force:(BOOL)force { 1210 [toolbarController_ setIsLoading:isLoading force:force]; 1211 } 1212 1213 // Make the location bar the first responder, if possible. 1214 - (void)focusLocationBar:(BOOL)selectAll { 1215 [toolbarController_ focusLocationBar:selectAll]; 1216 } 1217 1218 - (void)focusTabContents { 1219 [[self window] makeFirstResponder:[tabStripController_ selectedTabView]]; 1220 } 1221 1222 - (void)layoutTabs { 1223 [tabStripController_ layoutTabs]; 1224 } 1225 1226 - (TabWindowController*)detachTabToNewWindow:(TabView*)tabView { 1227 // Disable screen updates so that this appears as a single visual change. 1228 app::mac::ScopedNSDisableScreenUpdates disabler; 1229 1230 // Fetch the tab contents for the tab being dragged. 1231 int index = [tabStripController_ modelIndexForTabView:tabView]; 1232 TabContentsWrapper* contents = browser_->GetTabContentsWrapperAt(index); 1233 1234 // Set the window size. Need to do this before we detach the tab so it's 1235 // still in the window. We have to flip the coordinates as that's what 1236 // is expected by the Browser code. 1237 NSWindow* sourceWindow = [tabView window]; 1238 NSRect windowRect = [sourceWindow frame]; 1239 NSScreen* screen = [sourceWindow screen]; 1240 windowRect.origin.y = 1241 [screen frame].size.height - windowRect.size.height - 1242 windowRect.origin.y; 1243 gfx::Rect browserRect(windowRect.origin.x, windowRect.origin.y, 1244 windowRect.size.width, windowRect.size.height); 1245 1246 NSRect sourceTabRect = [tabView frame]; 1247 NSView* tabStrip = [self tabStripView]; 1248 1249 // Pushes tabView's frame back inside the tabstrip. 1250 NSSize tabOverflow = 1251 [self overflowFrom:[tabStrip convertRectToBase:sourceTabRect] 1252 to:[tabStrip frame]]; 1253 NSRect tabRect = NSOffsetRect(sourceTabRect, 1254 -tabOverflow.width, -tabOverflow.height); 1255 1256 // Before detaching the tab, store the pinned state. 1257 bool isPinned = browser_->tabstrip_model()->IsTabPinned(index); 1258 1259 // Detach it from the source window, which just updates the model without 1260 // deleting the tab contents. This needs to come before creating the new 1261 // Browser because it clears the TabContents' delegate, which gets hooked 1262 // up during creation of the new window. 1263 browser_->tabstrip_model()->DetachTabContentsAt(index); 1264 1265 // Create the new window with a single tab in its model, the one being 1266 // dragged. 1267 DockInfo dockInfo; 1268 Browser* newBrowser = browser_->tabstrip_model()->delegate()-> 1269 CreateNewStripWithContents(contents, browserRect, dockInfo, false); 1270 1271 // Propagate the tab pinned state of the new tab (which is the only tab in 1272 // this new window). 1273 newBrowser->tabstrip_model()->SetTabPinned(0, isPinned); 1274 1275 // Get the new controller by asking the new window for its delegate. 1276 BrowserWindowController* controller = 1277 reinterpret_cast<BrowserWindowController*>( 1278 [newBrowser->window()->GetNativeHandle() delegate]); 1279 DCHECK(controller && [controller isKindOfClass:[TabWindowController class]]); 1280 1281 // Force the added tab to the right size (remove stretching.) 1282 tabRect.size.height = [TabStripController defaultTabHeight]; 1283 1284 // And make sure we use the correct frame in the new view. 1285 [[controller tabStripController] setFrameOfSelectedTab:tabRect]; 1286 return controller; 1287 } 1288 1289 - (void)insertPlaceholderForTab:(TabView*)tab 1290 frame:(NSRect)frame 1291 yStretchiness:(CGFloat)yStretchiness { 1292 [super insertPlaceholderForTab:tab frame:frame yStretchiness:yStretchiness]; 1293 [tabStripController_ insertPlaceholderForTab:tab 1294 frame:frame 1295 yStretchiness:yStretchiness]; 1296 } 1297 1298 - (void)removePlaceholder { 1299 [super removePlaceholder]; 1300 [tabStripController_ insertPlaceholderForTab:nil 1301 frame:NSZeroRect 1302 yStretchiness:0]; 1303 } 1304 1305 - (BOOL)isDragSessionActive { 1306 // The tab can be dragged within the existing tab strip or detached 1307 // into its own window (then the overlay window will be present). 1308 return [[self tabStripController] isDragSessionActive] || 1309 [self overlayWindow] != nil; 1310 } 1311 1312 - (BOOL)tabDraggingAllowed { 1313 return [tabStripController_ tabDraggingAllowed]; 1314 } 1315 1316 - (BOOL)tabTearingAllowed { 1317 return ![self isFullscreen]; 1318 } 1319 1320 - (BOOL)windowMovementAllowed { 1321 return ![self isFullscreen]; 1322 } 1323 1324 - (BOOL)isTabFullyVisible:(TabView*)tab { 1325 return [tabStripController_ isTabFullyVisible:tab]; 1326 } 1327 1328 - (void)showNewTabButton:(BOOL)show { 1329 [tabStripController_ showNewTabButton:show]; 1330 } 1331 1332 - (BOOL)isBookmarkBarVisible { 1333 return [bookmarkBarController_ isVisible]; 1334 } 1335 1336 - (BOOL)isBookmarkBarAnimating { 1337 return [bookmarkBarController_ isAnimationRunning]; 1338 } 1339 1340 - (void)updateBookmarkBarVisibilityWithAnimation:(BOOL)animate { 1341 [bookmarkBarController_ 1342 updateAndShowNormalBar:[self shouldShowBookmarkBar] 1343 showDetachedBar:[self shouldShowDetachedBookmarkBar] 1344 withAnimation:animate]; 1345 } 1346 1347 - (BOOL)isDownloadShelfVisible { 1348 return downloadShelfController_ != nil && 1349 [downloadShelfController_ isVisible]; 1350 } 1351 1352 - (DownloadShelfController*)downloadShelf { 1353 if (!downloadShelfController_.get()) { 1354 downloadShelfController_.reset([[DownloadShelfController alloc] 1355 initWithBrowser:browser_.get() resizeDelegate:self]); 1356 [[[self window] contentView] addSubview:[downloadShelfController_ view]]; 1357 [downloadShelfController_ show:nil]; 1358 } 1359 return downloadShelfController_; 1360 } 1361 1362 - (void)addFindBar:(FindBarCocoaController*)findBarCocoaController { 1363 // Shouldn't call addFindBar twice. 1364 DCHECK(!findBarCocoaController_.get()); 1365 1366 // Create a controller for the findbar. 1367 findBarCocoaController_.reset([findBarCocoaController retain]); 1368 NSView *contentView = [[self window] contentView]; 1369 [contentView addSubview:[findBarCocoaController_ view] 1370 positioned:NSWindowAbove 1371 relativeTo:[infoBarContainerController_ view]]; 1372 1373 // Place the find bar immediately below the toolbar/attached bookmark bar. In 1374 // fullscreen mode, it hangs off the top of the screen when the bar is hidden. 1375 CGFloat maxY = [self placeBookmarkBarBelowInfoBar] ? 1376 NSMinY([[toolbarController_ view] frame]) : 1377 NSMinY([[bookmarkBarController_ view] frame]); 1378 CGFloat maxWidth = NSWidth([contentView frame]); 1379 [findBarCocoaController_ positionFindBarViewAtMaxY:maxY maxWidth:maxWidth]; 1380 1381 // This allows the FindBarCocoaController to call |layoutSubviews| and get 1382 // its position adjusted. 1383 [findBarCocoaController_ setBrowserWindowController:self]; 1384 } 1385 1386 - (NSWindow*)createFullscreenWindow { 1387 return [[[FullscreenWindow alloc] initForScreen:[[self window] screen]] 1388 autorelease]; 1389 } 1390 1391 - (NSInteger)numberOfTabs { 1392 // count() includes pinned tabs. 1393 return browser_->tabstrip_model()->count(); 1394 } 1395 1396 - (BOOL)hasLiveTabs { 1397 return !browser_->tabstrip_model()->empty(); 1398 } 1399 1400 - (NSString*)selectedTabTitle { 1401 TabContents* contents = browser_->GetSelectedTabContents(); 1402 return base::SysUTF16ToNSString(contents->GetTitle()); 1403 } 1404 1405 - (NSRect)regularWindowFrame { 1406 return [self isFullscreen] ? [savedRegularWindow_ frame] : 1407 [[self window] frame]; 1408 } 1409 1410 // (Override of |TabWindowController| method.) 1411 - (BOOL)hasTabStrip { 1412 return [self supportsWindowFeature:Browser::FEATURE_TABSTRIP]; 1413 } 1414 1415 // TabContentsControllerDelegate protocol. 1416 - (void)tabContentsViewFrameWillChange:(TabContentsController*)source 1417 frameRect:(NSRect)frameRect { 1418 TabContents* contents = [source tabContents]; 1419 RenderWidgetHostView* render_widget_host_view = contents ? 1420 contents->GetRenderWidgetHostView() : NULL; 1421 if (!render_widget_host_view) 1422 return; 1423 1424 gfx::Rect reserved_rect; 1425 1426 NSWindow* window = [self window]; 1427 if ([window respondsToSelector:@selector(_growBoxRect)]) { 1428 NSView* view = [source view]; 1429 if (view && [view superview]) { 1430 NSRect windowGrowBoxRect = [window _growBoxRect]; 1431 NSRect viewRect = [[view superview] convertRect:frameRect toView:nil]; 1432 NSRect growBoxRect = NSIntersectionRect(windowGrowBoxRect, viewRect); 1433 if (!NSIsEmptyRect(growBoxRect)) { 1434 // Before we return a rect, we need to convert it from window 1435 // coordinates to content area coordinates and flip the coordinate 1436 // system. 1437 // Superview is used here because, first, it's a frame rect, so it is 1438 // specified in the parent's coordinates and, second, view is not 1439 // positioned yet. 1440 growBoxRect = [[view superview] convertRect:growBoxRect fromView:nil]; 1441 growBoxRect.origin.y = 1442 NSHeight(frameRect) - NSHeight(growBoxRect); 1443 growBoxRect = 1444 NSOffsetRect(growBoxRect, -frameRect.origin.x, -frameRect.origin.y); 1445 1446 reserved_rect = 1447 gfx::Rect(growBoxRect.origin.x, growBoxRect.origin.y, 1448 growBoxRect.size.width, growBoxRect.size.height); 1449 } 1450 } 1451 } 1452 1453 render_widget_host_view->set_reserved_contents_rect(reserved_rect); 1454 } 1455 1456 // TabStripControllerDelegate protocol. 1457 - (void)onSelectTabWithContents:(TabContents*)contents { 1458 // Update various elements that are interested in knowing the current 1459 // TabContents. 1460 1461 // Update all the UI bits. 1462 windowShim_->UpdateTitleBar(); 1463 1464 [sidebarController_ updateSidebarForTabContents:contents]; 1465 [devToolsController_ updateDevToolsForTabContents:contents 1466 withProfile:browser_->profile()]; 1467 1468 // Update the bookmark bar. 1469 // Must do it after sidebar and devtools update, otherwise bookmark bar might 1470 // call resizeView -> layoutSubviews and cause unnecessary relayout. 1471 // TODO(viettrungluu): perhaps update to not terminate running animations (if 1472 // applicable)? 1473 [self updateBookmarkBarVisibilityWithAnimation:NO]; 1474 1475 [infoBarContainerController_ changeTabContents:contents]; 1476 1477 // Update devTools and sidebar contents after size for all views is set. 1478 [sidebarController_ ensureContentsVisible]; 1479 [devToolsController_ ensureContentsVisible]; 1480 } 1481 1482 - (void)onReplaceTabWithContents:(TabContents*)contents { 1483 // This is only called when instant results are committed. Simply remove the 1484 // preview view; the tab strip controller will reinstall the view as the 1485 // active view. 1486 [previewableContentsController_ hidePreview]; 1487 [self updateBookmarkBarVisibilityWithAnimation:NO]; 1488 } 1489 1490 - (void)onSelectedTabChange:(TabStripModelObserver::TabChangeType)change { 1491 // Update titles if this is the currently selected tab and if it isn't just 1492 // the loading state which changed. 1493 if (change != TabStripModelObserver::LOADING_ONLY) 1494 windowShim_->UpdateTitleBar(); 1495 1496 // Update the bookmark bar if this is the currently selected tab and if it 1497 // isn't just the title which changed. This for transitions between the NTP 1498 // (showing its floating bookmark bar) and normal web pages (showing no 1499 // bookmark bar). 1500 // TODO(viettrungluu): perhaps update to not terminate running animations? 1501 if (change != TabStripModelObserver::TITLE_NOT_LOADING) 1502 [self updateBookmarkBarVisibilityWithAnimation:NO]; 1503 } 1504 1505 - (void)onTabDetachedWithContents:(TabContents*)contents { 1506 [infoBarContainerController_ tabDetachedWithContents:contents]; 1507 } 1508 1509 - (void)userChangedTheme { 1510 // TODO(dmaclach): Instead of redrawing the whole window, views that care 1511 // about the active window state should be registering for notifications. 1512 [[self window] setViewsNeedDisplay:YES]; 1513 } 1514 1515 - (ui::ThemeProvider*)themeProvider { 1516 return ThemeServiceFactory::GetForProfile(browser_->profile()); 1517 } 1518 1519 - (ThemedWindowStyle)themedWindowStyle { 1520 ThemedWindowStyle style = 0; 1521 if (browser_->profile()->IsOffTheRecord()) 1522 style |= THEMED_INCOGNITO; 1523 1524 Browser::Type type = browser_->type(); 1525 if (type == Browser::TYPE_POPUP) 1526 style |= THEMED_POPUP; 1527 else if (type == Browser::TYPE_DEVTOOLS) 1528 style |= THEMED_DEVTOOLS; 1529 1530 return style; 1531 } 1532 1533 - (NSPoint)themePatternPhase { 1534 // Our patterns want to be drawn from the upper left hand corner of the view. 1535 // Cocoa wants to do it from the lower left of the window. 1536 // 1537 // Rephase our pattern to fit this view. Some other views (Tabs, Toolbar etc.) 1538 // will phase their patterns relative to this so all the views look right. 1539 // 1540 // To line up the background pattern with the pattern in the browser window 1541 // the background pattern for the tabs needs to be moved left by 5 pixels. 1542 const CGFloat kPatternHorizontalOffset = -5; 1543 NSView* tabStripView = [self tabStripView]; 1544 NSRect tabStripViewWindowBounds = [tabStripView bounds]; 1545 NSView* windowChromeView = [[[self window] contentView] superview]; 1546 tabStripViewWindowBounds = 1547 [tabStripView convertRect:tabStripViewWindowBounds 1548 toView:windowChromeView]; 1549 NSPoint phase = NSMakePoint(NSMinX(tabStripViewWindowBounds) 1550 + kPatternHorizontalOffset, 1551 NSMinY(tabStripViewWindowBounds) 1552 + [TabStripController defaultTabHeight]); 1553 return phase; 1554 } 1555 1556 - (NSPoint)bookmarkBubblePoint { 1557 return [toolbarController_ bookmarkBubblePoint]; 1558 } 1559 1560 // Show the bookmark bubble (e.g. user just clicked on the STAR). 1561 - (void)showBookmarkBubbleForURL:(const GURL&)url 1562 alreadyBookmarked:(BOOL)alreadyMarked { 1563 if (!bookmarkBubbleController_) { 1564 BookmarkModel* model = browser_->profile()->GetBookmarkModel(); 1565 const BookmarkNode* node = model->GetMostRecentlyAddedNodeForURL(url); 1566 bookmarkBubbleController_ = 1567 [[BookmarkBubbleController alloc] initWithParentWindow:[self window] 1568 model:model 1569 node:node 1570 alreadyBookmarked:alreadyMarked]; 1571 [bookmarkBubbleController_ showWindow:self]; 1572 NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; 1573 [center addObserver:self 1574 selector:@selector(bubbleWindowWillClose:) 1575 name:NSWindowWillCloseNotification 1576 object:[bookmarkBubbleController_ window]]; 1577 } 1578 } 1579 1580 // Nil out the weak bookmark bubble controller reference. 1581 - (void)bubbleWindowWillClose:(NSNotification*)notification { 1582 DCHECK([notification object] == [bookmarkBubbleController_ window]); 1583 NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; 1584 [center removeObserver:self 1585 name:NSWindowWillCloseNotification 1586 object:[bookmarkBubbleController_ window]]; 1587 bookmarkBubbleController_ = nil; 1588 } 1589 1590 // Handle the editBookmarkNode: action sent from bookmark bubble controllers. 1591 - (void)editBookmarkNode:(id)sender { 1592 BOOL responds = [sender respondsToSelector:@selector(node)]; 1593 DCHECK(responds); 1594 if (responds) { 1595 const BookmarkNode* node = [sender node]; 1596 if (node) { 1597 // A BookmarkEditorController is a sheet that owns itself, and 1598 // deallocates itself when closed. 1599 [[[BookmarkEditorController alloc] 1600 initWithParentWindow:[self window] 1601 profile:browser_->profile() 1602 parent:node->parent() 1603 node:node 1604 configuration:BookmarkEditor::SHOW_TREE] 1605 runAsModalSheet]; 1606 } 1607 } 1608 } 1609 1610 // If the browser is in incognito mode, install the image view to decorate 1611 // the window at the upper right. Use the same base y coordinate as the 1612 // tab strip. 1613 - (void)installIncognitoBadge { 1614 // Only install if this browser window is OTR and has a tab strip. 1615 if (!browser_->profile()->IsOffTheRecord() || ![self hasTabStrip]) 1616 return; 1617 1618 // Install the image into the badge view and size the view appropriately. 1619 // Hide it for now; positioning and showing will be done by the layout code. 1620 NSImage* image = app::mac::GetCachedImageWithName(@"otr_icon.pdf"); 1621 incognitoBadge_.reset([[IncognitoImageView alloc] init]); 1622 [incognitoBadge_ setImage:image]; 1623 [incognitoBadge_ setFrameSize:[image size]]; 1624 [incognitoBadge_ setAutoresizingMask:NSViewMinXMargin | NSViewMinYMargin]; 1625 [incognitoBadge_ setHidden:YES]; 1626 1627 // Install the view. 1628 [[[[self window] contentView] superview] addSubview:incognitoBadge_]; 1629 } 1630 1631 // Documented in 10.6+, but present starting in 10.5. Called when we get a 1632 // three-finger swipe. 1633 - (void)swipeWithEvent:(NSEvent*)event { 1634 // Map forwards and backwards to history; left is positive, right is negative. 1635 unsigned int command = 0; 1636 if ([event deltaX] > 0.5) { 1637 command = IDC_BACK; 1638 } else if ([event deltaX] < -0.5) { 1639 command = IDC_FORWARD; 1640 } else if ([event deltaY] > 0.5) { 1641 // TODO(pinkerton): figure out page-up, http://crbug.com/16305 1642 } else if ([event deltaY] < -0.5) { 1643 // TODO(pinkerton): figure out page-down, http://crbug.com/16305 1644 browser_->ExecuteCommand(IDC_TABPOSE); 1645 } 1646 1647 // Ensure the command is valid first (ExecuteCommand() won't do that) and 1648 // then make it so. 1649 if (browser_->command_updater()->IsCommandEnabled(command)) 1650 browser_->ExecuteCommandWithDisposition(command, 1651 event_utils::WindowOpenDispositionFromNSEvent(event)); 1652 } 1653 1654 // Documented in 10.6+, but present starting in 10.5. Called repeatedly during 1655 // a pinch gesture, with incremental change values. 1656 - (void)magnifyWithEvent:(NSEvent*)event { 1657 // The deltaZ difference necessary to trigger a zoom action. Derived from 1658 // experimentation to find a value that feels reasonable. 1659 const float kZoomStepValue = 300; 1660 1661 // Find the (absolute) thresholds on either side of the current zoom factor, 1662 // then convert those to actual numbers to trigger a zoom in or out. 1663 // This logic deliberately makes the range around the starting zoom value for 1664 // the gesture twice as large as the other ranges (i.e., the notches are at 1665 // ..., -3*step, -2*step, -step, step, 2*step, 3*step, ... but not at 0) 1666 // so that it's easier to get back to your starting point than it is to 1667 // overshoot. 1668 float nextStep = (abs(currentZoomStepDelta_) + 1) * kZoomStepValue; 1669 float backStep = abs(currentZoomStepDelta_) * kZoomStepValue; 1670 float zoomInThreshold = (currentZoomStepDelta_ >= 0) ? nextStep : -backStep; 1671 float zoomOutThreshold = (currentZoomStepDelta_ <= 0) ? -nextStep : backStep; 1672 1673 unsigned int command = 0; 1674 totalMagnifyGestureAmount_ += [event deltaZ]; 1675 if (totalMagnifyGestureAmount_ > zoomInThreshold) { 1676 command = IDC_ZOOM_PLUS; 1677 } else if (totalMagnifyGestureAmount_ < zoomOutThreshold) { 1678 command = IDC_ZOOM_MINUS; 1679 } 1680 1681 if (command && browser_->command_updater()->IsCommandEnabled(command)) { 1682 currentZoomStepDelta_ += (command == IDC_ZOOM_PLUS) ? 1 : -1; 1683 browser_->ExecuteCommandWithDisposition(command, 1684 event_utils::WindowOpenDispositionFromNSEvent(event)); 1685 } 1686 } 1687 1688 // Documented in 10.6+, but present starting in 10.5. Called at the beginning 1689 // of a gesture. 1690 - (void)beginGestureWithEvent:(NSEvent*)event { 1691 totalMagnifyGestureAmount_ = 0; 1692 currentZoomStepDelta_ = 0; 1693 } 1694 1695 // Delegate method called when window is resized. 1696 - (void)windowDidResize:(NSNotification*)notification { 1697 // Resize (and possibly move) the status bubble. Note that we may get called 1698 // when the status bubble does not exist. 1699 if (statusBubble_) { 1700 statusBubble_->UpdateSizeAndPosition(); 1701 } 1702 1703 // Let the selected RenderWidgetHostView know, so that it can tell plugins. 1704 if (TabContents* contents = browser_->GetSelectedTabContents()) { 1705 if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView()) 1706 rwhv->WindowFrameChanged(); 1707 } 1708 1709 // The FindBar needs to know its own position to properly detect overlaps 1710 // with find results. The position changes whenever the window is resized, 1711 // and |layoutSubviews| computes the FindBar's position. 1712 // TODO: calling |layoutSubviews| here is a waste, find a better way to 1713 // do this. 1714 if ([findBarCocoaController_ isFindBarVisible]) 1715 [self layoutSubviews]; 1716 } 1717 1718 // Handle the openLearnMoreAboutCrashLink: action from SadTabController when 1719 // "Learn more" link in "Aw snap" page (i.e. crash page or sad tab) is 1720 // clicked. Decoupling the action from its target makes unitestting possible. 1721 - (void)openLearnMoreAboutCrashLink:(id)sender { 1722 if ([sender isKindOfClass:[SadTabController class]]) { 1723 SadTabController* sad_tab = static_cast<SadTabController*>(sender); 1724 TabContents* tab_contents = [sad_tab tabContents]; 1725 if (tab_contents) { 1726 GURL helpUrl = 1727 google_util::AppendGoogleLocaleParam(GURL(chrome::kCrashReasonURL)); 1728 tab_contents->OpenURL(helpUrl, GURL(), CURRENT_TAB, PageTransition::LINK); 1729 } 1730 } 1731 } 1732 1733 // Delegate method called when window did move. (See below for why we don't use 1734 // |-windowWillMove:|, which is called less frequently than |-windowDidMove| 1735 // instead.) 1736 - (void)windowDidMove:(NSNotification*)notification { 1737 NSWindow* window = [self window]; 1738 NSRect windowFrame = [window frame]; 1739 NSRect workarea = [[window screen] visibleFrame]; 1740 1741 // We reset the window growth state whenever the window is moved out of the 1742 // work area or away (up or down) from the bottom or top of the work area. 1743 // Unfortunately, Cocoa sends |-windowWillMove:| too frequently (including 1744 // when clicking on the title bar to activate), and of course 1745 // |-windowWillMove| is called too early for us to apply our heuristic. (The 1746 // heuristic we use for detecting window movement is that if |windowTopGrowth_ 1747 // > 0|, then we should be at the bottom of the work area -- if we're not, 1748 // we've moved. Similarly for the other side.) 1749 if (!NSContainsRect(workarea, windowFrame) || 1750 (windowTopGrowth_ > 0 && NSMinY(windowFrame) != NSMinY(workarea)) || 1751 (windowBottomGrowth_ > 0 && NSMaxY(windowFrame) != NSMaxY(workarea))) 1752 [self resetWindowGrowthState]; 1753 1754 // Let the selected RenderWidgetHostView know, so that it can tell plugins. 1755 if (TabContents* contents = browser_->GetSelectedTabContents()) { 1756 if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView()) 1757 rwhv->WindowFrameChanged(); 1758 } 1759 } 1760 1761 // Delegate method called when window will be resized; not called for 1762 // |-setFrame:display:|. 1763 - (NSSize)windowWillResize:(NSWindow*)sender toSize:(NSSize)frameSize { 1764 [self resetWindowGrowthState]; 1765 return frameSize; 1766 } 1767 1768 // Delegate method: see |NSWindowDelegate| protocol. 1769 - (id)windowWillReturnFieldEditor:(NSWindow*)sender toObject:(id)obj { 1770 // Ask the toolbar controller if it wants to return a custom field editor 1771 // for the specific object. 1772 return [toolbarController_ customFieldEditorForObject:obj]; 1773 } 1774 1775 // (Needed for |BookmarkBarControllerDelegate| protocol.) 1776 - (void)bookmarkBar:(BookmarkBarController*)controller 1777 didChangeFromState:(bookmarks::VisualState)oldState 1778 toState:(bookmarks::VisualState)newState { 1779 [toolbarController_ 1780 setDividerOpacity:[bookmarkBarController_ toolbarDividerOpacity]]; 1781 [self adjustToolbarAndBookmarkBarForCompression: 1782 [controller getDesiredToolbarHeightCompression]]; 1783 } 1784 1785 // (Needed for |BookmarkBarControllerDelegate| protocol.) 1786 - (void)bookmarkBar:(BookmarkBarController*)controller 1787 willAnimateFromState:(bookmarks::VisualState)oldState 1788 toState:(bookmarks::VisualState)newState { 1789 [toolbarController_ 1790 setDividerOpacity:[bookmarkBarController_ toolbarDividerOpacity]]; 1791 [self adjustToolbarAndBookmarkBarForCompression: 1792 [controller getDesiredToolbarHeightCompression]]; 1793 } 1794 1795 // (Private/TestingAPI) 1796 - (void)resetWindowGrowthState { 1797 windowTopGrowth_ = 0; 1798 windowBottomGrowth_ = 0; 1799 isShrinkingFromZoomed_ = NO; 1800 } 1801 1802 - (NSSize)overflowFrom:(NSRect)source 1803 to:(NSRect)target { 1804 // If |source|'s boundary is outside of |target|'s, set its distance 1805 // to |x|. Note that |source| can overflow to both side, but we 1806 // have nothing to do for such case. 1807 CGFloat x = 0; 1808 if (NSMaxX(target) < NSMaxX(source)) // |source| overflows to right 1809 x = NSMaxX(source) - NSMaxX(target); 1810 else if (NSMinX(source) < NSMinX(target)) // |source| overflows to left 1811 x = NSMinX(source) - NSMinX(target); 1812 1813 // Same as |x| above. 1814 CGFloat y = 0; 1815 if (NSMaxY(target) < NSMaxY(source)) 1816 y = NSMaxY(source) - NSMaxY(target); 1817 else if (NSMinY(source) < NSMinY(target)) 1818 y = NSMinY(source) - NSMinY(target); 1819 1820 return NSMakeSize(x, y); 1821 } 1822 1823 // Override to swap in the correct tab strip controller based on the new 1824 // tab strip mode. 1825 - (void)toggleTabStripDisplayMode { 1826 [super toggleTabStripDisplayMode]; 1827 [self createTabStripController]; 1828 } 1829 1830 - (BOOL)useVerticalTabs { 1831 return browser_->tabstrip_model()->delegate()->UseVerticalTabs(); 1832 } 1833 1834 - (void)showInstant:(TabContents*)previewContents { 1835 [previewableContentsController_ showPreview:previewContents]; 1836 [self updateBookmarkBarVisibilityWithAnimation:NO]; 1837 } 1838 1839 - (void)hideInstant { 1840 // TODO(rohitrao): Revisit whether or not this method should be called when 1841 // instant isn't showing. 1842 if (![previewableContentsController_ isShowingPreview]) 1843 return; 1844 1845 [previewableContentsController_ hidePreview]; 1846 [self updateBookmarkBarVisibilityWithAnimation:NO]; 1847 } 1848 1849 - (void)commitInstant { 1850 InstantController::CommitIfCurrent(browser_->instant()); 1851 } 1852 1853 1854 - (NSRect)instantFrame { 1855 // The view's bounds are in its own coordinate system. Convert that to the 1856 // window base coordinate system, then translate it into the screen's 1857 // coordinate system. 1858 NSView* view = [previewableContentsController_ view]; 1859 if (!view) 1860 return NSZeroRect; 1861 1862 NSRect frame = [view convertRect:[view bounds] toView:nil]; 1863 NSPoint originInScreenCoords = 1864 [[view window] convertBaseToScreen:frame.origin]; 1865 frame.origin = originInScreenCoords; 1866 1867 // Account for the bookmark bar height if it is currently in the detached 1868 // state on the new tab page. 1869 if ([bookmarkBarController_ isInState:(bookmarks::kDetachedState)]) 1870 frame.size.height += [[bookmarkBarController_ view] bounds].size.height; 1871 1872 return frame; 1873 } 1874 1875 - (void)sheetDidEnd:(NSWindow*)sheet 1876 returnCode:(NSInteger)code 1877 context:(void*)context { 1878 [sheet orderOut:self]; 1879 } 1880 1881 @end // @implementation BrowserWindowController 1882 1883 1884 @implementation BrowserWindowController(Fullscreen) 1885 1886 - (IBAction)enterFullscreen:(id)sender { 1887 browser_->ExecuteCommand(IDC_FULLSCREEN); 1888 } 1889 1890 - (void)setFullscreen:(BOOL)fullscreen { 1891 // The logic in this function is a bit complicated and very carefully 1892 // arranged. See the below comments for more details. 1893 1894 if (fullscreen == [self isFullscreen]) 1895 return; 1896 1897 if (![self supportsFullscreen]) 1898 return; 1899 1900 // Fade to black. 1901 const CGDisplayReservationInterval kFadeDurationSeconds = 0.6; 1902 Boolean didFadeOut = NO; 1903 CGDisplayFadeReservationToken token; 1904 if (CGAcquireDisplayFadeReservation(kFadeDurationSeconds, &token) 1905 == kCGErrorSuccess) { 1906 didFadeOut = YES; 1907 CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendNormal, 1908 kCGDisplayBlendSolidColor, 0.0, 0.0, 0.0, /*synchronous=*/true); 1909 } 1910 1911 // Close the bookmark bubble, if it's open. We use |-ok:| instead of 1912 // |-cancel:| or |-close| because that matches the behavior when the bubble 1913 // loses key status. 1914 [bookmarkBubbleController_ ok:self]; 1915 1916 // Save the current first responder so we can restore after views are moved. 1917 NSWindow* window = [self window]; 1918 scoped_nsobject<FocusTracker> focusTracker( 1919 [[FocusTracker alloc] initWithWindow:window]); 1920 BOOL showDropdown = [self floatingBarHasFocus]; 1921 1922 // While we move views (and focus) around, disable any bar visibility changes. 1923 [self disableBarVisibilityUpdates]; 1924 1925 // If we're entering fullscreen, create the fullscreen controller. If we're 1926 // exiting fullscreen, kill the controller. 1927 if (fullscreen) { 1928 fullscreenController_.reset([[FullscreenController alloc] 1929 initWithBrowserController:self]); 1930 } else { 1931 [fullscreenController_ exitFullscreen]; 1932 fullscreenController_.reset(); 1933 } 1934 1935 // Destroy the tab strip's sheet controller. We will recreate it in the new 1936 // window when needed. 1937 [tabStripController_ destroySheetController]; 1938 1939 // Retain the tab strip view while we remove it from its superview. 1940 scoped_nsobject<NSView> tabStripView; 1941 if ([self hasTabStrip] && ![self useVerticalTabs]) { 1942 tabStripView.reset([[self tabStripView] retain]); 1943 [tabStripView removeFromSuperview]; 1944 } 1945 1946 // Ditto for the content view. 1947 scoped_nsobject<NSView> contentView([[window contentView] retain]); 1948 // Disable autoresizing of subviews while we move views around. This prevents 1949 // spurious renderer resizes. 1950 [contentView setAutoresizesSubviews:NO]; 1951 [contentView removeFromSuperview]; 1952 1953 NSWindow* destWindow = nil; 1954 if (fullscreen) { 1955 DCHECK(!savedRegularWindow_); 1956 savedRegularWindow_ = [window retain]; 1957 destWindow = [self createFullscreenWindow]; 1958 } else { 1959 DCHECK(savedRegularWindow_); 1960 destWindow = [savedRegularWindow_ autorelease]; 1961 savedRegularWindow_ = nil; 1962 } 1963 DCHECK(destWindow); 1964 1965 // Have to do this here, otherwise later calls can crash because the window 1966 // has no delegate. 1967 [window setDelegate:nil]; 1968 [destWindow setDelegate:self]; 1969 1970 // With this call, valgrind complains that a "Conditional jump or move depends 1971 // on uninitialised value(s)". The error happens in -[NSThemeFrame 1972 // drawOverlayRect:]. I'm pretty convinced this is an Apple bug, but there is 1973 // no visual impact. I have been unable to tickle it away with other window 1974 // or view manipulation Cocoa calls. Stack added to suppressions_mac.txt. 1975 [contentView setAutoresizesSubviews:YES]; 1976 [destWindow setContentView:contentView]; 1977 1978 // Move the incognito badge if present. 1979 if (incognitoBadge_.get()) { 1980 [incognitoBadge_ removeFromSuperview]; 1981 [incognitoBadge_ setHidden:YES]; // Will be shown in layout. 1982 [[[destWindow contentView] superview] addSubview:incognitoBadge_]; 1983 } 1984 1985 // Add the tab strip after setting the content view and moving the incognito 1986 // badge (if any), so that the tab strip will be on top (in the z-order). 1987 if ([self hasTabStrip] && ![self useVerticalTabs]) 1988 [[[destWindow contentView] superview] addSubview:tabStripView]; 1989 1990 [window setWindowController:nil]; 1991 [self setWindow:destWindow]; 1992 [destWindow setWindowController:self]; 1993 [self adjustUIForFullscreen:fullscreen]; 1994 1995 // Adjust the infobar container. In fullscreen, it needs to be below all 1996 // top chrome elements so it only sits atop the web contents. When in normal 1997 // mode, it needs to draw over the bookmark bar and part of the toolbar. 1998 [[infoBarContainerController_ view] removeFromSuperview]; 1999 NSView* infoBarDest = [[destWindow contentView] superview]; 2000 [infoBarDest addSubview:[infoBarContainerController_ view] 2001 positioned:fullscreen ? NSWindowBelow : NSWindowAbove 2002 relativeTo:fullscreen ? floatingBarBackingView_ 2003 : [bookmarkBarController_ view]]; 2004 2005 // When entering fullscreen mode, the controller forces a layout for us. When 2006 // exiting, we need to call layoutSubviews manually. 2007 if (fullscreen) { 2008 [fullscreenController_ enterFullscreenForContentView:contentView 2009 showDropdown:showDropdown]; 2010 } else { 2011 [self layoutSubviews]; 2012 } 2013 2014 // Move the status bubble over, if we have one. 2015 if (statusBubble_) 2016 statusBubble_->SwitchParentWindow(destWindow); 2017 2018 // Move the title over. 2019 [destWindow setTitle:[window title]]; 2020 2021 // The window needs to be onscreen before we can set its first responder. 2022 // Ordering the window to the front can change the active Space (either to 2023 // the window's old Space or to the application's assigned Space). To prevent 2024 // this by temporarily change the collectionBehavior. 2025 NSWindowCollectionBehavior behavior = [window collectionBehavior]; 2026 [destWindow setCollectionBehavior: 2027 NSWindowCollectionBehaviorMoveToActiveSpace]; 2028 [destWindow makeKeyAndOrderFront:self]; 2029 [destWindow setCollectionBehavior:behavior]; 2030 2031 [focusTracker restoreFocusInWindow:destWindow]; 2032 [window orderOut:self]; 2033 2034 // We're done moving focus, so re-enable bar visibility changes. 2035 [self enableBarVisibilityUpdates]; 2036 2037 // This needs to be done when leaving full-screen mode to ensure that the 2038 // button's action is set properly. 2039 [self setUpOSFullScreenButton]; 2040 2041 // Fade back in. 2042 if (didFadeOut) { 2043 CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendSolidColor, 2044 kCGDisplayBlendNormal, 0.0, 0.0, 0.0, /*synchronous=*/false); 2045 CGReleaseDisplayFadeReservation(token); 2046 } 2047 } 2048 2049 - (BOOL)isFullscreen { 2050 return fullscreenController_.get() && [fullscreenController_ isFullscreen]; 2051 } 2052 2053 - (void)resizeFullscreenWindow { 2054 DCHECK([self isFullscreen]); 2055 if (![self isFullscreen]) 2056 return; 2057 2058 NSWindow* window = [self window]; 2059 [window setFrame:[[window screen] frame] display:YES]; 2060 [self layoutSubviews]; 2061 } 2062 2063 - (CGFloat)floatingBarShownFraction { 2064 return floatingBarShownFraction_; 2065 } 2066 2067 - (void)setFloatingBarShownFraction:(CGFloat)fraction { 2068 floatingBarShownFraction_ = fraction; 2069 [self layoutSubviews]; 2070 } 2071 2072 - (BOOL)isBarVisibilityLockedForOwner:(id)owner { 2073 DCHECK(owner); 2074 DCHECK(barVisibilityLocks_); 2075 return [barVisibilityLocks_ containsObject:owner]; 2076 } 2077 2078 - (void)lockBarVisibilityForOwner:(id)owner 2079 withAnimation:(BOOL)animate 2080 delay:(BOOL)delay { 2081 if (![self isBarVisibilityLockedForOwner:owner]) { 2082 [barVisibilityLocks_ addObject:owner]; 2083 2084 // If enabled, show the overlay if necessary (and if in fullscreen mode). 2085 if (barVisibilityUpdatesEnabled_) { 2086 [fullscreenController_ ensureOverlayShownWithAnimation:animate 2087 delay:delay]; 2088 } 2089 } 2090 } 2091 2092 - (void)releaseBarVisibilityForOwner:(id)owner 2093 withAnimation:(BOOL)animate 2094 delay:(BOOL)delay { 2095 if ([self isBarVisibilityLockedForOwner:owner]) { 2096 [barVisibilityLocks_ removeObject:owner]; 2097 2098 // If enabled, hide the overlay if necessary (and if in fullscreen mode). 2099 if (barVisibilityUpdatesEnabled_ && 2100 ![barVisibilityLocks_ count]) { 2101 [fullscreenController_ ensureOverlayHiddenWithAnimation:animate 2102 delay:delay]; 2103 } 2104 } 2105 } 2106 2107 - (BOOL)floatingBarHasFocus { 2108 NSResponder* focused = [[self window] firstResponder]; 2109 return [focused isKindOfClass:[AutocompleteTextFieldEditor class]]; 2110 } 2111 2112 - (void)tabposeWillClose:(NSNotification*)notif { 2113 // Re-show the container after Tabpose closes. 2114 [[infoBarContainerController_ view] setHidden:NO]; 2115 } 2116 2117 - (void)openTabpose { 2118 NSUInteger modifierFlags = [[NSApp currentEvent] modifierFlags]; 2119 BOOL slomo = (modifierFlags & NSShiftKeyMask) != 0; 2120 2121 // Cover info bars, inspector window, and detached bookmark bar on NTP. 2122 // Do not cover download shelf. 2123 NSRect activeArea = [[self tabContentArea] frame]; 2124 // Take out the anti-spoof height so that Tabpose doesn't draw on top of the 2125 // browser chrome. 2126 activeArea.size.height += 2127 NSHeight([[infoBarContainerController_ view] frame]) - 2128 [infoBarContainerController_ antiSpoofHeight]; 2129 if ([self isBookmarkBarVisible] && [self placeBookmarkBarBelowInfoBar]) { 2130 NSView* bookmarkBarView = [bookmarkBarController_ view]; 2131 activeArea.size.height += NSHeight([bookmarkBarView frame]); 2132 } 2133 2134 // Hide the infobar container so that the anti-spoof bulge doesn't show when 2135 // Tabpose is open. 2136 [[infoBarContainerController_ view] setHidden:YES]; 2137 2138 TabposeWindow* window = 2139 [TabposeWindow openTabposeFor:[self window] 2140 rect:activeArea 2141 slomo:slomo 2142 tabStripModel:browser_->tabstrip_model()]; 2143 2144 // When the Tabpose window closes, the infobar container needs to be made 2145 // visible again. 2146 NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; 2147 [center addObserver:self 2148 selector:@selector(tabposeWillClose:) 2149 name:NSWindowWillCloseNotification 2150 object:window]; 2151 } 2152 2153 @end // @implementation BrowserWindowController(Fullscreen) 2154 2155 2156 @implementation BrowserWindowController(WindowType) 2157 2158 - (BOOL)supportsWindowFeature:(int)feature { 2159 return browser_->SupportsWindowFeature( 2160 static_cast<Browser::WindowFeature>(feature)); 2161 } 2162 2163 - (BOOL)hasTitleBar { 2164 return [self supportsWindowFeature:Browser::FEATURE_TITLEBAR]; 2165 } 2166 2167 - (BOOL)hasToolbar { 2168 return [self supportsWindowFeature:Browser::FEATURE_TOOLBAR]; 2169 } 2170 2171 - (BOOL)hasLocationBar { 2172 return [self supportsWindowFeature:Browser::FEATURE_LOCATIONBAR]; 2173 } 2174 2175 - (BOOL)supportsBookmarkBar { 2176 return [self supportsWindowFeature:Browser::FEATURE_BOOKMARKBAR]; 2177 } 2178 2179 - (BOOL)isNormalWindow { 2180 return browser_->type() == Browser::TYPE_NORMAL; 2181 } 2182 2183 @end // @implementation BrowserWindowController(WindowType) 2184