Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #import "chrome/browser/cocoa/tab_strip_controller.h" | 5 #import "chrome/browser/cocoa/tab_strip_controller.h" |
| 6 | 6 |
| 7 #include "app/l10n_util.h" | 7 #include "app/l10n_util.h" |
| 8 #include "base/mac_util.h" | 8 #include "base/mac_util.h" |
| 9 #include "base/sys_string_conversions.h" | 9 #include "base/sys_string_conversions.h" |
| 10 #include "chrome/app/chrome_dll_resource.h" | 10 #include "chrome/app/chrome_dll_resource.h" |
| (...skipping 11 matching lines...) Expand all Loading... | |
| 22 #include "chrome/browser/tab_contents/navigation_controller.h" | 22 #include "chrome/browser/tab_contents/navigation_controller.h" |
| 23 #include "chrome/browser/tab_contents/navigation_entry.h" | 23 #include "chrome/browser/tab_contents/navigation_entry.h" |
| 24 #include "chrome/browser/tab_contents/tab_contents.h" | 24 #include "chrome/browser/tab_contents/tab_contents.h" |
| 25 #include "chrome/browser/tab_contents/tab_contents_view.h" | 25 #include "chrome/browser/tab_contents/tab_contents_view.h" |
| 26 #include "chrome/browser/tabs/tab_strip_model.h" | 26 #include "chrome/browser/tabs/tab_strip_model.h" |
| 27 #include "grit/generated_resources.h" | 27 #include "grit/generated_resources.h" |
| 28 #include "skia/ext/skia_utils_mac.h" | 28 #include "skia/ext/skia_utils_mac.h" |
| 29 | 29 |
| 30 NSString* const kTabStripNumberOfTabsChanged = @"kTabStripNumberOfTabsChanged"; | 30 NSString* const kTabStripNumberOfTabsChanged = @"kTabStripNumberOfTabsChanged"; |
| 31 | 31 |
| 32 // A value to indicate tab layout should use the full available width of the | |
| 33 // view. | |
| 34 static const float kUseFullAvailableWidth = -1.0; | |
| 35 | |
| 32 // A simple view class that prevents the windowserver from dragging the | 36 // A simple view class that prevents the windowserver from dragging the |
| 33 // area behind tabs. Sometimes core animation confuses it. | 37 // area behind tabs. Sometimes core animation confuses it. |
| 34 @interface TabStripControllerDragBlockingView : NSView | 38 @interface TabStripControllerDragBlockingView : NSView |
| 35 @end | 39 @end |
| 36 @implementation TabStripControllerDragBlockingView | 40 @implementation TabStripControllerDragBlockingView |
| 37 - (BOOL)mouseDownCanMoveWindow {return NO;} | 41 - (BOOL)mouseDownCanMoveWindow {return NO;} |
| 38 - (void)drawRect:(NSRect)rect {} | 42 - (void)drawRect:(NSRect)rect {} |
| 39 @end | 43 @end |
| 40 | 44 |
| 45 @interface TabStripController(Private) | |
| 46 - (void)installTrackingArea; | |
| 47 - (BOOL)useFullWidthForLayout; | |
| 48 @end | |
| 49 | |
| 41 @implementation TabStripController | 50 @implementation TabStripController |
| 42 | 51 |
| 43 - (id)initWithView:(TabStripView*)view | 52 - (id)initWithView:(TabStripView*)view |
| 44 switchView:(NSView*)switchView | 53 switchView:(NSView*)switchView |
| 45 model:(TabStripModel*)model { | 54 model:(TabStripModel*)model { |
| 46 DCHECK(view && switchView && model); | 55 DCHECK(view && switchView && model); |
| 47 if ((self = [super init])) { | 56 if ((self = [super init])) { |
| 48 tabView_ = view; | 57 tabView_ = view; |
| 49 switchView_ = switchView; | 58 switchView_ = switchView; |
| 50 tabModel_ = model; | 59 tabModel_ = model; |
| 51 bridge_.reset(new TabStripModelObserverBridge(tabModel_, self)); | 60 bridge_.reset(new TabStripModelObserverBridge(tabModel_, self)); |
| 52 tabContentsArray_.reset([[NSMutableArray alloc] init]); | 61 tabContentsArray_.reset([[NSMutableArray alloc] init]); |
| 53 tabArray_.reset([[NSMutableArray alloc] init]); | 62 tabArray_.reset([[NSMutableArray alloc] init]); |
| 54 // Take the only child view present in the nib as the new tab button. For | 63 // Take the only child view present in the nib as the new tab button. For |
| 55 // some reason, if the view is present in the nib apriori, it draws | 64 // some reason, if the view is present in the nib apriori, it draws |
| 56 // correctly. If we create it in code and add it to the tab view, it draws | 65 // correctly. If we create it in code and add it to the tab view, it draws |
| 57 // with all sorts of crazy artifacts. | 66 // with all sorts of crazy artifacts. |
| 58 newTabButton_ = [[tabView_ subviews] objectAtIndex:0]; | 67 newTabButton_ = [[tabView_ subviews] objectAtIndex:0]; |
| 59 DCHECK([newTabButton_ isKindOfClass:[NSButton class]]); | 68 DCHECK([newTabButton_ isKindOfClass:[NSButton class]]); |
| 60 [newTabButton_ setTarget:nil]; | 69 [newTabButton_ setTarget:nil]; |
| 61 [newTabButton_ setAction:@selector(commandDispatch:)]; | 70 [newTabButton_ setAction:@selector(commandDispatch:)]; |
| 62 [newTabButton_ setTag:IDC_NEW_TAB]; | 71 [newTabButton_ setTag:IDC_NEW_TAB]; |
| 63 targetFrames_.reset([[NSMutableDictionary alloc] init]); | 72 targetFrames_.reset([[NSMutableDictionary alloc] init]); |
| 64 [tabView_ setWantsLayer:YES]; | 73 [tabView_ setWantsLayer:YES]; |
| 65 dragBlockingView_.reset([[TabStripControllerDragBlockingView alloc] | 74 dragBlockingView_.reset([[TabStripControllerDragBlockingView alloc] |
| 66 initWithFrame:NSZeroRect]); | 75 initWithFrame:NSZeroRect]); |
| 67 [view addSubview:dragBlockingView_]; | 76 [view addSubview:dragBlockingView_]; |
| 68 newTabTargetFrame_ = NSMakeRect(0, 0, 0, 0); | 77 newTabTargetFrame_ = NSMakeRect(0, 0, 0, 0); |
| 78 availableResizeWidth_ = kUseFullAvailableWidth; | |
| 69 | 79 |
| 70 // Watch for notifications that the tab strip view has changed size so | 80 // Watch for notifications that the tab strip view has changed size so |
| 71 // we can tell it to layout for the new size. | 81 // we can tell it to layout for the new size. |
| 72 [[NSNotificationCenter defaultCenter] | 82 [[NSNotificationCenter defaultCenter] |
| 73 addObserver:self | 83 addObserver:self |
| 74 selector:@selector(tabViewFrameChanged:) | 84 selector:@selector(tabViewFrameChanged:) |
| 75 name:NSViewFrameDidChangeNotification | 85 name:NSViewFrameDidChangeNotification |
| 76 object:tabView_]; | 86 object:tabView_]; |
| 77 } | 87 } |
| 78 return self; | 88 return self; |
| 79 } | 89 } |
| 80 | 90 |
| 81 - (void)dealloc { | 91 - (void)dealloc { |
| 92 [tabView_ removeTrackingArea:closeTabTrackingArea_.get()]; | |
| 82 [[NSNotificationCenter defaultCenter] removeObserver:self]; | 93 [[NSNotificationCenter defaultCenter] removeObserver:self]; |
| 83 [super dealloc]; | 94 [super dealloc]; |
| 84 } | 95 } |
| 85 | 96 |
| 86 + (CGFloat)defaultTabHeight { | 97 + (CGFloat)defaultTabHeight { |
| 87 return 24.0; | 98 return 24.0; |
| 88 } | 99 } |
| 89 | 100 |
| 90 // Finds the associated TabContentsController at the given |index| and swaps | 101 // Finds the associated TabContentsController at the given |index| and swaps |
| 91 // out the sole child of the contentArea to display its contents. | 102 // out the sole child of the contentArea to display its contents. |
| (...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 150 // get the associated view. Returns nil if out of range. | 161 // get the associated view. Returns nil if out of range. |
| 151 - (NSView*)viewAtIndex:(NSUInteger)index { | 162 - (NSView*)viewAtIndex:(NSUInteger)index { |
| 152 if (index >= [tabArray_ count]) | 163 if (index >= [tabArray_ count]) |
| 153 return NULL; | 164 return NULL; |
| 154 return [[tabArray_ objectAtIndex:index] view]; | 165 return [[tabArray_ objectAtIndex:index] view]; |
| 155 } | 166 } |
| 156 | 167 |
| 157 // Called when the user clicks a tab. Tell the model the selection has changed, | 168 // Called when the user clicks a tab. Tell the model the selection has changed, |
| 158 // which feeds back into us via a notification. | 169 // which feeds back into us via a notification. |
| 159 - (void)selectTab:(id)sender { | 170 - (void)selectTab:(id)sender { |
| 171 DCHECK([sender isKindOfClass:[NSView class]]); | |
| 160 int index = [self indexForTabView:sender]; | 172 int index = [self indexForTabView:sender]; |
| 161 if (index >= 0 && tabModel_->ContainsIndex(index)) | 173 if (index >= 0 && tabModel_->ContainsIndex(index)) |
| 162 tabModel_->SelectTabContentsAt(index, true); | 174 tabModel_->SelectTabContentsAt(index, true); |
| 163 } | 175 } |
| 164 | 176 |
| 165 // Called when the user closes a tab. Asks the model to close the tab. | 177 // Called when the user closes a tab. Asks the model to close the tab. |sender| |
| 178 // is the TabView that is potentially going away. | |
| 166 - (void)closeTab:(id)sender { | 179 - (void)closeTab:(id)sender { |
| 180 DCHECK([sender isKindOfClass:[NSView class]]); | |
| 167 int index = [self indexForTabView:sender]; | 181 int index = [self indexForTabView:sender]; |
| 168 if (tabModel_->ContainsIndex(index)) { | 182 if (tabModel_->ContainsIndex(index)) { |
| 169 TabContents* contents = tabModel_->GetTabContentsAt(index); | 183 TabContents* contents = tabModel_->GetTabContentsAt(index); |
| 170 if (contents) | 184 if (contents) |
| 171 UserMetrics::RecordAction(L"CloseTab_Mouse", contents->profile()); | 185 UserMetrics::RecordAction(L"CloseTab_Mouse", contents->profile()); |
| 172 if ([self numberOfTabViews] > 1) { | 186 if ([self numberOfTabViews] > 1) { |
| 187 // Limit the width available for laying out tabs so that tabs are not | |
| 188 // resized until a later time (when the mouse leaves the tab strip). | |
| 189 NSView* penultimateTab = [self viewAtIndex:[tabArray_ count] - 2]; | |
|
rohitrao (ping after 24h)
2009/07/24 20:35:57
Will we have to revisit this once we implement rea
| |
| 190 availableResizeWidth_ = NSMaxX([penultimateTab frame]); | |
| 191 [self installTrackingArea]; | |
| 173 tabModel_->CloseTabContentsAt(index); | 192 tabModel_->CloseTabContentsAt(index); |
| 174 } else { | 193 } else { |
| 175 // Use the standard window close if this is the last tab | 194 // Use the standard window close if this is the last tab |
| 176 // this prevents the tab from being removed from the model until after | 195 // this prevents the tab from being removed from the model until after |
| 177 // the window dissapears | 196 // the window dissapears |
| 178 [[tabView_ window] performClose:nil]; | 197 [[tabView_ window] performClose:nil]; |
| 179 } | 198 } |
| 180 } | 199 } |
| 181 } | 200 } |
| 182 | 201 |
| (...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 218 const float kTabOverlap = 20.0; | 237 const float kTabOverlap = 20.0; |
| 219 const float kNewTabButtonOffset = 8.0; | 238 const float kNewTabButtonOffset = 8.0; |
| 220 const float kMaxTabWidth = [TabController maxTabWidth]; | 239 const float kMaxTabWidth = [TabController maxTabWidth]; |
| 221 const float kMinTabWidth = [TabController minTabWidth]; | 240 const float kMinTabWidth = [TabController minTabWidth]; |
| 222 const float kMinSelectedTabWidth = [TabController minSelectedTabWidth]; | 241 const float kMinSelectedTabWidth = [TabController minSelectedTabWidth]; |
| 223 | 242 |
| 224 NSRect enclosingRect = NSZeroRect; | 243 NSRect enclosingRect = NSZeroRect; |
| 225 [NSAnimationContext beginGrouping]; | 244 [NSAnimationContext beginGrouping]; |
| 226 [[NSAnimationContext currentContext] setDuration:0.2]; | 245 [[NSAnimationContext currentContext] setDuration:0.2]; |
| 227 | 246 |
| 228 // Compute the base width of tabs given how much size we have available. | 247 // Compute the base width of tabs given how much room we're allowed. We |
| 229 float availableWidth = | 248 // may not be able to use the entire width if the user is quickly closing |
| 230 NSWidth([tabView_ frame]) - NSWidth([newTabButton_ frame]) - | 249 // tabs. |
| 231 kNewTabButtonOffset - kIndentLeavingSpaceForControls; | 250 float availableWidth = 0; |
| 251 if ([self useFullWidthForLayout]) { | |
| 252 availableWidth = NSWidth([tabView_ frame]); | |
| 253 availableWidth -= NSWidth([newTabButton_ frame]) + kNewTabButtonOffset; | |
| 254 } else { | |
| 255 availableWidth = availableResizeWidth_; | |
| 256 } | |
| 257 availableWidth -= kIndentLeavingSpaceForControls; | |
| 258 | |
| 232 // Add back in the amount we "get back" from the tabs overlapping. | 259 // Add back in the amount we "get back" from the tabs overlapping. |
| 233 availableWidth += [tabContentsArray_ count] * kTabOverlap; | 260 availableWidth += ([tabContentsArray_ count] - 1) * kTabOverlap; |
| 234 const float baseTabWidth = | 261 const float baseTabWidth = |
| 235 MAX(MIN(availableWidth / [tabContentsArray_ count], | 262 MAX(MIN(availableWidth / [tabContentsArray_ count], |
| 236 kMaxTabWidth), | 263 kMaxTabWidth), |
| 237 kMinTabWidth); | 264 kMinTabWidth); |
| 238 | 265 |
| 239 CGFloat minX = NSMinX(placeholderFrame_); | 266 CGFloat minX = NSMinX(placeholderFrame_); |
| 240 BOOL visible = [[tabView_ window] isVisible]; | 267 BOOL visible = [[tabView_ window] isVisible]; |
| 241 | 268 |
| 242 float offset = kIndentLeavingSpaceForControls; | 269 float offset = kIndentLeavingSpaceForControls; |
| 243 NSUInteger i = 0; | 270 NSUInteger i = 0; |
| (...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 301 NSValue *oldTargetValue = [targetFrames_ objectForKey:identifier]; | 328 NSValue *oldTargetValue = [targetFrames_ objectForKey:identifier]; |
| 302 if (!oldTargetValue || | 329 if (!oldTargetValue || |
| 303 !NSEqualRects([oldTargetValue rectValue], tabFrame)) { | 330 !NSEqualRects([oldTargetValue rectValue], tabFrame)) { |
| 304 [frameTarget setFrame:tabFrame]; | 331 [frameTarget setFrame:tabFrame]; |
| 305 [targetFrames_ setObject:[NSValue valueWithRect:tabFrame] | 332 [targetFrames_ setObject:[NSValue valueWithRect:tabFrame] |
| 306 forKey:identifier]; | 333 forKey:identifier]; |
| 307 } | 334 } |
| 308 enclosingRect = NSUnionRect(tabFrame, enclosingRect); | 335 enclosingRect = NSUnionRect(tabFrame, enclosingRect); |
| 309 } | 336 } |
| 310 | 337 |
| 338 #if 0 | |
| 311 // Ensure the current tab is "below" the tab before it in z-order so that | 339 // Ensure the current tab is "below" the tab before it in z-order so that |
| 312 // all the tab overlaps are consistent. The selected tab is always the | 340 // all the tab overlaps are consistent. The selected tab is always the |
| 313 // frontmost, but it's already been made frontmost when the tab was selected | 341 // frontmost, but it's already been made frontmost when the tab was selected |
| 314 // so we don't need to do anything about it here. It will get put back into | 342 // so we don't need to do anything about it here. It will get put back into |
| 315 // place when another tab is selected. | 343 // place when another tab is selected. |
| 344 // TODO(pinkerton): this doesn't seem to work in the case where a tab | |
| 345 // is opened between existing tabs. Disabling. | |
| 316 if (![tab selected]) { | 346 if (![tab selected]) { |
| 317 [tabView_ addSubview:[tab view] | 347 [tabView_ addSubview:[tab view] |
| 318 positioned:NSWindowBelow | 348 positioned:NSWindowBelow |
| 319 relativeTo:previousTab]; | 349 relativeTo:previousTab]; |
| 320 } | 350 } |
| 351 #endif | |
| 321 previousTab = [tab view]; | 352 previousTab = [tab view]; |
| 322 | 353 |
| 323 offset += NSWidth(tabFrame); | 354 offset += NSWidth(tabFrame); |
| 324 offset -= kTabOverlap; | 355 offset -= kTabOverlap; |
| 325 i++; | 356 i++; |
| 326 } | 357 } |
| 327 | 358 |
| 328 NSRect newTabNewFrame = [newTabButton_ frame]; | 359 NSRect newTabNewFrame = [newTabButton_ frame]; |
| 329 newTabNewFrame.origin = | 360 if ([self useFullWidthForLayout]) |
| 330 NSMakePoint(MIN(availableWidth, offset + kNewTabButtonOffset), 0); | 361 newTabNewFrame.origin = |
| 362 NSMakePoint(MIN(availableWidth, offset + kNewTabButtonOffset), 0); | |
| 363 else | |
| 364 newTabNewFrame.origin = NSMakePoint(offset + kNewTabButtonOffset, 0); | |
| 331 newTabNewFrame.origin.x = MAX(newTabNewFrame.origin.x, | 365 newTabNewFrame.origin.x = MAX(newTabNewFrame.origin.x, |
| 332 NSMaxX(placeholderFrame_)); | 366 NSMaxX(placeholderFrame_)); |
| 333 if (i > 0 && [newTabButton_ isHidden]) { | 367 if (i > 0 && [newTabButton_ isHidden]) { |
| 334 id target = animate ? [newTabButton_ animator] : newTabButton_; | 368 id target = animate ? [newTabButton_ animator] : newTabButton_; |
| 335 [target setHidden:NO]; | 369 [target setHidden:NO]; |
| 336 } | 370 } |
| 337 | 371 |
| 338 if (!NSEqualRects(newTabTargetFrame_, newTabNewFrame)) { | 372 if (!NSEqualRects(newTabTargetFrame_, newTabNewFrame)) { |
| 339 [newTabButton_ setFrame:newTabNewFrame]; | 373 [newTabButton_ setFrame:newTabNewFrame]; |
| 340 newTabTargetFrame_ = newTabNewFrame; | 374 newTabTargetFrame_ = newTabNewFrame; |
| (...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 394 // a little better that just sliding over (maybe?). | 428 // a little better that just sliding over (maybe?). |
| 395 [newView setFrame:NSOffsetRect([newView frame], | 429 [newView setFrame:NSOffsetRect([newView frame], |
| 396 0, -[[self class] defaultTabHeight])]; | 430 0, -[[self class] defaultTabHeight])]; |
| 397 | 431 |
| 398 [tabView_ addSubview:newView | 432 [tabView_ addSubview:newView |
| 399 positioned:inForeground ? NSWindowAbove : NSWindowBelow | 433 positioned:inForeground ? NSWindowAbove : NSWindowBelow |
| 400 relativeTo:nil]; | 434 relativeTo:nil]; |
| 401 | 435 |
| 402 [self setTabTitle:newController withContents:contents]; | 436 [self setTabTitle:newController withContents:contents]; |
| 403 | 437 |
| 438 // If a tab is being inserted, we can again use the entire tab strip width | |
| 439 // for layout. | |
| 440 availableResizeWidth_ = kUseFullAvailableWidth; | |
| 441 | |
| 404 // We don't need to call |-layoutTabs| if the tab will be in the foreground | 442 // We don't need to call |-layoutTabs| if the tab will be in the foreground |
| 405 // because it will get called when the new tab is selected by the tab model. | 443 // because it will get called when the new tab is selected by the tab model. |
| 406 if (!inForeground) { | 444 if (!inForeground) { |
| 407 [self layoutTabs]; | 445 [self layoutTabs]; |
| 408 } | 446 } |
| 409 | 447 |
| 410 // Send a broadcast that the number of tabs have changed. | 448 // Send a broadcast that the number of tabs have changed. |
| 411 [[NSNotificationCenter defaultCenter] | 449 [[NSNotificationCenter defaultCenter] |
| 412 postNotificationName:kTabStripNumberOfTabsChanged | 450 postNotificationName:kTabStripNumberOfTabsChanged |
| 413 object:self]; | 451 object:self]; |
| (...skipping 233 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 647 } | 685 } |
| 648 | 686 |
| 649 // Called when the tab strip view changes size. As we only registered for | 687 // Called when the tab strip view changes size. As we only registered for |
| 650 // changes on our view, we know it's only for our view. Layout w/out | 688 // changes on our view, we know it's only for our view. Layout w/out |
| 651 // animations since they are blocked by the resize nested runloop. We need | 689 // animations since they are blocked by the resize nested runloop. We need |
| 652 // the views to adjust immediately. | 690 // the views to adjust immediately. |
| 653 - (void)tabViewFrameChanged:(NSNotification*)info { | 691 - (void)tabViewFrameChanged:(NSNotification*)info { |
| 654 [self layoutTabsWithAnimation:NO]; | 692 [self layoutTabsWithAnimation:NO]; |
| 655 } | 693 } |
| 656 | 694 |
| 695 - (BOOL)useFullWidthForLayout { | |
| 696 return availableResizeWidth_ == kUseFullAvailableWidth; | |
| 697 } | |
| 698 | |
| 699 // Call to install a tracking area that reports mouseEnter/Exit messages so | |
| 700 // we can track when the mouse leaves the tab view after closing a tab with | |
| 701 // the mouse. Don't install another tracking rect if one is already there. | |
| 702 - (void)installTrackingArea { | |
| 703 if (closeTabTrackingArea_.get()) | |
| 704 return; | |
| 705 // Note that we pass |NSTrackingInVisibleRect| so the rect is actually | |
| 706 // ignored. | |
| 707 closeTabTrackingArea_.reset([[NSTrackingArea alloc] | |
| 708 initWithRect:[tabView_ bounds] | |
| 709 options:NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways | | |
| 710 NSTrackingInVisibleRect | |
| 711 owner:self | |
| 712 userInfo:nil]); | |
| 713 [tabView_ addTrackingArea:closeTabTrackingArea_.get()]; | |
| 714 } | |
| 715 | |
| 716 - (void)mouseEntered:(NSEvent*)event { | |
| 717 // Do nothing. | |
| 718 } | |
| 719 | |
| 720 // Called when the tracking area is in effect which means we're tracking to | |
| 721 // see if the user leaves the tab strip with their mouse. When they do, | |
| 722 // reset layout to use all available width. | |
| 723 - (void)mouseExited:(NSEvent*)event { | |
| 724 [tabView_ removeTrackingArea:closeTabTrackingArea_.get()]; | |
| 725 closeTabTrackingArea_.reset(nil); | |
| 726 availableResizeWidth_ = kUseFullAvailableWidth; | |
| 727 [self layoutTabs]; | |
| 728 } | |
| 729 | |
| 657 @end | 730 @end |
| OLD | NEW |