| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2010 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/tab_strip_controller.h" | |
| 6 | |
| 7 #import <QuartzCore/QuartzCore.h> | |
| 8 | |
| 9 #include <limits> | |
| 10 #include <string> | |
| 11 | |
| 12 #include "app/l10n_util.h" | |
| 13 #include "app/mac/nsimage_cache.h" | |
| 14 #include "app/resource_bundle.h" | |
| 15 #include "base/mac/mac_util.h" | |
| 16 #include "base/sys_string_conversions.h" | |
| 17 #include "chrome/app/chrome_command_ids.h" | |
| 18 #include "chrome/browser/autocomplete/autocomplete.h" | |
| 19 #include "chrome/browser/autocomplete/autocomplete_classifier.h" | |
| 20 #include "chrome/browser/autocomplete/autocomplete_match.h" | |
| 21 #include "chrome/browser/metrics/user_metrics.h" | |
| 22 #include "chrome/browser/profiles/profile.h" | |
| 23 #include "chrome/browser/debugger/devtools_window.h" | |
| 24 #include "chrome/browser/net/url_fixer_upper.h" | |
| 25 #include "chrome/browser/sidebar/sidebar_container.h" | |
| 26 #include "chrome/browser/sidebar/sidebar_manager.h" | |
| 27 #include "chrome/browser/tab_contents/navigation_controller.h" | |
| 28 #include "chrome/browser/tab_contents/navigation_entry.h" | |
| 29 #include "chrome/browser/tab_contents/tab_contents.h" | |
| 30 #include "chrome/browser/tab_contents/tab_contents_view.h" | |
| 31 #include "chrome/browser/tabs/tab_strip_model.h" | |
| 32 #include "chrome/browser/ui/browser.h" | |
| 33 #include "chrome/browser/ui/browser_navigator.h" | |
| 34 #import "chrome/browser/ui/cocoa/browser_window_controller.h" | |
| 35 #import "chrome/browser/ui/cocoa/constrained_window_mac.h" | |
| 36 #import "chrome/browser/ui/cocoa/new_tab_button.h" | |
| 37 #import "chrome/browser/ui/cocoa/tab_strip_view.h" | |
| 38 #import "chrome/browser/ui/cocoa/tab_contents_controller.h" | |
| 39 #import "chrome/browser/ui/cocoa/tab_controller.h" | |
| 40 #import "chrome/browser/ui/cocoa/tab_strip_model_observer_bridge.h" | |
| 41 #import "chrome/browser/ui/cocoa/tab_view.h" | |
| 42 #import "chrome/browser/ui/cocoa/throbber_view.h" | |
| 43 #include "chrome/browser/ui/find_bar/find_bar.h" | |
| 44 #include "chrome/browser/ui/find_bar/find_bar_controller.h" | |
| 45 #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h" | |
| 46 #include "grit/app_resources.h" | |
| 47 #include "grit/generated_resources.h" | |
| 48 #include "grit/theme_resources.h" | |
| 49 #include "skia/ext/skia_utils_mac.h" | |
| 50 #import "third_party/GTM/AppKit/GTMNSAnimation+Duration.h" | |
| 51 | |
| 52 NSString* const kTabStripNumberOfTabsChanged = @"kTabStripNumberOfTabsChanged"; | |
| 53 | |
| 54 namespace { | |
| 55 | |
| 56 // The images names used for different states of the new tab button. | |
| 57 NSString* const kNewTabHoverImage = @"newtab_h.pdf"; | |
| 58 NSString* const kNewTabImage = @"newtab.pdf"; | |
| 59 NSString* const kNewTabPressedImage = @"newtab_p.pdf"; | |
| 60 | |
| 61 // A value to indicate tab layout should use the full available width of the | |
| 62 // view. | |
| 63 const CGFloat kUseFullAvailableWidth = -1.0; | |
| 64 | |
| 65 // The amount by which tabs overlap. | |
| 66 const CGFloat kTabOverlap = 20.0; | |
| 67 | |
| 68 // The width and height for a tab's icon. | |
| 69 const CGFloat kIconWidthAndHeight = 16.0; | |
| 70 | |
| 71 // The amount by which the new tab button is offset (from the tabs). | |
| 72 const CGFloat kNewTabButtonOffset = 8.0; | |
| 73 | |
| 74 // The amount by which to shrink the tab strip (on the right) when the | |
| 75 // incognito badge is present. | |
| 76 const CGFloat kIncognitoBadgeTabStripShrink = 18; | |
| 77 | |
| 78 // Time (in seconds) in which tabs animate to their final position. | |
| 79 const NSTimeInterval kAnimationDuration = 0.125; | |
| 80 | |
| 81 // Helper class for doing NSAnimationContext calls that takes a bool to disable | |
| 82 // all the work. Useful for code that wants to conditionally animate. | |
| 83 class ScopedNSAnimationContextGroup { | |
| 84 public: | |
| 85 explicit ScopedNSAnimationContextGroup(bool animate) | |
| 86 : animate_(animate) { | |
| 87 if (animate_) { | |
| 88 [NSAnimationContext beginGrouping]; | |
| 89 } | |
| 90 } | |
| 91 | |
| 92 ~ScopedNSAnimationContextGroup() { | |
| 93 if (animate_) { | |
| 94 [NSAnimationContext endGrouping]; | |
| 95 } | |
| 96 } | |
| 97 | |
| 98 void SetCurrentContextDuration(NSTimeInterval duration) { | |
| 99 if (animate_) { | |
| 100 [[NSAnimationContext currentContext] gtm_setDuration:duration | |
| 101 eventMask:NSLeftMouseUpMask]; | |
| 102 } | |
| 103 } | |
| 104 | |
| 105 void SetCurrentContextShortestDuration() { | |
| 106 if (animate_) { | |
| 107 // The minimum representable time interval. This used to stop an | |
| 108 // in-progress animation as quickly as possible. | |
| 109 const NSTimeInterval kMinimumTimeInterval = | |
| 110 std::numeric_limits<NSTimeInterval>::min(); | |
| 111 // Directly set the duration to be short, avoiding the Steve slowmotion | |
| 112 // ettect the gtm_setDuration: provides. | |
| 113 [[NSAnimationContext currentContext] setDuration:kMinimumTimeInterval]; | |
| 114 } | |
| 115 } | |
| 116 | |
| 117 private: | |
| 118 bool animate_; | |
| 119 DISALLOW_COPY_AND_ASSIGN(ScopedNSAnimationContextGroup); | |
| 120 }; | |
| 121 | |
| 122 } // namespace | |
| 123 | |
| 124 @interface TabStripController (Private) | |
| 125 - (void)installTrackingArea; | |
| 126 - (void)addSubviewToPermanentList:(NSView*)aView; | |
| 127 - (void)regenerateSubviewList; | |
| 128 - (NSInteger)indexForContentsView:(NSView*)view; | |
| 129 - (void)updateFavIconForContents:(TabContents*)contents | |
| 130 atIndex:(NSInteger)modelIndex; | |
| 131 - (void)layoutTabsWithAnimation:(BOOL)animate | |
| 132 regenerateSubviews:(BOOL)doUpdate; | |
| 133 - (void)animationDidStopForController:(TabController*)controller | |
| 134 finished:(BOOL)finished; | |
| 135 - (NSInteger)indexFromModelIndex:(NSInteger)index; | |
| 136 - (NSInteger)numberOfOpenTabs; | |
| 137 - (NSInteger)numberOfOpenMiniTabs; | |
| 138 - (NSInteger)numberOfOpenNonMiniTabs; | |
| 139 - (void)mouseMoved:(NSEvent*)event; | |
| 140 - (void)setTabTrackingAreasEnabled:(BOOL)enabled; | |
| 141 - (void)droppingURLsAt:(NSPoint)point | |
| 142 givesIndex:(NSInteger*)index | |
| 143 disposition:(WindowOpenDisposition*)disposition; | |
| 144 - (void)setNewTabButtonHoverState:(BOOL)showHover; | |
| 145 @end | |
| 146 | |
| 147 // A simple view class that prevents the Window Server from dragging the area | |
| 148 // behind tabs. Sometimes core animation confuses it. Unfortunately, it can also | |
| 149 // falsely pick up clicks during rapid tab closure, so we have to account for | |
| 150 // that. | |
| 151 @interface TabStripControllerDragBlockingView : NSView { | |
| 152 TabStripController* controller_; // weak; owns us | |
| 153 } | |
| 154 | |
| 155 - (id)initWithFrame:(NSRect)frameRect | |
| 156 controller:(TabStripController*)controller; | |
| 157 @end | |
| 158 | |
| 159 @implementation TabStripControllerDragBlockingView | |
| 160 - (BOOL)mouseDownCanMoveWindow {return NO;} | |
| 161 - (void)drawRect:(NSRect)rect {} | |
| 162 | |
| 163 - (id)initWithFrame:(NSRect)frameRect | |
| 164 controller:(TabStripController*)controller { | |
| 165 if ((self = [super initWithFrame:frameRect])) | |
| 166 controller_ = controller; | |
| 167 return self; | |
| 168 } | |
| 169 | |
| 170 // In "rapid tab closure" mode (i.e., the user is clicking close tab buttons in | |
| 171 // rapid succession), the animations confuse Cocoa's hit testing (which appears | |
| 172 // to use cached results, among other tricks), so this view can somehow end up | |
| 173 // getting a mouse down event. Thus we do an explicit hit test during rapid tab | |
| 174 // closure, and if we find that we got a mouse down we shouldn't have, we send | |
| 175 // it off to the appropriate view. | |
| 176 - (void)mouseDown:(NSEvent*)event { | |
| 177 if ([controller_ inRapidClosureMode]) { | |
| 178 NSView* superview = [self superview]; | |
| 179 NSPoint hitLocation = | |
| 180 [[superview superview] convertPoint:[event locationInWindow] | |
| 181 fromView:nil]; | |
| 182 NSView* hitView = [superview hitTest:hitLocation]; | |
| 183 if (hitView != self) { | |
| 184 [hitView mouseDown:event]; | |
| 185 return; | |
| 186 } | |
| 187 } | |
| 188 [super mouseDown:event]; | |
| 189 } | |
| 190 @end | |
| 191 | |
| 192 #pragma mark - | |
| 193 | |
| 194 // A delegate, owned by the CAAnimation system, that is alerted when the | |
| 195 // animation to close a tab is completed. Calls back to the given tab strip | |
| 196 // to let it know that |controller_| is ready to be removed from the model. | |
| 197 // Since we only maintain weak references, the tab strip must call -invalidate: | |
| 198 // to prevent the use of dangling pointers. | |
| 199 @interface TabCloseAnimationDelegate : NSObject { | |
| 200 @private | |
| 201 TabStripController* strip_; // weak; owns us indirectly | |
| 202 TabController* controller_; // weak | |
| 203 } | |
| 204 | |
| 205 // Will tell |strip| when the animation for |controller|'s view has completed. | |
| 206 // These should not be nil, and will not be retained. | |
| 207 - (id)initWithTabStrip:(TabStripController*)strip | |
| 208 tabController:(TabController*)controller; | |
| 209 | |
| 210 // Invalidates this object so that no further calls will be made to | |
| 211 // |strip_|. This should be called when |strip_| is released, to | |
| 212 // prevent attempts to call into the released object. | |
| 213 - (void)invalidate; | |
| 214 | |
| 215 // CAAnimation delegate method | |
| 216 - (void)animationDidStop:(CAAnimation*)animation finished:(BOOL)finished; | |
| 217 | |
| 218 @end | |
| 219 | |
| 220 @implementation TabCloseAnimationDelegate | |
| 221 | |
| 222 - (id)initWithTabStrip:(TabStripController*)strip | |
| 223 tabController:(TabController*)controller { | |
| 224 if ((self == [super init])) { | |
| 225 DCHECK(strip && controller); | |
| 226 strip_ = strip; | |
| 227 controller_ = controller; | |
| 228 } | |
| 229 return self; | |
| 230 } | |
| 231 | |
| 232 - (void)invalidate { | |
| 233 strip_ = nil; | |
| 234 controller_ = nil; | |
| 235 } | |
| 236 | |
| 237 - (void)animationDidStop:(CAAnimation*)animation finished:(BOOL)finished { | |
| 238 [strip_ animationDidStopForController:controller_ finished:finished]; | |
| 239 } | |
| 240 | |
| 241 @end | |
| 242 | |
| 243 #pragma mark - | |
| 244 | |
| 245 // In general, there is a one-to-one correspondence between TabControllers, | |
| 246 // TabViews, TabContentsControllers, and the TabContents in the TabStripModel. | |
| 247 // In the steady-state, the indices line up so an index coming from the model | |
| 248 // is directly mapped to the same index in the parallel arrays holding our | |
| 249 // views and controllers. This is also true when new tabs are created (even | |
| 250 // though there is a small period of animation) because the tab is present | |
| 251 // in the model while the TabView is animating into place. As a result, nothing | |
| 252 // special need be done to handle "new tab" animation. | |
| 253 // | |
| 254 // This all goes out the window with the "close tab" animation. The animation | |
| 255 // kicks off in |-tabDetachedWithContents:atIndex:| with the notification that | |
| 256 // the tab has been removed from the model. The simplest solution at this | |
| 257 // point would be to remove the views and controllers as well, however once | |
| 258 // the TabView is removed from the view list, the tab z-order code takes care of | |
| 259 // removing it from the tab strip and we'll get no animation. That means if | |
| 260 // there is to be any visible animation, the TabView needs to stay around until | |
| 261 // its animation is complete. In order to maintain consistency among the | |
| 262 // internal parallel arrays, this means all structures are kept around until | |
| 263 // the animation completes. At this point, though, the model and our internal | |
| 264 // structures are out of sync: the indices no longer line up. As a result, | |
| 265 // there is a concept of a "model index" which represents an index valid in | |
| 266 // the TabStripModel. During steady-state, the "model index" is just the same | |
| 267 // index as our parallel arrays (as above), but during tab close animations, | |
| 268 // it is different, offset by the number of tabs preceding the index which | |
| 269 // are undergoing tab closing animation. As a result, the caller needs to be | |
| 270 // careful to use the available conversion routines when accessing the internal | |
| 271 // parallel arrays (e.g., -indexFromModelIndex:). Care also needs to be taken | |
| 272 // during tab layout to ignore closing tabs in the total width calculations and | |
| 273 // in individual tab positioning (to avoid moving them right back to where they | |
| 274 // were). | |
| 275 // | |
| 276 // In order to prevent actions being taken on tabs which are closing, the tab | |
| 277 // itself gets marked as such so it no longer will send back its select action | |
| 278 // or allow itself to be dragged. In addition, drags on the tab strip as a | |
| 279 // whole are disabled while there are tabs closing. | |
| 280 | |
| 281 @implementation TabStripController | |
| 282 | |
| 283 @synthesize indentForControls = indentForControls_; | |
| 284 | |
| 285 - (id)initWithView:(TabStripView*)view | |
| 286 switchView:(NSView*)switchView | |
| 287 browser:(Browser*)browser | |
| 288 delegate:(id<TabStripControllerDelegate>)delegate { | |
| 289 DCHECK(view && switchView && browser && delegate); | |
| 290 if ((self = [super init])) { | |
| 291 tabStripView_.reset([view retain]); | |
| 292 switchView_ = switchView; | |
| 293 browser_ = browser; | |
| 294 tabStripModel_ = browser_->tabstrip_model(); | |
| 295 delegate_ = delegate; | |
| 296 bridge_.reset(new TabStripModelObserverBridge(tabStripModel_, self)); | |
| 297 tabContentsArray_.reset([[NSMutableArray alloc] init]); | |
| 298 tabArray_.reset([[NSMutableArray alloc] init]); | |
| 299 | |
| 300 // Important note: any non-tab subviews not added to |permanentSubviews_| | |
| 301 // (see |-addSubviewToPermanentList:|) will be wiped out. | |
| 302 permanentSubviews_.reset([[NSMutableArray alloc] init]); | |
| 303 | |
| 304 ResourceBundle& rb = ResourceBundle::GetSharedInstance(); | |
| 305 defaultFavIcon_.reset([rb.GetNativeImageNamed(IDR_DEFAULT_FAVICON) retain]); | |
| 306 | |
| 307 [self setIndentForControls:[[self class] defaultIndentForControls]]; | |
| 308 | |
| 309 // TODO(viettrungluu): WTF? "For some reason, if the view is present in the | |
| 310 // nib a priori, it draws correctly. If we create it in code and add it to | |
| 311 // the tab view, it draws with all sorts of crazy artifacts." | |
| 312 newTabButton_ = [view newTabButton]; | |
| 313 [self addSubviewToPermanentList:newTabButton_]; | |
| 314 [newTabButton_ setTarget:nil]; | |
| 315 [newTabButton_ setAction:@selector(commandDispatch:)]; | |
| 316 [newTabButton_ setTag:IDC_NEW_TAB]; | |
| 317 // Set the images from code because Cocoa fails to find them in our sub | |
| 318 // bundle during tests. | |
| 319 [newTabButton_ setImage:app::mac::GetCachedImageWithName(kNewTabImage)]; | |
| 320 [newTabButton_ setAlternateImage: | |
| 321 app::mac::GetCachedImageWithName(kNewTabPressedImage)]; | |
| 322 newTabButtonShowingHoverImage_ = NO; | |
| 323 newTabTrackingArea_.reset( | |
| 324 [[NSTrackingArea alloc] initWithRect:[newTabButton_ bounds] | |
| 325 options:(NSTrackingMouseEnteredAndExited | | |
| 326 NSTrackingActiveAlways) | |
| 327 owner:self | |
| 328 userInfo:nil]); | |
| 329 [newTabButton_ addTrackingArea:newTabTrackingArea_.get()]; | |
| 330 targetFrames_.reset([[NSMutableDictionary alloc] init]); | |
| 331 | |
| 332 dragBlockingView_.reset( | |
| 333 [[TabStripControllerDragBlockingView alloc] initWithFrame:NSZeroRect | |
| 334 controller:self]); | |
| 335 [self addSubviewToPermanentList:dragBlockingView_]; | |
| 336 | |
| 337 newTabTargetFrame_ = NSMakeRect(0, 0, 0, 0); | |
| 338 availableResizeWidth_ = kUseFullAvailableWidth; | |
| 339 | |
| 340 closingControllers_.reset([[NSMutableSet alloc] init]); | |
| 341 | |
| 342 // Install the permanent subviews. | |
| 343 [self regenerateSubviewList]; | |
| 344 | |
| 345 // Watch for notifications that the tab strip view has changed size so | |
| 346 // we can tell it to layout for the new size. | |
| 347 [[NSNotificationCenter defaultCenter] | |
| 348 addObserver:self | |
| 349 selector:@selector(tabViewFrameChanged:) | |
| 350 name:NSViewFrameDidChangeNotification | |
| 351 object:tabStripView_]; | |
| 352 | |
| 353 trackingArea_.reset([[NSTrackingArea alloc] | |
| 354 initWithRect:NSZeroRect // Ignored by NSTrackingInVisibleRect | |
| 355 options:NSTrackingMouseEnteredAndExited | | |
| 356 NSTrackingMouseMoved | | |
| 357 NSTrackingActiveAlways | | |
| 358 NSTrackingInVisibleRect | |
| 359 owner:self | |
| 360 userInfo:nil]); | |
| 361 [tabStripView_ addTrackingArea:trackingArea_.get()]; | |
| 362 | |
| 363 // Check to see if the mouse is currently in our bounds so we can | |
| 364 // enable the tracking areas. Otherwise we won't get hover states | |
| 365 // or tab gradients if we load the window up under the mouse. | |
| 366 NSPoint mouseLoc = [[view window] mouseLocationOutsideOfEventStream]; | |
| 367 mouseLoc = [view convertPoint:mouseLoc fromView:nil]; | |
| 368 if (NSPointInRect(mouseLoc, [view bounds])) { | |
| 369 [self setTabTrackingAreasEnabled:YES]; | |
| 370 mouseInside_ = YES; | |
| 371 } | |
| 372 | |
| 373 // Set accessibility descriptions. http://openradar.appspot.com/7496255 | |
| 374 NSString* description = l10n_util::GetNSStringWithFixup(IDS_ACCNAME_NEWTAB); | |
| 375 [[newTabButton_ cell] | |
| 376 accessibilitySetOverrideValue:description | |
| 377 forAttribute:NSAccessibilityDescriptionAttribute]; | |
| 378 | |
| 379 // Controller may have been (re-)created by switching layout modes, which | |
| 380 // means the tab model is already fully formed with tabs. Need to walk the | |
| 381 // list and create the UI for each. | |
| 382 const int existingTabCount = tabStripModel_->count(); | |
| 383 const TabContentsWrapper* selection = | |
| 384 tabStripModel_->GetSelectedTabContents(); | |
| 385 for (int i = 0; i < existingTabCount; ++i) { | |
| 386 TabContentsWrapper* currentContents = tabStripModel_->GetTabContentsAt(i); | |
| 387 [self insertTabWithContents:currentContents | |
| 388 atIndex:i | |
| 389 inForeground:NO]; | |
| 390 if (selection == currentContents) { | |
| 391 // Must manually force a selection since the model won't send | |
| 392 // selection messages in this scenario. | |
| 393 [self selectTabWithContents:currentContents | |
| 394 previousContents:NULL | |
| 395 atIndex:i | |
| 396 userGesture:NO]; | |
| 397 } | |
| 398 } | |
| 399 // Don't lay out the tabs until after the controller has been fully | |
| 400 // constructed. The |verticalLayout_| flag has not been initialized by | |
| 401 // subclasses at this point, which would cause layout to potentially use | |
| 402 // the wrong mode. | |
| 403 if (existingTabCount) { | |
| 404 [self performSelectorOnMainThread:@selector(layoutTabs) | |
| 405 withObject:nil | |
| 406 waitUntilDone:NO]; | |
| 407 } | |
| 408 } | |
| 409 return self; | |
| 410 } | |
| 411 | |
| 412 - (void)dealloc { | |
| 413 if (trackingArea_.get()) | |
| 414 [tabStripView_ removeTrackingArea:trackingArea_.get()]; | |
| 415 | |
| 416 [newTabButton_ removeTrackingArea:newTabTrackingArea_.get()]; | |
| 417 // Invalidate all closing animations so they don't call back to us after | |
| 418 // we're gone. | |
| 419 for (TabController* controller in closingControllers_.get()) { | |
| 420 NSView* view = [controller view]; | |
| 421 [[[view animationForKey:@"frameOrigin"] delegate] invalidate]; | |
| 422 } | |
| 423 [[NSNotificationCenter defaultCenter] removeObserver:self]; | |
| 424 [super dealloc]; | |
| 425 } | |
| 426 | |
| 427 + (CGFloat)defaultTabHeight { | |
| 428 return 25.0; | |
| 429 } | |
| 430 | |
| 431 + (CGFloat)defaultIndentForControls { | |
| 432 // Default indentation leaves enough room so tabs don't overlap with the | |
| 433 // window controls. | |
| 434 return 68.0; | |
| 435 } | |
| 436 | |
| 437 // Finds the TabContentsController associated with the given index into the tab | |
| 438 // model and swaps out the sole child of the contentArea to display its | |
| 439 // contents. | |
| 440 - (void)swapInTabAtIndex:(NSInteger)modelIndex { | |
| 441 DCHECK(modelIndex >= 0 && modelIndex < tabStripModel_->count()); | |
| 442 NSInteger index = [self indexFromModelIndex:modelIndex]; | |
| 443 TabContentsController* controller = [tabContentsArray_ objectAtIndex:index]; | |
| 444 | |
| 445 // Resize the new view to fit the window. Calling |view| may lazily | |
| 446 // instantiate the TabContentsController from the nib. Until we call | |
| 447 // |-ensureContentsVisible|, the controller doesn't install the RWHVMac into | |
| 448 // the view hierarchy. This is in order to avoid sending the renderer a | |
| 449 // spurious default size loaded from the nib during the call to |-view|. | |
| 450 NSView* newView = [controller view]; | |
| 451 | |
| 452 // Turns content autoresizing off, so removing and inserting views won't | |
| 453 // trigger unnecessary content relayout. | |
| 454 [controller ensureContentsSizeDoesNotChange]; | |
| 455 | |
| 456 // Remove the old view from the view hierarchy. We know there's only one | |
| 457 // child of |switchView_| because we're the one who put it there. There | |
| 458 // may not be any children in the case of a tab that's been closed, in | |
| 459 // which case there's no swapping going on. | |
| 460 NSArray* subviews = [switchView_ subviews]; | |
| 461 if ([subviews count]) { | |
| 462 NSView* oldView = [subviews objectAtIndex:0]; | |
| 463 // Set newView frame to the oldVew frame to prevent NSSplitView hosting | |
| 464 // sidebar and tab content from resizing sidebar's content view. | |
| 465 // ensureContentsVisible (see below) sets content size and autoresizing | |
| 466 // properties. | |
| 467 [newView setFrame:[oldView frame]]; | |
| 468 [switchView_ replaceSubview:oldView with:newView]; | |
| 469 } else { | |
| 470 [newView setFrame:[switchView_ bounds]]; | |
| 471 [switchView_ addSubview:newView]; | |
| 472 } | |
| 473 | |
| 474 // New content is in place, delegate should adjust itself accordingly. | |
| 475 [delegate_ onSelectTabWithContents:[controller tabContents]]; | |
| 476 | |
| 477 // It also restores content autoresizing properties. | |
| 478 [controller ensureContentsVisible]; | |
| 479 | |
| 480 // Make sure the new tabs's sheets are visible (necessary when a background | |
| 481 // tab opened a sheet while it was in the background and now becomes active). | |
| 482 TabContentsWrapper* newTab = tabStripModel_->GetTabContentsAt(modelIndex); | |
| 483 DCHECK(newTab); | |
| 484 if (newTab) { | |
| 485 TabContents::ConstrainedWindowList::iterator it, end; | |
| 486 end = newTab->tab_contents()->constrained_window_end(); | |
| 487 NSWindowController* controller = [[newView window] windowController]; | |
| 488 DCHECK([controller isKindOfClass:[BrowserWindowController class]]); | |
| 489 | |
| 490 for (it = newTab->tab_contents()->constrained_window_begin(); | |
| 491 it != end; | |
| 492 ++it) { | |
| 493 ConstrainedWindow* constrainedWindow = *it; | |
| 494 static_cast<ConstrainedWindowMac*>(constrainedWindow)->Realize( | |
| 495 static_cast<BrowserWindowController*>(controller)); | |
| 496 } | |
| 497 } | |
| 498 | |
| 499 // Tell per-tab sheet manager about currently selected tab. | |
| 500 if (sheetController_.get()) { | |
| 501 [sheetController_ setActiveView:newView]; | |
| 502 } | |
| 503 } | |
| 504 | |
| 505 // Create a new tab view and set its cell correctly so it draws the way we want | |
| 506 // it to. It will be sized and positioned by |-layoutTabs| so there's no need to | |
| 507 // set the frame here. This also creates the view as hidden, it will be | |
| 508 // shown during layout. | |
| 509 - (TabController*)newTab { | |
| 510 TabController* controller = [[[TabController alloc] init] autorelease]; | |
| 511 [controller setTarget:self]; | |
| 512 [controller setAction:@selector(selectTab:)]; | |
| 513 [[controller view] setHidden:YES]; | |
| 514 | |
| 515 return controller; | |
| 516 } | |
| 517 | |
| 518 // (Private) Returns the number of open tabs in the tab strip. This is the | |
| 519 // number of TabControllers we know about (as there's a 1-to-1 mapping from | |
| 520 // these controllers to a tab) less the number of closing tabs. | |
| 521 - (NSInteger)numberOfOpenTabs { | |
| 522 return static_cast<NSInteger>(tabStripModel_->count()); | |
| 523 } | |
| 524 | |
| 525 // (Private) Returns the number of open, mini-tabs. | |
| 526 - (NSInteger)numberOfOpenMiniTabs { | |
| 527 // Ask the model for the number of mini tabs. Note that tabs which are in | |
| 528 // the process of closing (i.e., whose controllers are in | |
| 529 // |closingControllers_|) have already been removed from the model. | |
| 530 return tabStripModel_->IndexOfFirstNonMiniTab(); | |
| 531 } | |
| 532 | |
| 533 // (Private) Returns the number of open, non-mini tabs. | |
| 534 - (NSInteger)numberOfOpenNonMiniTabs { | |
| 535 NSInteger number = [self numberOfOpenTabs] - [self numberOfOpenMiniTabs]; | |
| 536 DCHECK_GE(number, 0); | |
| 537 return number; | |
| 538 } | |
| 539 | |
| 540 // Given an index into the tab model, returns the index into the tab controller | |
| 541 // or tab contents controller array accounting for tabs that are currently | |
| 542 // closing. For example, if there are two tabs in the process of closing before | |
| 543 // |index|, this returns |index| + 2. If there are no closing tabs, this will | |
| 544 // return |index|. | |
| 545 - (NSInteger)indexFromModelIndex:(NSInteger)index { | |
| 546 DCHECK(index >= 0); | |
| 547 if (index < 0) | |
| 548 return index; | |
| 549 | |
| 550 NSInteger i = 0; | |
| 551 for (TabController* controller in tabArray_.get()) { | |
| 552 if ([closingControllers_ containsObject:controller]) { | |
| 553 DCHECK([(TabView*)[controller view] isClosing]); | |
| 554 ++index; | |
| 555 } | |
| 556 if (i == index) // No need to check anything after, it has no effect. | |
| 557 break; | |
| 558 ++i; | |
| 559 } | |
| 560 return index; | |
| 561 } | |
| 562 | |
| 563 | |
| 564 // Returns the index of the subview |view|. Returns -1 if not present. Takes | |
| 565 // closing tabs into account such that this index will correctly match the tab | |
| 566 // model. If |view| is in the process of closing, returns -1, as closing tabs | |
| 567 // are no longer in the model. | |
| 568 - (NSInteger)modelIndexForTabView:(NSView*)view { | |
| 569 NSInteger index = 0; | |
| 570 for (TabController* current in tabArray_.get()) { | |
| 571 // If |current| is closing, skip it. | |
| 572 if ([closingControllers_ containsObject:current]) | |
| 573 continue; | |
| 574 else if ([current view] == view) | |
| 575 return index; | |
| 576 ++index; | |
| 577 } | |
| 578 return -1; | |
| 579 } | |
| 580 | |
| 581 // Returns the index of the contents subview |view|. Returns -1 if not present. | |
| 582 // Takes closing tabs into account such that this index will correctly match the | |
| 583 // tab model. If |view| is in the process of closing, returns -1, as closing | |
| 584 // tabs are no longer in the model. | |
| 585 - (NSInteger)modelIndexForContentsView:(NSView*)view { | |
| 586 NSInteger index = 0; | |
| 587 NSInteger i = 0; | |
| 588 for (TabContentsController* current in tabContentsArray_.get()) { | |
| 589 // If the TabController corresponding to |current| is closing, skip it. | |
| 590 TabController* controller = [tabArray_ objectAtIndex:i]; | |
| 591 if ([closingControllers_ containsObject:controller]) { | |
| 592 ++i; | |
| 593 continue; | |
| 594 } else if ([current view] == view) { | |
| 595 return index; | |
| 596 } | |
| 597 ++index; | |
| 598 ++i; | |
| 599 } | |
| 600 return -1; | |
| 601 } | |
| 602 | |
| 603 | |
| 604 // Returns the view at the given index, using the array of TabControllers to | |
| 605 // get the associated view. Returns nil if out of range. | |
| 606 - (NSView*)viewAtIndex:(NSUInteger)index { | |
| 607 if (index >= [tabArray_ count]) | |
| 608 return NULL; | |
| 609 return [[tabArray_ objectAtIndex:index] view]; | |
| 610 } | |
| 611 | |
| 612 - (NSUInteger)viewsCount { | |
| 613 return [tabArray_ count]; | |
| 614 } | |
| 615 | |
| 616 // Called when the user clicks a tab. Tell the model the selection has changed, | |
| 617 // which feeds back into us via a notification. | |
| 618 - (void)selectTab:(id)sender { | |
| 619 DCHECK([sender isKindOfClass:[NSView class]]); | |
| 620 int index = [self modelIndexForTabView:sender]; | |
| 621 if (tabStripModel_->ContainsIndex(index)) | |
| 622 tabStripModel_->SelectTabContentsAt(index, true); | |
| 623 } | |
| 624 | |
| 625 // Called when the user closes a tab. Asks the model to close the tab. |sender| | |
| 626 // is the TabView that is potentially going away. | |
| 627 - (void)closeTab:(id)sender { | |
| 628 DCHECK([sender isKindOfClass:[TabView class]]); | |
| 629 if ([hoveredTab_ isEqual:sender]) { | |
| 630 hoveredTab_ = nil; | |
| 631 } | |
| 632 | |
| 633 NSInteger index = [self modelIndexForTabView:sender]; | |
| 634 if (!tabStripModel_->ContainsIndex(index)) | |
| 635 return; | |
| 636 | |
| 637 TabContentsWrapper* contents = tabStripModel_->GetTabContentsAt(index); | |
| 638 if (contents) | |
| 639 UserMetrics::RecordAction(UserMetricsAction("CloseTab_Mouse"), | |
| 640 contents->tab_contents()->profile()); | |
| 641 const NSInteger numberOfOpenTabs = [self numberOfOpenTabs]; | |
| 642 if (numberOfOpenTabs > 1) { | |
| 643 bool isClosingLastTab = index == numberOfOpenTabs - 1; | |
| 644 if (!isClosingLastTab) { | |
| 645 // Limit the width available for laying out tabs so that tabs are not | |
| 646 // resized until a later time (when the mouse leaves the tab strip). | |
| 647 // However, if the tab being closed is a pinned tab, break out of | |
| 648 // rapid-closure mode since the mouse is almost guaranteed not to be over | |
| 649 // the closebox of the adjacent tab (due to the difference in widths). | |
| 650 // TODO(pinkerton): re-visit when handling tab overflow. | |
| 651 // http://crbug.com/188 | |
| 652 if (tabStripModel_->IsTabPinned(index)) { | |
| 653 availableResizeWidth_ = kUseFullAvailableWidth; | |
| 654 } else { | |
| 655 NSView* penultimateTab = [self viewAtIndex:numberOfOpenTabs - 2]; | |
| 656 availableResizeWidth_ = NSMaxX([penultimateTab frame]); | |
| 657 } | |
| 658 } else { | |
| 659 // If the rightmost tab is closed, change the available width so that | |
| 660 // another tab's close button lands below the cursor (assuming the tabs | |
| 661 // are currently below their maximum width and can grow). | |
| 662 NSView* lastTab = [self viewAtIndex:numberOfOpenTabs - 1]; | |
| 663 availableResizeWidth_ = NSMaxX([lastTab frame]); | |
| 664 } | |
| 665 tabStripModel_->CloseTabContentsAt( | |
| 666 index, | |
| 667 TabStripModel::CLOSE_USER_GESTURE | | |
| 668 TabStripModel::CLOSE_CREATE_HISTORICAL_TAB); | |
| 669 } else { | |
| 670 // Use the standard window close if this is the last tab | |
| 671 // this prevents the tab from being removed from the model until after | |
| 672 // the window dissapears | |
| 673 [[tabStripView_ window] performClose:nil]; | |
| 674 } | |
| 675 } | |
| 676 | |
| 677 // Dispatch context menu commands for the given tab controller. | |
| 678 - (void)commandDispatch:(TabStripModel::ContextMenuCommand)command | |
| 679 forController:(TabController*)controller { | |
| 680 int index = [self modelIndexForTabView:[controller view]]; | |
| 681 if (tabStripModel_->ContainsIndex(index)) | |
| 682 tabStripModel_->ExecuteContextMenuCommand(index, command); | |
| 683 } | |
| 684 | |
| 685 // Returns YES if the specificed command should be enabled for the given | |
| 686 // controller. | |
| 687 - (BOOL)isCommandEnabled:(TabStripModel::ContextMenuCommand)command | |
| 688 forController:(TabController*)controller { | |
| 689 int index = [self modelIndexForTabView:[controller view]]; | |
| 690 if (!tabStripModel_->ContainsIndex(index)) | |
| 691 return NO; | |
| 692 return tabStripModel_->IsContextMenuCommandEnabled(index, command) ? YES : NO; | |
| 693 } | |
| 694 | |
| 695 - (void)insertPlaceholderForTab:(TabView*)tab | |
| 696 frame:(NSRect)frame | |
| 697 yStretchiness:(CGFloat)yStretchiness { | |
| 698 placeholderTab_ = tab; | |
| 699 placeholderFrame_ = frame; | |
| 700 placeholderStretchiness_ = yStretchiness; | |
| 701 [self layoutTabsWithAnimation:initialLayoutComplete_ regenerateSubviews:NO]; | |
| 702 } | |
| 703 | |
| 704 - (BOOL)isDragSessionActive { | |
| 705 return placeholderTab_ != nil; | |
| 706 } | |
| 707 | |
| 708 - (BOOL)isTabFullyVisible:(TabView*)tab { | |
| 709 NSRect frame = [tab frame]; | |
| 710 return NSMinX(frame) >= [self indentForControls] && | |
| 711 NSMaxX(frame) <= NSMaxX([tabStripView_ frame]); | |
| 712 } | |
| 713 | |
| 714 - (void)showNewTabButton:(BOOL)show { | |
| 715 forceNewTabButtonHidden_ = show ? NO : YES; | |
| 716 if (forceNewTabButtonHidden_) | |
| 717 [newTabButton_ setHidden:YES]; | |
| 718 } | |
| 719 | |
| 720 // Lay out all tabs in the order of their TabContentsControllers, which matches | |
| 721 // the ordering in the TabStripModel. This call isn't that expensive, though | |
| 722 // it is O(n) in the number of tabs. Tabs will animate to their new position | |
| 723 // if the window is visible and |animate| is YES. | |
| 724 // TODO(pinkerton): Note this doesn't do too well when the number of min-sized | |
| 725 // tabs would cause an overflow. http://crbug.com/188 | |
| 726 - (void)layoutTabsWithAnimation:(BOOL)animate | |
| 727 regenerateSubviews:(BOOL)doUpdate { | |
| 728 DCHECK([NSThread isMainThread]); | |
| 729 if (![tabArray_ count]) | |
| 730 return; | |
| 731 | |
| 732 const CGFloat kMaxTabWidth = [TabController maxTabWidth]; | |
| 733 const CGFloat kMinTabWidth = [TabController minTabWidth]; | |
| 734 const CGFloat kMinSelectedTabWidth = [TabController minSelectedTabWidth]; | |
| 735 const CGFloat kMiniTabWidth = [TabController miniTabWidth]; | |
| 736 const CGFloat kAppTabWidth = [TabController appTabWidth]; | |
| 737 | |
| 738 NSRect enclosingRect = NSZeroRect; | |
| 739 ScopedNSAnimationContextGroup mainAnimationGroup(animate); | |
| 740 mainAnimationGroup.SetCurrentContextDuration(kAnimationDuration); | |
| 741 | |
| 742 // Update the current subviews and their z-order if requested. | |
| 743 if (doUpdate) | |
| 744 [self regenerateSubviewList]; | |
| 745 | |
| 746 // Compute the base width of tabs given how much room we're allowed. Note that | |
| 747 // mini-tabs have a fixed width. We may not be able to use the entire width | |
| 748 // if the user is quickly closing tabs. This may be negative, but that's okay | |
| 749 // (taken care of by |MAX()| when calculating tab sizes). | |
| 750 CGFloat availableSpace = 0; | |
| 751 if (verticalLayout_) { | |
| 752 availableSpace = NSHeight([tabStripView_ bounds]); | |
| 753 } else { | |
| 754 if ([self inRapidClosureMode]) { | |
| 755 availableSpace = availableResizeWidth_; | |
| 756 } else { | |
| 757 availableSpace = NSWidth([tabStripView_ frame]); | |
| 758 // Account for the new tab button and the incognito badge. | |
| 759 availableSpace -= NSWidth([newTabButton_ frame]) + kNewTabButtonOffset; | |
| 760 if (browser_->profile()->IsOffTheRecord()) | |
| 761 availableSpace -= kIncognitoBadgeTabStripShrink; | |
| 762 } | |
| 763 availableSpace -= [self indentForControls]; | |
| 764 } | |
| 765 | |
| 766 // This may be negative, but that's okay (taken care of by |MAX()| when | |
| 767 // calculating tab sizes). "mini" tabs in horizontal mode just get a special | |
| 768 // section, they don't change size. | |
| 769 CGFloat availableSpaceForNonMini = availableSpace; | |
| 770 if (!verticalLayout_) { | |
| 771 availableSpaceForNonMini -= | |
| 772 [self numberOfOpenMiniTabs] * (kMiniTabWidth - kTabOverlap); | |
| 773 } | |
| 774 | |
| 775 // Initialize |nonMiniTabWidth| in case there aren't any non-mini-tabs; this | |
| 776 // value shouldn't actually be used. | |
| 777 CGFloat nonMiniTabWidth = kMaxTabWidth; | |
| 778 const NSInteger numberOfOpenNonMiniTabs = [self numberOfOpenNonMiniTabs]; | |
| 779 if (!verticalLayout_ && numberOfOpenNonMiniTabs) { | |
| 780 // Find the width of a non-mini-tab. This only applies to horizontal | |
| 781 // mode. Add in the amount we "get back" from the tabs overlapping. | |
| 782 availableSpaceForNonMini += (numberOfOpenNonMiniTabs - 1) * kTabOverlap; | |
| 783 | |
| 784 // Divide up the space between the non-mini-tabs. | |
| 785 nonMiniTabWidth = availableSpaceForNonMini / numberOfOpenNonMiniTabs; | |
| 786 | |
| 787 // Clamp the width between the max and min. | |
| 788 nonMiniTabWidth = MAX(MIN(nonMiniTabWidth, kMaxTabWidth), kMinTabWidth); | |
| 789 } | |
| 790 | |
| 791 BOOL visible = [[tabStripView_ window] isVisible]; | |
| 792 | |
| 793 CGFloat offset = [self indentForControls]; | |
| 794 bool hasPlaceholderGap = false; | |
| 795 for (TabController* tab in tabArray_.get()) { | |
| 796 // Ignore a tab that is going through a close animation. | |
| 797 if ([closingControllers_ containsObject:tab]) | |
| 798 continue; | |
| 799 | |
| 800 BOOL isPlaceholder = [[tab view] isEqual:placeholderTab_]; | |
| 801 NSRect tabFrame = [[tab view] frame]; | |
| 802 tabFrame.size.height = [[self class] defaultTabHeight] + 1; | |
| 803 if (verticalLayout_) { | |
| 804 tabFrame.origin.y = availableSpace - tabFrame.size.height - offset; | |
| 805 tabFrame.origin.x = 0; | |
| 806 } else { | |
| 807 tabFrame.origin.y = 0; | |
| 808 tabFrame.origin.x = offset; | |
| 809 } | |
| 810 // If the tab is hidden, we consider it a new tab. We make it visible | |
| 811 // and animate it in. | |
| 812 BOOL newTab = [[tab view] isHidden]; | |
| 813 if (newTab) | |
| 814 [[tab view] setHidden:NO]; | |
| 815 | |
| 816 if (isPlaceholder) { | |
| 817 // Move the current tab to the correct location instantly. | |
| 818 // We need a duration or else it doesn't cancel an inflight animation. | |
| 819 ScopedNSAnimationContextGroup localAnimationGroup(animate); | |
| 820 localAnimationGroup.SetCurrentContextShortestDuration(); | |
| 821 if (verticalLayout_) | |
| 822 tabFrame.origin.y = availableSpace - tabFrame.size.height - offset; | |
| 823 else | |
| 824 tabFrame.origin.x = placeholderFrame_.origin.x; | |
| 825 // TODO(alcor): reenable this | |
| 826 //tabFrame.size.height += 10.0 * placeholderStretchiness_; | |
| 827 id target = animate ? [[tab view] animator] : [tab view]; | |
| 828 [target setFrame:tabFrame]; | |
| 829 | |
| 830 // Store the frame by identifier to aviod redundant calls to animator. | |
| 831 NSValue* identifier = [NSValue valueWithPointer:[tab view]]; | |
| 832 [targetFrames_ setObject:[NSValue valueWithRect:tabFrame] | |
| 833 forKey:identifier]; | |
| 834 continue; | |
| 835 } | |
| 836 | |
| 837 if (placeholderTab_ && !hasPlaceholderGap) { | |
| 838 const CGFloat placeholderMin = | |
| 839 verticalLayout_ ? NSMinY(placeholderFrame_) : | |
| 840 NSMinX(placeholderFrame_); | |
| 841 if (verticalLayout_) { | |
| 842 if (NSMidY(tabFrame) > placeholderMin) { | |
| 843 hasPlaceholderGap = true; | |
| 844 offset += NSHeight(placeholderFrame_); | |
| 845 tabFrame.origin.y = availableSpace - tabFrame.size.height - offset; | |
| 846 } | |
| 847 } else { | |
| 848 // If the left edge is to the left of the placeholder's left, but the | |
| 849 // mid is to the right of it slide over to make space for it. | |
| 850 if (NSMidX(tabFrame) > placeholderMin) { | |
| 851 hasPlaceholderGap = true; | |
| 852 offset += NSWidth(placeholderFrame_); | |
| 853 offset -= kTabOverlap; | |
| 854 tabFrame.origin.x = offset; | |
| 855 } | |
| 856 } | |
| 857 } | |
| 858 | |
| 859 // Set the width. Selected tabs are slightly wider when things get really | |
| 860 // small and thus we enforce a different minimum width. | |
| 861 tabFrame.size.width = [tab mini] ? | |
| 862 ([tab app] ? kAppTabWidth : kMiniTabWidth) : nonMiniTabWidth; | |
| 863 if ([tab selected]) | |
| 864 tabFrame.size.width = MAX(tabFrame.size.width, kMinSelectedTabWidth); | |
| 865 | |
| 866 // Animate a new tab in by putting it below the horizon unless told to put | |
| 867 // it in a specific location (i.e., from a drop). | |
| 868 // TODO(pinkerton): figure out vertical tab animations. | |
| 869 if (newTab && visible && animate) { | |
| 870 if (NSEqualRects(droppedTabFrame_, NSZeroRect)) { | |
| 871 [[tab view] setFrame:NSOffsetRect(tabFrame, 0, -NSHeight(tabFrame))]; | |
| 872 } else { | |
| 873 [[tab view] setFrame:droppedTabFrame_]; | |
| 874 droppedTabFrame_ = NSZeroRect; | |
| 875 } | |
| 876 } | |
| 877 | |
| 878 // Check the frame by identifier to avoid redundant calls to animator. | |
| 879 id frameTarget = visible && animate ? [[tab view] animator] : [tab view]; | |
| 880 NSValue* identifier = [NSValue valueWithPointer:[tab view]]; | |
| 881 NSValue* oldTargetValue = [targetFrames_ objectForKey:identifier]; | |
| 882 if (!oldTargetValue || | |
| 883 !NSEqualRects([oldTargetValue rectValue], tabFrame)) { | |
| 884 [frameTarget setFrame:tabFrame]; | |
| 885 [targetFrames_ setObject:[NSValue valueWithRect:tabFrame] | |
| 886 forKey:identifier]; | |
| 887 } | |
| 888 | |
| 889 enclosingRect = NSUnionRect(tabFrame, enclosingRect); | |
| 890 | |
| 891 if (verticalLayout_) { | |
| 892 offset += NSHeight(tabFrame); | |
| 893 } else { | |
| 894 offset += NSWidth(tabFrame); | |
| 895 offset -= kTabOverlap; | |
| 896 } | |
| 897 } | |
| 898 | |
| 899 // Hide the new tab button if we're explicitly told to. It may already | |
| 900 // be hidden, doing it again doesn't hurt. Otherwise position it | |
| 901 // appropriately, showing it if necessary. | |
| 902 if (forceNewTabButtonHidden_) { | |
| 903 [newTabButton_ setHidden:YES]; | |
| 904 } else { | |
| 905 NSRect newTabNewFrame = [newTabButton_ frame]; | |
| 906 // We've already ensured there's enough space for the new tab button | |
| 907 // so we don't have to check it against the available space. We do need | |
| 908 // to make sure we put it after any placeholder. | |
| 909 CGFloat maxTabX = MAX(offset, NSMaxX(placeholderFrame_) - kTabOverlap); | |
| 910 newTabNewFrame.origin = NSMakePoint(maxTabX + kNewTabButtonOffset, 0); | |
| 911 if ([tabContentsArray_ count]) | |
| 912 [newTabButton_ setHidden:NO]; | |
| 913 | |
| 914 if (!NSEqualRects(newTabTargetFrame_, newTabNewFrame)) { | |
| 915 // Set the new tab button image correctly based on where the cursor is. | |
| 916 NSWindow* window = [tabStripView_ window]; | |
| 917 NSPoint currentMouse = [window mouseLocationOutsideOfEventStream]; | |
| 918 currentMouse = [tabStripView_ convertPoint:currentMouse fromView:nil]; | |
| 919 | |
| 920 BOOL shouldShowHover = [newTabButton_ pointIsOverButton:currentMouse]; | |
| 921 [self setNewTabButtonHoverState:shouldShowHover]; | |
| 922 | |
| 923 // Move the new tab button into place. We want to animate the new tab | |
| 924 // button if it's moving to the left (closing a tab), but not when it's | |
| 925 // moving to the right (inserting a new tab). If moving right, we need | |
| 926 // to use a very small duration to make sure we cancel any in-flight | |
| 927 // animation to the left. | |
| 928 if (visible && animate) { | |
| 929 ScopedNSAnimationContextGroup localAnimationGroup(true); | |
| 930 BOOL movingLeft = NSMinX(newTabNewFrame) < NSMinX(newTabTargetFrame_); | |
| 931 if (!movingLeft) { | |
| 932 localAnimationGroup.SetCurrentContextShortestDuration(); | |
| 933 } | |
| 934 [[newTabButton_ animator] setFrame:newTabNewFrame]; | |
| 935 newTabTargetFrame_ = newTabNewFrame; | |
| 936 } else { | |
| 937 [newTabButton_ setFrame:newTabNewFrame]; | |
| 938 newTabTargetFrame_ = newTabNewFrame; | |
| 939 } | |
| 940 } | |
| 941 } | |
| 942 | |
| 943 [dragBlockingView_ setFrame:enclosingRect]; | |
| 944 | |
| 945 // Mark that we've successfully completed layout of at least one tab. | |
| 946 initialLayoutComplete_ = YES; | |
| 947 } | |
| 948 | |
| 949 // When we're told to layout from the public API we usually want to animate, | |
| 950 // except when it's the first time. | |
| 951 - (void)layoutTabs { | |
| 952 [self layoutTabsWithAnimation:initialLayoutComplete_ regenerateSubviews:YES]; | |
| 953 } | |
| 954 | |
| 955 // Handles setting the title of the tab based on the given |contents|. Uses | |
| 956 // a canned string if |contents| is NULL. | |
| 957 - (void)setTabTitle:(NSViewController*)tab withContents:(TabContents*)contents { | |
| 958 NSString* titleString = nil; | |
| 959 if (contents) | |
| 960 titleString = base::SysUTF16ToNSString(contents->GetTitle()); | |
| 961 if (![titleString length]) { | |
| 962 titleString = l10n_util::GetNSString(IDS_BROWSER_WINDOW_MAC_TAB_UNTITLED); | |
| 963 } | |
| 964 [tab setTitle:titleString]; | |
| 965 } | |
| 966 | |
| 967 // Called when a notification is received from the model to insert a new tab | |
| 968 // at |modelIndex|. | |
| 969 - (void)insertTabWithContents:(TabContentsWrapper*)contents | |
| 970 atIndex:(NSInteger)modelIndex | |
| 971 inForeground:(bool)inForeground { | |
| 972 DCHECK(contents); | |
| 973 DCHECK(modelIndex == TabStripModel::kNoTab || | |
| 974 tabStripModel_->ContainsIndex(modelIndex)); | |
| 975 | |
| 976 // Take closing tabs into account. | |
| 977 NSInteger index = [self indexFromModelIndex:modelIndex]; | |
| 978 | |
| 979 // Make a new tab. Load the contents of this tab from the nib and associate | |
| 980 // the new controller with |contents| so it can be looked up later. | |
| 981 scoped_nsobject<TabContentsController> contentsController( | |
| 982 [[TabContentsController alloc] initWithContents:contents->tab_contents() | |
| 983 delegate:self]); | |
| 984 [tabContentsArray_ insertObject:contentsController atIndex:index]; | |
| 985 | |
| 986 // Make a new tab and add it to the strip. Keep track of its controller. | |
| 987 TabController* newController = [self newTab]; | |
| 988 [newController setMini:tabStripModel_->IsMiniTab(modelIndex)]; | |
| 989 [newController setPinned:tabStripModel_->IsTabPinned(modelIndex)]; | |
| 990 [newController setApp:tabStripModel_->IsAppTab(modelIndex)]; | |
| 991 [tabArray_ insertObject:newController atIndex:index]; | |
| 992 NSView* newView = [newController view]; | |
| 993 | |
| 994 // Set the originating frame to just below the strip so that it animates | |
| 995 // upwards as it's being initially layed out. Oddly, this works while doing | |
| 996 // something similar in |-layoutTabs| confuses the window server. | |
| 997 [newView setFrame:NSOffsetRect([newView frame], | |
| 998 0, -[[self class] defaultTabHeight])]; | |
| 999 | |
| 1000 [self setTabTitle:newController withContents:contents->tab_contents()]; | |
| 1001 | |
| 1002 // If a tab is being inserted, we can again use the entire tab strip width | |
| 1003 // for layout. | |
| 1004 availableResizeWidth_ = kUseFullAvailableWidth; | |
| 1005 | |
| 1006 // We don't need to call |-layoutTabs| if the tab will be in the foreground | |
| 1007 // because it will get called when the new tab is selected by the tab model. | |
| 1008 // Whenever |-layoutTabs| is called, it'll also add the new subview. | |
| 1009 if (!inForeground) { | |
| 1010 [self layoutTabs]; | |
| 1011 } | |
| 1012 | |
| 1013 // During normal loading, we won't yet have a favicon and we'll get | |
| 1014 // subsequent state change notifications to show the throbber, but when we're | |
| 1015 // dragging a tab out into a new window, we have to put the tab's favicon | |
| 1016 // into the right state up front as we won't be told to do it from anywhere | |
| 1017 // else. | |
| 1018 [self updateFavIconForContents:contents->tab_contents() atIndex:modelIndex]; | |
| 1019 | |
| 1020 // Send a broadcast that the number of tabs have changed. | |
| 1021 [[NSNotificationCenter defaultCenter] | |
| 1022 postNotificationName:kTabStripNumberOfTabsChanged | |
| 1023 object:self]; | |
| 1024 } | |
| 1025 | |
| 1026 // Called when a notification is received from the model to select a particular | |
| 1027 // tab. Swaps in the toolbar and content area associated with |newContents|. | |
| 1028 - (void)selectTabWithContents:(TabContentsWrapper*)newContents | |
| 1029 previousContents:(TabContentsWrapper*)oldContents | |
| 1030 atIndex:(NSInteger)modelIndex | |
| 1031 userGesture:(bool)wasUserGesture { | |
| 1032 // Take closing tabs into account. | |
| 1033 NSInteger index = [self indexFromModelIndex:modelIndex]; | |
| 1034 | |
| 1035 if (oldContents) { | |
| 1036 int oldModelIndex = | |
| 1037 browser_->GetIndexOfController(&(oldContents->controller())); | |
| 1038 if (oldModelIndex != -1) { // When closing a tab, the old tab may be gone. | |
| 1039 NSInteger oldIndex = [self indexFromModelIndex:oldModelIndex]; | |
| 1040 TabContentsController* oldController = | |
| 1041 [tabContentsArray_ objectAtIndex:oldIndex]; | |
| 1042 [oldController willBecomeUnselectedTab]; | |
| 1043 oldContents->view()->StoreFocus(); | |
| 1044 oldContents->tab_contents()->WasHidden(); | |
| 1045 } | |
| 1046 } | |
| 1047 | |
| 1048 // De-select all other tabs and select the new tab. | |
| 1049 int i = 0; | |
| 1050 for (TabController* current in tabArray_.get()) { | |
| 1051 [current setSelected:(i == index) ? YES : NO]; | |
| 1052 ++i; | |
| 1053 } | |
| 1054 | |
| 1055 // Tell the new tab contents it is about to become the selected tab. Here it | |
| 1056 // can do things like make sure the toolbar is up to date. | |
| 1057 TabContentsController* newController = | |
| 1058 [tabContentsArray_ objectAtIndex:index]; | |
| 1059 [newController willBecomeSelectedTab]; | |
| 1060 | |
| 1061 // Relayout for new tabs and to let the selected tab grow to be larger in | |
| 1062 // size than surrounding tabs if the user has many. This also raises the | |
| 1063 // selected tab to the top. | |
| 1064 [self layoutTabs]; | |
| 1065 | |
| 1066 // Swap in the contents for the new tab. | |
| 1067 [self swapInTabAtIndex:modelIndex]; | |
| 1068 | |
| 1069 if (newContents) { | |
| 1070 newContents->tab_contents()->DidBecomeSelected(); | |
| 1071 newContents->view()->RestoreFocus(); | |
| 1072 | |
| 1073 if (newContents->tab_contents()->find_ui_active()) | |
| 1074 browser_->GetFindBarController()->find_bar()->SetFocusAndSelection(); | |
| 1075 } | |
| 1076 } | |
| 1077 | |
| 1078 - (void)tabReplacedWithContents:(TabContentsWrapper*)newContents | |
| 1079 previousContents:(TabContentsWrapper*)oldContents | |
| 1080 atIndex:(NSInteger)modelIndex { | |
| 1081 NSInteger index = [self indexFromModelIndex:modelIndex]; | |
| 1082 TabContentsController* oldController = | |
| 1083 [tabContentsArray_ objectAtIndex:index]; | |
| 1084 DCHECK_EQ(oldContents->tab_contents(), [oldController tabContents]); | |
| 1085 | |
| 1086 // Simply create a new TabContentsController for |newContents| and place it | |
| 1087 // into the array, replacing |oldContents|. A TabSelectedAt notification will | |
| 1088 // follow, at which point we will install the new view. | |
| 1089 scoped_nsobject<TabContentsController> newController( | |
| 1090 [[TabContentsController alloc] | |
| 1091 initWithContents:newContents->tab_contents() | |
| 1092 delegate:self]); | |
| 1093 | |
| 1094 // Bye bye, |oldController|. | |
| 1095 [tabContentsArray_ replaceObjectAtIndex:index withObject:newController]; | |
| 1096 | |
| 1097 [delegate_ onReplaceTabWithContents:newContents->tab_contents()]; | |
| 1098 | |
| 1099 // Fake a tab changed notification to force tab titles and favicons to update. | |
| 1100 [self tabChangedWithContents:newContents | |
| 1101 atIndex:modelIndex | |
| 1102 changeType:TabStripModelObserver::ALL]; | |
| 1103 } | |
| 1104 | |
| 1105 // Remove all knowledge about this tab and its associated controller, and remove | |
| 1106 // the view from the strip. | |
| 1107 - (void)removeTab:(TabController*)controller { | |
| 1108 NSUInteger index = [tabArray_ indexOfObject:controller]; | |
| 1109 | |
| 1110 // Release the tab contents controller so those views get destroyed. This | |
| 1111 // will remove all the tab content Cocoa views from the hierarchy. A | |
| 1112 // subsequent "select tab" notification will follow from the model. To | |
| 1113 // tell us what to swap in in its absence. | |
| 1114 [tabContentsArray_ removeObjectAtIndex:index]; | |
| 1115 | |
| 1116 // Remove the view from the tab strip. | |
| 1117 NSView* tab = [controller view]; | |
| 1118 [tab removeFromSuperview]; | |
| 1119 | |
| 1120 // Remove ourself as an observer. | |
| 1121 [[NSNotificationCenter defaultCenter] | |
| 1122 removeObserver:self | |
| 1123 name:NSViewDidUpdateTrackingAreasNotification | |
| 1124 object:tab]; | |
| 1125 | |
| 1126 // Clear the tab controller's target. | |
| 1127 // TODO(viettrungluu): [crbug.com/23829] Find a better way to handle the tab | |
| 1128 // controller's target. | |
| 1129 [controller setTarget:nil]; | |
| 1130 | |
| 1131 if ([hoveredTab_ isEqual:tab]) | |
| 1132 hoveredTab_ = nil; | |
| 1133 | |
| 1134 NSValue* identifier = [NSValue valueWithPointer:tab]; | |
| 1135 [targetFrames_ removeObjectForKey:identifier]; | |
| 1136 | |
| 1137 // Once we're totally done with the tab, delete its controller | |
| 1138 [tabArray_ removeObjectAtIndex:index]; | |
| 1139 } | |
| 1140 | |
| 1141 // Called by the CAAnimation delegate when the tab completes the closing | |
| 1142 // animation. | |
| 1143 - (void)animationDidStopForController:(TabController*)controller | |
| 1144 finished:(BOOL)finished { | |
| 1145 [closingControllers_ removeObject:controller]; | |
| 1146 [self removeTab:controller]; | |
| 1147 } | |
| 1148 | |
| 1149 // Save off which TabController is closing and tell its view's animator | |
| 1150 // where to move the tab to. Registers a delegate to call back when the | |
| 1151 // animation is complete in order to remove the tab from the model. | |
| 1152 - (void)startClosingTabWithAnimation:(TabController*)closingTab { | |
| 1153 DCHECK([NSThread isMainThread]); | |
| 1154 // Save off the controller into the set of animating tabs. This alerts | |
| 1155 // the layout method to not do anything with it and allows us to correctly | |
| 1156 // calculate offsets when working with indices into the model. | |
| 1157 [closingControllers_ addObject:closingTab]; | |
| 1158 | |
| 1159 // Mark the tab as closing. This prevents it from generating any drags or | |
| 1160 // selections while it's animating closed. | |
| 1161 [(TabView*)[closingTab view] setClosing:YES]; | |
| 1162 | |
| 1163 // Register delegate (owned by the animation system). | |
| 1164 NSView* tabView = [closingTab view]; | |
| 1165 CAAnimation* animation = [[tabView animationForKey:@"frameOrigin"] copy]; | |
| 1166 [animation autorelease]; | |
| 1167 scoped_nsobject<TabCloseAnimationDelegate> delegate( | |
| 1168 [[TabCloseAnimationDelegate alloc] initWithTabStrip:self | |
| 1169 tabController:closingTab]); | |
| 1170 [animation setDelegate:delegate.get()]; // Retains delegate. | |
| 1171 NSMutableDictionary* animationDictionary = | |
| 1172 [NSMutableDictionary dictionaryWithDictionary:[tabView animations]]; | |
| 1173 [animationDictionary setObject:animation forKey:@"frameOrigin"]; | |
| 1174 [tabView setAnimations:animationDictionary]; | |
| 1175 | |
| 1176 // Periscope down! Animate the tab. | |
| 1177 NSRect newFrame = [tabView frame]; | |
| 1178 newFrame = NSOffsetRect(newFrame, 0, -newFrame.size.height); | |
| 1179 ScopedNSAnimationContextGroup animationGroup(true); | |
| 1180 animationGroup.SetCurrentContextDuration(kAnimationDuration); | |
| 1181 [[tabView animator] setFrame:newFrame]; | |
| 1182 } | |
| 1183 | |
| 1184 // Called when a notification is received from the model that the given tab | |
| 1185 // has gone away. Start an animation then force a layout to put everything | |
| 1186 // in motion. | |
| 1187 - (void)tabDetachedWithContents:(TabContentsWrapper*)contents | |
| 1188 atIndex:(NSInteger)modelIndex { | |
| 1189 // Take closing tabs into account. | |
| 1190 NSInteger index = [self indexFromModelIndex:modelIndex]; | |
| 1191 | |
| 1192 TabController* tab = [tabArray_ objectAtIndex:index]; | |
| 1193 if (tabStripModel_->count() > 0) { | |
| 1194 [self startClosingTabWithAnimation:tab]; | |
| 1195 [self layoutTabs]; | |
| 1196 } else { | |
| 1197 [self removeTab:tab]; | |
| 1198 } | |
| 1199 | |
| 1200 // Send a broadcast that the number of tabs have changed. | |
| 1201 [[NSNotificationCenter defaultCenter] | |
| 1202 postNotificationName:kTabStripNumberOfTabsChanged | |
| 1203 object:self]; | |
| 1204 | |
| 1205 [delegate_ onTabDetachedWithContents:contents->tab_contents()]; | |
| 1206 } | |
| 1207 | |
| 1208 // A helper routine for creating an NSImageView to hold the fav icon or app icon | |
| 1209 // for |contents|. | |
| 1210 - (NSImageView*)iconImageViewForContents:(TabContents*)contents { | |
| 1211 BOOL isApp = contents->is_app(); | |
| 1212 NSImage* image = nil; | |
| 1213 // Favicons come from the renderer, and the renderer draws everything in the | |
| 1214 // system color space. | |
| 1215 CGColorSpaceRef colorSpace = base::mac::GetSystemColorSpace(); | |
| 1216 if (isApp) { | |
| 1217 SkBitmap* icon = contents->GetExtensionAppIcon(); | |
| 1218 if (icon) | |
| 1219 image = gfx::SkBitmapToNSImageWithColorSpace(*icon, colorSpace); | |
| 1220 } else { | |
| 1221 image = gfx::SkBitmapToNSImageWithColorSpace(contents->GetFavIcon(), | |
| 1222 colorSpace); | |
| 1223 } | |
| 1224 | |
| 1225 // Either we don't have a valid favicon or there was some issue converting it | |
| 1226 // from an SkBitmap. Either way, just show the default. | |
| 1227 if (!image) | |
| 1228 image = defaultFavIcon_.get(); | |
| 1229 NSRect frame = NSMakeRect(0, 0, kIconWidthAndHeight, kIconWidthAndHeight); | |
| 1230 NSImageView* view = [[[NSImageView alloc] initWithFrame:frame] autorelease]; | |
| 1231 [view setImage:image]; | |
| 1232 return view; | |
| 1233 } | |
| 1234 | |
| 1235 // Updates the current loading state, replacing the icon view with a favicon, | |
| 1236 // a throbber, the default icon, or nothing at all. | |
| 1237 - (void)updateFavIconForContents:(TabContents*)contents | |
| 1238 atIndex:(NSInteger)modelIndex { | |
| 1239 if (!contents) | |
| 1240 return; | |
| 1241 | |
| 1242 static NSImage* throbberWaitingImage = | |
| 1243 [ResourceBundle::GetSharedInstance().GetNativeImageNamed( | |
| 1244 IDR_THROBBER_WAITING) retain]; | |
| 1245 static NSImage* throbberLoadingImage = | |
| 1246 [ResourceBundle::GetSharedInstance().GetNativeImageNamed(IDR_THROBBER) | |
| 1247 retain]; | |
| 1248 static NSImage* sadFaviconImage = | |
| 1249 [ResourceBundle::GetSharedInstance().GetNativeImageNamed(IDR_SAD_FAVICON) | |
| 1250 retain]; | |
| 1251 | |
| 1252 // Take closing tabs into account. | |
| 1253 NSInteger index = [self indexFromModelIndex:modelIndex]; | |
| 1254 TabController* tabController = [tabArray_ objectAtIndex:index]; | |
| 1255 | |
| 1256 bool oldHasIcon = [tabController iconView] != nil; | |
| 1257 bool newHasIcon = contents->ShouldDisplayFavIcon() || | |
| 1258 tabStripModel_->IsMiniTab(modelIndex); // Always show icon if mini. | |
| 1259 | |
| 1260 TabLoadingState oldState = [tabController loadingState]; | |
| 1261 TabLoadingState newState = kTabDone; | |
| 1262 NSImage* throbberImage = nil; | |
| 1263 if (contents->is_crashed()) { | |
| 1264 newState = kTabCrashed; | |
| 1265 newHasIcon = true; | |
| 1266 } else if (contents->waiting_for_response()) { | |
| 1267 newState = kTabWaiting; | |
| 1268 throbberImage = throbberWaitingImage; | |
| 1269 } else if (contents->is_loading()) { | |
| 1270 newState = kTabLoading; | |
| 1271 throbberImage = throbberLoadingImage; | |
| 1272 } | |
| 1273 | |
| 1274 if (oldState != newState) | |
| 1275 [tabController setLoadingState:newState]; | |
| 1276 | |
| 1277 // While loading, this function is called repeatedly with the same state. | |
| 1278 // To avoid expensive unnecessary view manipulation, only make changes when | |
| 1279 // the state is actually changing. When loading is complete (kTabDone), | |
| 1280 // every call to this function is significant. | |
| 1281 if (newState == kTabDone || oldState != newState || | |
| 1282 oldHasIcon != newHasIcon) { | |
| 1283 NSView* iconView = nil; | |
| 1284 if (newHasIcon) { | |
| 1285 if (newState == kTabDone) { | |
| 1286 iconView = [self iconImageViewForContents:contents]; | |
| 1287 } else if (newState == kTabCrashed) { | |
| 1288 NSImage* oldImage = [[self iconImageViewForContents:contents] image]; | |
| 1289 NSRect frame = | |
| 1290 NSMakeRect(0, 0, kIconWidthAndHeight, kIconWidthAndHeight); | |
| 1291 iconView = [ThrobberView toastThrobberViewWithFrame:frame | |
| 1292 beforeImage:oldImage | |
| 1293 afterImage:sadFaviconImage]; | |
| 1294 } else { | |
| 1295 NSRect frame = | |
| 1296 NSMakeRect(0, 0, kIconWidthAndHeight, kIconWidthAndHeight); | |
| 1297 iconView = [ThrobberView filmstripThrobberViewWithFrame:frame | |
| 1298 image:throbberImage]; | |
| 1299 } | |
| 1300 } | |
| 1301 | |
| 1302 [tabController setIconView:iconView]; | |
| 1303 } | |
| 1304 } | |
| 1305 | |
| 1306 // Called when a notification is received from the model that the given tab | |
| 1307 // has been updated. |loading| will be YES when we only want to update the | |
| 1308 // throbber state, not anything else about the (partially) loading tab. | |
| 1309 - (void)tabChangedWithContents:(TabContentsWrapper*)contents | |
| 1310 atIndex:(NSInteger)modelIndex | |
| 1311 changeType:(TabStripModelObserver::TabChangeType)change { | |
| 1312 // Take closing tabs into account. | |
| 1313 NSInteger index = [self indexFromModelIndex:modelIndex]; | |
| 1314 | |
| 1315 if (modelIndex == tabStripModel_->selected_index()) | |
| 1316 [delegate_ onSelectedTabChange:change]; | |
| 1317 | |
| 1318 if (change == TabStripModelObserver::TITLE_NOT_LOADING) { | |
| 1319 // TODO(sky): make this work. | |
| 1320 // We'll receive another notification of the change asynchronously. | |
| 1321 return; | |
| 1322 } | |
| 1323 | |
| 1324 TabController* tabController = [tabArray_ objectAtIndex:index]; | |
| 1325 | |
| 1326 if (change != TabStripModelObserver::LOADING_ONLY) | |
| 1327 [self setTabTitle:tabController withContents:contents->tab_contents()]; | |
| 1328 | |
| 1329 [self updateFavIconForContents:contents->tab_contents() atIndex:modelIndex]; | |
| 1330 | |
| 1331 TabContentsController* updatedController = | |
| 1332 [tabContentsArray_ objectAtIndex:index]; | |
| 1333 [updatedController tabDidChange:contents->tab_contents()]; | |
| 1334 } | |
| 1335 | |
| 1336 // Called when a tab is moved (usually by drag&drop). Keep our parallel arrays | |
| 1337 // in sync with the tab strip model. It can also be pinned/unpinned | |
| 1338 // simultaneously, so we need to take care of that. | |
| 1339 - (void)tabMovedWithContents:(TabContentsWrapper*)contents | |
| 1340 fromIndex:(NSInteger)modelFrom | |
| 1341 toIndex:(NSInteger)modelTo { | |
| 1342 // Take closing tabs into account. | |
| 1343 NSInteger from = [self indexFromModelIndex:modelFrom]; | |
| 1344 NSInteger to = [self indexFromModelIndex:modelTo]; | |
| 1345 | |
| 1346 scoped_nsobject<TabContentsController> movedTabContentsController( | |
| 1347 [[tabContentsArray_ objectAtIndex:from] retain]); | |
| 1348 [tabContentsArray_ removeObjectAtIndex:from]; | |
| 1349 [tabContentsArray_ insertObject:movedTabContentsController.get() | |
| 1350 atIndex:to]; | |
| 1351 scoped_nsobject<TabController> movedTabController( | |
| 1352 [[tabArray_ objectAtIndex:from] retain]); | |
| 1353 DCHECK([movedTabController isKindOfClass:[TabController class]]); | |
| 1354 [tabArray_ removeObjectAtIndex:from]; | |
| 1355 [tabArray_ insertObject:movedTabController.get() atIndex:to]; | |
| 1356 | |
| 1357 // The tab moved, which means that the mini-tab state may have changed. | |
| 1358 if (tabStripModel_->IsMiniTab(modelTo) != [movedTabController mini]) | |
| 1359 [self tabMiniStateChangedWithContents:contents atIndex:modelTo]; | |
| 1360 | |
| 1361 [self layoutTabs]; | |
| 1362 } | |
| 1363 | |
| 1364 // Called when a tab is pinned or unpinned without moving. | |
| 1365 - (void)tabMiniStateChangedWithContents:(TabContentsWrapper*)contents | |
| 1366 atIndex:(NSInteger)modelIndex { | |
| 1367 // Take closing tabs into account. | |
| 1368 NSInteger index = [self indexFromModelIndex:modelIndex]; | |
| 1369 | |
| 1370 TabController* tabController = [tabArray_ objectAtIndex:index]; | |
| 1371 DCHECK([tabController isKindOfClass:[TabController class]]); | |
| 1372 | |
| 1373 // Don't do anything if the change was already picked up by the move event. | |
| 1374 if (tabStripModel_->IsMiniTab(modelIndex) == [tabController mini]) | |
| 1375 return; | |
| 1376 | |
| 1377 [tabController setMini:tabStripModel_->IsMiniTab(modelIndex)]; | |
| 1378 [tabController setPinned:tabStripModel_->IsTabPinned(modelIndex)]; | |
| 1379 [tabController setApp:tabStripModel_->IsAppTab(modelIndex)]; | |
| 1380 [self updateFavIconForContents:contents->tab_contents() atIndex:modelIndex]; | |
| 1381 // If the tab is being restored and it's pinned, the mini state is set after | |
| 1382 // the tab has already been rendered, so re-layout the tabstrip. In all other | |
| 1383 // cases, the state is set before the tab is rendered so this isn't needed. | |
| 1384 [self layoutTabs]; | |
| 1385 } | |
| 1386 | |
| 1387 - (void)setFrameOfSelectedTab:(NSRect)frame { | |
| 1388 NSView* view = [self selectedTabView]; | |
| 1389 NSValue* identifier = [NSValue valueWithPointer:view]; | |
| 1390 [targetFrames_ setObject:[NSValue valueWithRect:frame] | |
| 1391 forKey:identifier]; | |
| 1392 [view setFrame:frame]; | |
| 1393 } | |
| 1394 | |
| 1395 - (NSView*)selectedTabView { | |
| 1396 int selectedIndex = tabStripModel_->selected_index(); | |
| 1397 // Take closing tabs into account. They can't ever be selected. | |
| 1398 selectedIndex = [self indexFromModelIndex:selectedIndex]; | |
| 1399 return [self viewAtIndex:selectedIndex]; | |
| 1400 } | |
| 1401 | |
| 1402 // Find the model index based on the x coordinate of the placeholder. If there | |
| 1403 // is no placeholder, this returns the end of the tab strip. Closing tabs are | |
| 1404 // not considered in computing the index. | |
| 1405 - (int)indexOfPlaceholder { | |
| 1406 double placeholderX = placeholderFrame_.origin.x; | |
| 1407 int index = 0; | |
| 1408 int location = 0; | |
| 1409 // Use |tabArray_| here instead of the tab strip count in order to get the | |
| 1410 // correct index when there are closing tabs to the left of the placeholder. | |
| 1411 const int count = [tabArray_ count]; | |
| 1412 while (index < count) { | |
| 1413 // Ignore closing tabs for simplicity. The only drawback of this is that | |
| 1414 // if the placeholder is placed right before one or several contiguous | |
| 1415 // currently closing tabs, the associated TabController will start at the | |
| 1416 // end of the closing tabs. | |
| 1417 if ([closingControllers_ containsObject:[tabArray_ objectAtIndex:index]]) { | |
| 1418 index++; | |
| 1419 continue; | |
| 1420 } | |
| 1421 NSView* curr = [self viewAtIndex:index]; | |
| 1422 // The placeholder tab works by changing the frame of the tab being dragged | |
| 1423 // to be the bounds of the placeholder, so we need to skip it while we're | |
| 1424 // iterating, otherwise we'll end up off by one. Note This only effects | |
| 1425 // dragging to the right, not to the left. | |
| 1426 if (curr == placeholderTab_) { | |
| 1427 index++; | |
| 1428 continue; | |
| 1429 } | |
| 1430 if (placeholderX <= NSMinX([curr frame])) | |
| 1431 break; | |
| 1432 index++; | |
| 1433 location++; | |
| 1434 } | |
| 1435 return location; | |
| 1436 } | |
| 1437 | |
| 1438 // Move the given tab at index |from| in this window to the location of the | |
| 1439 // current placeholder. | |
| 1440 - (void)moveTabFromIndex:(NSInteger)from { | |
| 1441 int toIndex = [self indexOfPlaceholder]; | |
| 1442 tabStripModel_->MoveTabContentsAt(from, toIndex, true); | |
| 1443 } | |
| 1444 | |
| 1445 // Drop a given TabContents at the location of the current placeholder. If there | |
| 1446 // is no placeholder, it will go at the end. Used when dragging from another | |
| 1447 // window when we don't have access to the TabContents as part of our strip. | |
| 1448 // |frame| is in the coordinate system of the tab strip view and represents | |
| 1449 // where the user dropped the new tab so it can be animated into its correct | |
| 1450 // location when the tab is added to the model. If the tab was pinned in its | |
| 1451 // previous window, setting |pinned| to YES will propagate that state to the | |
| 1452 // new window. Mini-tabs are either app or pinned tabs; the app state is stored | |
| 1453 // by the |contents|, but the |pinned| state is the caller's responsibility. | |
| 1454 - (void)dropTabContents:(TabContentsWrapper*)contents | |
| 1455 withFrame:(NSRect)frame | |
| 1456 asPinnedTab:(BOOL)pinned { | |
| 1457 int modelIndex = [self indexOfPlaceholder]; | |
| 1458 | |
| 1459 // Mark that the new tab being created should start at |frame|. It will be | |
| 1460 // reset as soon as the tab has been positioned. | |
| 1461 droppedTabFrame_ = frame; | |
| 1462 | |
| 1463 // Insert it into this tab strip. We want it in the foreground and to not | |
| 1464 // inherit the current tab's group. | |
| 1465 tabStripModel_->InsertTabContentsAt( | |
| 1466 modelIndex, contents, | |
| 1467 TabStripModel::ADD_SELECTED | (pinned ? TabStripModel::ADD_PINNED : 0)); | |
| 1468 } | |
| 1469 | |
| 1470 // Called when the tab strip view changes size. As we only registered for | |
| 1471 // changes on our view, we know it's only for our view. Layout w/out | |
| 1472 // animations since they are blocked by the resize nested runloop. We need | |
| 1473 // the views to adjust immediately. Neither the tabs nor their z-order are | |
| 1474 // changed, so we don't need to update the subviews. | |
| 1475 - (void)tabViewFrameChanged:(NSNotification*)info { | |
| 1476 [self layoutTabsWithAnimation:NO regenerateSubviews:NO]; | |
| 1477 } | |
| 1478 | |
| 1479 // Called when the tracking areas for any given tab are updated. This allows | |
| 1480 // the individual tabs to update their hover states correctly. | |
| 1481 // Only generates the event if the cursor is in the tab strip. | |
| 1482 - (void)tabUpdateTracking:(NSNotification*)notification { | |
| 1483 DCHECK([[notification object] isKindOfClass:[TabView class]]); | |
| 1484 DCHECK(mouseInside_); | |
| 1485 NSWindow* window = [tabStripView_ window]; | |
| 1486 NSPoint location = [window mouseLocationOutsideOfEventStream]; | |
| 1487 if (NSPointInRect(location, [tabStripView_ frame])) { | |
| 1488 NSEvent* mouseEvent = [NSEvent mouseEventWithType:NSMouseMoved | |
| 1489 location:location | |
| 1490 modifierFlags:0 | |
| 1491 timestamp:0 | |
| 1492 windowNumber:[window windowNumber] | |
| 1493 context:nil | |
| 1494 eventNumber:0 | |
| 1495 clickCount:0 | |
| 1496 pressure:0]; | |
| 1497 [self mouseMoved:mouseEvent]; | |
| 1498 } | |
| 1499 } | |
| 1500 | |
| 1501 - (BOOL)inRapidClosureMode { | |
| 1502 return availableResizeWidth_ != kUseFullAvailableWidth; | |
| 1503 } | |
| 1504 | |
| 1505 // Disable tab dragging when there are any pending animations. | |
| 1506 - (BOOL)tabDraggingAllowed { | |
| 1507 return [closingControllers_ count] == 0; | |
| 1508 } | |
| 1509 | |
| 1510 - (void)mouseMoved:(NSEvent*)event { | |
| 1511 // Use hit test to figure out what view we are hovering over. | |
| 1512 NSView* targetView = [tabStripView_ hitTest:[event locationInWindow]]; | |
| 1513 | |
| 1514 // Set the new tab button hover state iff the mouse is over the button. | |
| 1515 BOOL shouldShowHoverImage = [targetView isKindOfClass:[NewTabButton class]]; | |
| 1516 [self setNewTabButtonHoverState:shouldShowHoverImage]; | |
| 1517 | |
| 1518 TabView* tabView = (TabView*)targetView; | |
| 1519 if (![tabView isKindOfClass:[TabView class]]) { | |
| 1520 if ([[tabView superview] isKindOfClass:[TabView class]]) { | |
| 1521 tabView = (TabView*)[targetView superview]; | |
| 1522 } else { | |
| 1523 tabView = nil; | |
| 1524 } | |
| 1525 } | |
| 1526 | |
| 1527 if (hoveredTab_ != tabView) { | |
| 1528 [hoveredTab_ mouseExited:nil]; // We don't pass event because moved events | |
| 1529 [tabView mouseEntered:nil]; // don't have valid tracking areas | |
| 1530 hoveredTab_ = tabView; | |
| 1531 } else { | |
| 1532 [hoveredTab_ mouseMoved:event]; | |
| 1533 } | |
| 1534 } | |
| 1535 | |
| 1536 - (void)mouseEntered:(NSEvent*)event { | |
| 1537 NSTrackingArea* area = [event trackingArea]; | |
| 1538 if ([area isEqual:trackingArea_]) { | |
| 1539 mouseInside_ = YES; | |
| 1540 [self setTabTrackingAreasEnabled:YES]; | |
| 1541 [self mouseMoved:event]; | |
| 1542 } | |
| 1543 } | |
| 1544 | |
| 1545 // Called when the tracking area is in effect which means we're tracking to | |
| 1546 // see if the user leaves the tab strip with their mouse. When they do, | |
| 1547 // reset layout to use all available width. | |
| 1548 - (void)mouseExited:(NSEvent*)event { | |
| 1549 NSTrackingArea* area = [event trackingArea]; | |
| 1550 if ([area isEqual:trackingArea_]) { | |
| 1551 mouseInside_ = NO; | |
| 1552 [self setTabTrackingAreasEnabled:NO]; | |
| 1553 availableResizeWidth_ = kUseFullAvailableWidth; | |
| 1554 [hoveredTab_ mouseExited:event]; | |
| 1555 hoveredTab_ = nil; | |
| 1556 [self layoutTabs]; | |
| 1557 } else if ([area isEqual:newTabTrackingArea_]) { | |
| 1558 // If the mouse is moved quickly enough, it is possible for the mouse to | |
| 1559 // leave the tabstrip without sending any mouseMoved: messages at all. | |
| 1560 // Since this would result in the new tab button incorrectly staying in the | |
| 1561 // hover state, disable the hover image on every mouse exit. | |
| 1562 [self setNewTabButtonHoverState:NO]; | |
| 1563 } | |
| 1564 } | |
| 1565 | |
| 1566 // Enable/Disable the tracking areas for the tabs. They are only enabled | |
| 1567 // when the mouse is in the tabstrip. | |
| 1568 - (void)setTabTrackingAreasEnabled:(BOOL)enabled { | |
| 1569 NSNotificationCenter* defaultCenter = [NSNotificationCenter defaultCenter]; | |
| 1570 for (TabController* controller in tabArray_.get()) { | |
| 1571 TabView* tabView = [controller tabView]; | |
| 1572 if (enabled) { | |
| 1573 // Set self up to observe tabs so hover states will be correct. | |
| 1574 [defaultCenter addObserver:self | |
| 1575 selector:@selector(tabUpdateTracking:) | |
| 1576 name:NSViewDidUpdateTrackingAreasNotification | |
| 1577 object:tabView]; | |
| 1578 } else { | |
| 1579 [defaultCenter removeObserver:self | |
| 1580 name:NSViewDidUpdateTrackingAreasNotification | |
| 1581 object:tabView]; | |
| 1582 } | |
| 1583 [tabView setTrackingEnabled:enabled]; | |
| 1584 } | |
| 1585 } | |
| 1586 | |
| 1587 // Sets the new tab button's image based on the current hover state. Does | |
| 1588 // nothing if the hover state is already correct. | |
| 1589 - (void)setNewTabButtonHoverState:(BOOL)shouldShowHover { | |
| 1590 if (shouldShowHover && !newTabButtonShowingHoverImage_) { | |
| 1591 newTabButtonShowingHoverImage_ = YES; | |
| 1592 [newTabButton_ setImage: | |
| 1593 app::mac::GetCachedImageWithName(kNewTabHoverImage)]; | |
| 1594 } else if (!shouldShowHover && newTabButtonShowingHoverImage_) { | |
| 1595 newTabButtonShowingHoverImage_ = NO; | |
| 1596 [newTabButton_ setImage:app::mac::GetCachedImageWithName(kNewTabImage)]; | |
| 1597 } | |
| 1598 } | |
| 1599 | |
| 1600 // Adds the given subview to (the end of) the list of permanent subviews | |
| 1601 // (specified from bottom up). These subviews will always be below the | |
| 1602 // transitory subviews (tabs). |-regenerateSubviewList| must be called to | |
| 1603 // effectuate the addition. | |
| 1604 - (void)addSubviewToPermanentList:(NSView*)aView { | |
| 1605 if (aView) | |
| 1606 [permanentSubviews_ addObject:aView]; | |
| 1607 } | |
| 1608 | |
| 1609 // Update the subviews, keeping the permanent ones (or, more correctly, putting | |
| 1610 // in the ones listed in permanentSubviews_), and putting in the current tabs in | |
| 1611 // the correct z-order. Any current subviews which is neither in the permanent | |
| 1612 // list nor a (current) tab will be removed. So if you add such a subview, you | |
| 1613 // should call |-addSubviewToPermanentList:| (or better yet, call that and then | |
| 1614 // |-regenerateSubviewList| to actually add it). | |
| 1615 - (void)regenerateSubviewList { | |
| 1616 // Remove self as an observer from all the old tabs before a new set of | |
| 1617 // potentially different tabs is put in place. | |
| 1618 [self setTabTrackingAreasEnabled:NO]; | |
| 1619 | |
| 1620 // Subviews to put in (in bottom-to-top order), beginning with the permanent | |
| 1621 // ones. | |
| 1622 NSMutableArray* subviews = [NSMutableArray arrayWithArray:permanentSubviews_]; | |
| 1623 | |
| 1624 NSView* selectedTabView = nil; | |
| 1625 // Go through tabs in reverse order, since |subviews| is bottom-to-top. | |
| 1626 for (TabController* tab in [tabArray_ reverseObjectEnumerator]) { | |
| 1627 NSView* tabView = [tab view]; | |
| 1628 if ([tab selected]) { | |
| 1629 DCHECK(!selectedTabView); | |
| 1630 selectedTabView = tabView; | |
| 1631 } else { | |
| 1632 [subviews addObject:tabView]; | |
| 1633 } | |
| 1634 } | |
| 1635 if (selectedTabView) { | |
| 1636 [subviews addObject:selectedTabView]; | |
| 1637 } | |
| 1638 [tabStripView_ setSubviews:subviews]; | |
| 1639 [self setTabTrackingAreasEnabled:mouseInside_]; | |
| 1640 } | |
| 1641 | |
| 1642 // Get the index and disposition for a potential URL(s) drop given a point (in | |
| 1643 // the |TabStripView|'s coordinates). It considers only the x-coordinate of the | |
| 1644 // given point. If it's in the "middle" of a tab, it drops on that tab. If it's | |
| 1645 // to the left, it inserts to the left, and similarly for the right. | |
| 1646 - (void)droppingURLsAt:(NSPoint)point | |
| 1647 givesIndex:(NSInteger*)index | |
| 1648 disposition:(WindowOpenDisposition*)disposition { | |
| 1649 // Proportion of the tab which is considered the "middle" (and causes things | |
| 1650 // to drop on that tab). | |
| 1651 const double kMiddleProportion = 0.5; | |
| 1652 const double kLRProportion = (1.0 - kMiddleProportion) / 2.0; | |
| 1653 | |
| 1654 DCHECK(index && disposition); | |
| 1655 NSInteger i = 0; | |
| 1656 for (TabController* tab in tabArray_.get()) { | |
| 1657 NSView* view = [tab view]; | |
| 1658 DCHECK([view isKindOfClass:[TabView class]]); | |
| 1659 | |
| 1660 // Recall that |-[NSView frame]| is in its superview's coordinates, so a | |
| 1661 // |TabView|'s frame is in the coordinates of the |TabStripView| (which | |
| 1662 // matches the coordinate system of |point|). | |
| 1663 NSRect frame = [view frame]; | |
| 1664 | |
| 1665 // Modify the frame to make it "unoverlapped". | |
| 1666 frame.origin.x += kTabOverlap / 2.0; | |
| 1667 frame.size.width -= kTabOverlap; | |
| 1668 if (frame.size.width < 1.0) | |
| 1669 frame.size.width = 1.0; // try to avoid complete failure | |
| 1670 | |
| 1671 // Drop in a new tab to the left of tab |i|? | |
| 1672 if (point.x < (frame.origin.x + kLRProportion * frame.size.width)) { | |
| 1673 *index = i; | |
| 1674 *disposition = NEW_FOREGROUND_TAB; | |
| 1675 return; | |
| 1676 } | |
| 1677 | |
| 1678 // Drop on tab |i|? | |
| 1679 if (point.x <= (frame.origin.x + | |
| 1680 (1.0 - kLRProportion) * frame.size.width)) { | |
| 1681 *index = i; | |
| 1682 *disposition = CURRENT_TAB; | |
| 1683 return; | |
| 1684 } | |
| 1685 | |
| 1686 // (Dropping in a new tab to the right of tab |i| will be taken care of in | |
| 1687 // the next iteration.) | |
| 1688 i++; | |
| 1689 } | |
| 1690 | |
| 1691 // If we've made it here, we want to append a new tab to the end. | |
| 1692 *index = -1; | |
| 1693 *disposition = NEW_FOREGROUND_TAB; | |
| 1694 } | |
| 1695 | |
| 1696 - (void)openURL:(GURL*)url inView:(NSView*)view at:(NSPoint)point { | |
| 1697 // Get the index and disposition. | |
| 1698 NSInteger index; | |
| 1699 WindowOpenDisposition disposition; | |
| 1700 [self droppingURLsAt:point | |
| 1701 givesIndex:&index | |
| 1702 disposition:&disposition]; | |
| 1703 | |
| 1704 // Either insert a new tab or open in a current tab. | |
| 1705 switch (disposition) { | |
| 1706 case NEW_FOREGROUND_TAB: { | |
| 1707 UserMetrics::RecordAction(UserMetricsAction("Tab_DropURLBetweenTabs"), | |
| 1708 browser_->profile()); | |
| 1709 browser::NavigateParams params(browser_, *url, PageTransition::TYPED); | |
| 1710 params.disposition = disposition; | |
| 1711 params.tabstrip_index = index; | |
| 1712 params.tabstrip_add_types = | |
| 1713 TabStripModel::ADD_SELECTED | TabStripModel::ADD_FORCE_INDEX; | |
| 1714 browser::Navigate(¶ms); | |
| 1715 break; | |
| 1716 } | |
| 1717 case CURRENT_TAB: | |
| 1718 UserMetrics::RecordAction(UserMetricsAction("Tab_DropURLOnTab"), | |
| 1719 browser_->profile()); | |
| 1720 tabStripModel_->GetTabContentsAt(index) | |
| 1721 ->tab_contents()->OpenURL(*url, GURL(), CURRENT_TAB, | |
| 1722 PageTransition::TYPED); | |
| 1723 tabStripModel_->SelectTabContentsAt(index, true); | |
| 1724 break; | |
| 1725 default: | |
| 1726 NOTIMPLEMENTED(); | |
| 1727 } | |
| 1728 } | |
| 1729 | |
| 1730 // (URLDropTargetController protocol) | |
| 1731 - (void)dropURLs:(NSArray*)urls inView:(NSView*)view at:(NSPoint)point { | |
| 1732 DCHECK_EQ(view, tabStripView_.get()); | |
| 1733 | |
| 1734 if ([urls count] < 1) { | |
| 1735 NOTREACHED(); | |
| 1736 return; | |
| 1737 } | |
| 1738 | |
| 1739 //TODO(viettrungluu): dropping multiple URLs. | |
| 1740 if ([urls count] > 1) | |
| 1741 NOTIMPLEMENTED(); | |
| 1742 | |
| 1743 // Get the first URL and fix it up. | |
| 1744 GURL url(GURL(URLFixerUpper::FixupURL( | |
| 1745 base::SysNSStringToUTF8([urls objectAtIndex:0]), std::string()))); | |
| 1746 | |
| 1747 [self openURL:&url inView:view at:point]; | |
| 1748 } | |
| 1749 | |
| 1750 // (URLDropTargetController protocol) | |
| 1751 - (void)dropText:(NSString*)text inView:(NSView*)view at:(NSPoint)point { | |
| 1752 DCHECK_EQ(view, tabStripView_.get()); | |
| 1753 | |
| 1754 // If the input is plain text, classify the input and make the URL. | |
| 1755 AutocompleteMatch match; | |
| 1756 browser_->profile()->GetAutocompleteClassifier()->Classify( | |
| 1757 base::SysNSStringToWide(text), | |
| 1758 std::wstring(), false, &match, NULL); | |
| 1759 GURL url(match.destination_url); | |
| 1760 | |
| 1761 [self openURL:&url inView:view at:point]; | |
| 1762 } | |
| 1763 | |
| 1764 // (URLDropTargetController protocol) | |
| 1765 - (void)indicateDropURLsInView:(NSView*)view at:(NSPoint)point { | |
| 1766 DCHECK_EQ(view, tabStripView_.get()); | |
| 1767 | |
| 1768 // The minimum y-coordinate at which one should consider place the arrow. | |
| 1769 const CGFloat arrowBaseY = 25; | |
| 1770 | |
| 1771 NSInteger index; | |
| 1772 WindowOpenDisposition disposition; | |
| 1773 [self droppingURLsAt:point | |
| 1774 givesIndex:&index | |
| 1775 disposition:&disposition]; | |
| 1776 | |
| 1777 NSPoint arrowPos = NSMakePoint(0, arrowBaseY); | |
| 1778 if (index == -1) { | |
| 1779 // Append a tab at the end. | |
| 1780 DCHECK(disposition == NEW_FOREGROUND_TAB); | |
| 1781 NSInteger lastIndex = [tabArray_ count] - 1; | |
| 1782 NSRect overRect = [[[tabArray_ objectAtIndex:lastIndex] view] frame]; | |
| 1783 arrowPos.x = overRect.origin.x + overRect.size.width - kTabOverlap / 2.0; | |
| 1784 } else { | |
| 1785 NSRect overRect = [[[tabArray_ objectAtIndex:index] view] frame]; | |
| 1786 switch (disposition) { | |
| 1787 case NEW_FOREGROUND_TAB: | |
| 1788 // Insert tab (to the left of the given tab). | |
| 1789 arrowPos.x = overRect.origin.x + kTabOverlap / 2.0; | |
| 1790 break; | |
| 1791 case CURRENT_TAB: | |
| 1792 // Overwrite the given tab. | |
| 1793 arrowPos.x = overRect.origin.x + overRect.size.width / 2.0; | |
| 1794 break; | |
| 1795 default: | |
| 1796 NOTREACHED(); | |
| 1797 } | |
| 1798 } | |
| 1799 | |
| 1800 [tabStripView_ setDropArrowPosition:arrowPos]; | |
| 1801 [tabStripView_ setDropArrowShown:YES]; | |
| 1802 [tabStripView_ setNeedsDisplay:YES]; | |
| 1803 } | |
| 1804 | |
| 1805 // (URLDropTargetController protocol) | |
| 1806 - (void)hideDropURLsIndicatorInView:(NSView*)view { | |
| 1807 DCHECK_EQ(view, tabStripView_.get()); | |
| 1808 | |
| 1809 if ([tabStripView_ dropArrowShown]) { | |
| 1810 [tabStripView_ setDropArrowShown:NO]; | |
| 1811 [tabStripView_ setNeedsDisplay:YES]; | |
| 1812 } | |
| 1813 } | |
| 1814 | |
| 1815 - (GTMWindowSheetController*)sheetController { | |
| 1816 if (!sheetController_.get()) | |
| 1817 sheetController_.reset([[GTMWindowSheetController alloc] | |
| 1818 initWithWindow:[switchView_ window] delegate:self]); | |
| 1819 return sheetController_.get(); | |
| 1820 } | |
| 1821 | |
| 1822 - (void)destroySheetController { | |
| 1823 // Make sure there are no open sheets. | |
| 1824 DCHECK_EQ(0U, [[sheetController_ viewsWithAttachedSheets] count]); | |
| 1825 sheetController_.reset(); | |
| 1826 } | |
| 1827 | |
| 1828 // TabContentsControllerDelegate protocol. | |
| 1829 - (void)tabContentsViewFrameWillChange:(TabContentsController*)source | |
| 1830 frameRect:(NSRect)frameRect { | |
| 1831 id<TabContentsControllerDelegate> controller = | |
| 1832 [[switchView_ window] windowController]; | |
| 1833 [controller tabContentsViewFrameWillChange:source frameRect:frameRect]; | |
| 1834 } | |
| 1835 | |
| 1836 - (TabContentsController*)activeTabContentsController { | |
| 1837 int modelIndex = tabStripModel_->selected_index(); | |
| 1838 if (modelIndex < 0) | |
| 1839 return nil; | |
| 1840 NSInteger index = [self indexFromModelIndex:modelIndex]; | |
| 1841 if (index < 0 || | |
| 1842 index >= (NSInteger)[tabContentsArray_ count]) | |
| 1843 return nil; | |
| 1844 return [tabContentsArray_ objectAtIndex:index]; | |
| 1845 } | |
| 1846 | |
| 1847 - (void)gtm_systemRequestsVisibilityForView:(NSView*)view { | |
| 1848 // This implementation is required by GTMWindowSheetController. | |
| 1849 | |
| 1850 // Raise window... | |
| 1851 [[switchView_ window] makeKeyAndOrderFront:self]; | |
| 1852 | |
| 1853 // ...and raise a tab with a sheet. | |
| 1854 NSInteger index = [self modelIndexForContentsView:view]; | |
| 1855 DCHECK(index >= 0); | |
| 1856 if (index >= 0) | |
| 1857 tabStripModel_->SelectTabContentsAt(index, false /* not a user gesture */); | |
| 1858 } | |
| 1859 | |
| 1860 - (void)attachConstrainedWindow:(ConstrainedWindowMac*)window { | |
| 1861 // TODO(thakis, avi): Figure out how to make this work when tabs are dragged | |
| 1862 // out or if fullscreen mode is toggled. | |
| 1863 | |
| 1864 // View hierarchy of the contents view: | |
| 1865 // NSView -- switchView, same for all tabs | |
| 1866 // +- NSView -- TabContentsController's view | |
| 1867 // +- TabContentsViewCocoa | |
| 1868 // Changing it? Do not forget to modify removeConstrainedWindow too. | |
| 1869 // We use the TabContentsController's view in |swapInTabAtIndex|, so we have | |
| 1870 // to pass it to the sheet controller here. | |
| 1871 NSView* tabContentsView = [window->owner()->GetNativeView() superview]; | |
| 1872 window->delegate()->RunSheet([self sheetController], tabContentsView); | |
| 1873 | |
| 1874 // TODO(avi, thakis): GTMWindowSheetController has no api to move tabsheets | |
| 1875 // between windows. Until then, we have to prevent having to move a tabsheet | |
| 1876 // between windows, e.g. no tearing off of tabs. | |
| 1877 NSInteger modelIndex = [self modelIndexForContentsView:tabContentsView]; | |
| 1878 NSInteger index = [self indexFromModelIndex:modelIndex]; | |
| 1879 BrowserWindowController* controller = | |
| 1880 (BrowserWindowController*)[[switchView_ window] windowController]; | |
| 1881 DCHECK(controller != nil); | |
| 1882 DCHECK(index >= 0); | |
| 1883 if (index >= 0) { | |
| 1884 [controller setTab:[self viewAtIndex:index] isDraggable:NO]; | |
| 1885 } | |
| 1886 } | |
| 1887 | |
| 1888 - (void)removeConstrainedWindow:(ConstrainedWindowMac*)window { | |
| 1889 NSView* tabContentsView = [window->owner()->GetNativeView() superview]; | |
| 1890 | |
| 1891 // TODO(avi, thakis): GTMWindowSheetController has no api to move tabsheets | |
| 1892 // between windows. Until then, we have to prevent having to move a tabsheet | |
| 1893 // between windows, e.g. no tearing off of tabs. | |
| 1894 NSInteger modelIndex = [self modelIndexForContentsView:tabContentsView]; | |
| 1895 NSInteger index = [self indexFromModelIndex:modelIndex]; | |
| 1896 BrowserWindowController* controller = | |
| 1897 (BrowserWindowController*)[[switchView_ window] windowController]; | |
| 1898 DCHECK(index >= 0); | |
| 1899 if (index >= 0) { | |
| 1900 [controller setTab:[self viewAtIndex:index] isDraggable:YES]; | |
| 1901 } | |
| 1902 } | |
| 1903 | |
| 1904 @end | |
| OLD | NEW |