| OLD | NEW |
| 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 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/ui/cocoa/tabs/tab_strip_controller.h" | 5 #import "chrome/browser/ui/cocoa/tabs/tab_strip_controller.h" |
| 6 | 6 |
| 7 #import <QuartzCore/QuartzCore.h> | 7 #import <QuartzCore/QuartzCore.h> |
| 8 | 8 |
| 9 #include <limits> | 9 #include <limits> |
| 10 #include <string> | 10 #include <string> |
| (...skipping 79 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 90 | 90 |
| 91 // The amount by which tabs overlap. | 91 // The amount by which tabs overlap. |
| 92 const CGFloat kTabOverlap = 20.0; | 92 const CGFloat kTabOverlap = 20.0; |
| 93 | 93 |
| 94 // The width and height for a tab's icon. | 94 // The width and height for a tab's icon. |
| 95 const CGFloat kIconWidthAndHeight = 16.0; | 95 const CGFloat kIconWidthAndHeight = 16.0; |
| 96 | 96 |
| 97 // The amount by which the new tab button is offset (from the tabs). | 97 // The amount by which the new tab button is offset (from the tabs). |
| 98 const CGFloat kNewTabButtonOffset = 8.0; | 98 const CGFloat kNewTabButtonOffset = 8.0; |
| 99 | 99 |
| 100 // The amount by which to shrink the tab strip (on the right) when the | |
| 101 // incognito badge is present. | |
| 102 const CGFloat kAvatarTabStripShrink = 18; | |
| 103 | |
| 104 // Time (in seconds) in which tabs animate to their final position. | 100 // Time (in seconds) in which tabs animate to their final position. |
| 105 const NSTimeInterval kAnimationDuration = 0.125; | 101 const NSTimeInterval kAnimationDuration = 0.125; |
| 106 | 102 |
| 107 // The amount by wich the profile menu button is offset (from tab tabs or new | 103 // The amount by wich the profile menu button is offset (from tab tabs or new |
| 108 // tab button). | 104 // tab button). |
| 109 const CGFloat kProfileMenuButtonOffset = 6.0; | 105 const CGFloat kProfileMenuButtonOffset = 6.0; |
| 110 | 106 |
| 111 // Helper class for doing NSAnimationContext calls that takes a bool to disable | 107 // Helper class for doing NSAnimationContext calls that takes a bool to disable |
| 112 // all the work. Useful for code that wants to conditionally animate. | 108 // all the work. Useful for code that wants to conditionally animate. |
| 113 class ScopedNSAnimationContextGroup { | 109 class ScopedNSAnimationContextGroup { |
| (...skipping 188 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 302 // in individual tab positioning (to avoid moving them right back to where they | 298 // in individual tab positioning (to avoid moving them right back to where they |
| 303 // were). | 299 // were). |
| 304 // | 300 // |
| 305 // In order to prevent actions being taken on tabs which are closing, the tab | 301 // In order to prevent actions being taken on tabs which are closing, the tab |
| 306 // itself gets marked as such so it no longer will send back its select action | 302 // itself gets marked as such so it no longer will send back its select action |
| 307 // or allow itself to be dragged. In addition, drags on the tab strip as a | 303 // or allow itself to be dragged. In addition, drags on the tab strip as a |
| 308 // whole are disabled while there are tabs closing. | 304 // whole are disabled while there are tabs closing. |
| 309 | 305 |
| 310 @implementation TabStripController | 306 @implementation TabStripController |
| 311 | 307 |
| 312 @synthesize indentForControls = indentForControls_; | 308 @synthesize leftIndentForControls = leftIndentForControls_; |
| 309 @synthesize rightIndentForControls = rightIndentForControls_; |
| 313 | 310 |
| 314 - (id)initWithView:(TabStripView*)view | 311 - (id)initWithView:(TabStripView*)view |
| 315 switchView:(NSView*)switchView | 312 switchView:(NSView*)switchView |
| 316 browser:(Browser*)browser | 313 browser:(Browser*)browser |
| 317 delegate:(id<TabStripControllerDelegate>)delegate { | 314 delegate:(id<TabStripControllerDelegate>)delegate { |
| 318 DCHECK(view && switchView && browser && delegate); | 315 DCHECK(view && switchView && browser && delegate); |
| 319 if ((self = [super init])) { | 316 if ((self = [super init])) { |
| 320 tabStripView_.reset([view retain]); | 317 tabStripView_.reset([view retain]); |
| 321 switchView_ = switchView; | 318 switchView_ = switchView; |
| 322 browser_ = browser; | 319 browser_ = browser; |
| 323 tabStripModel_ = browser_->tabstrip_model(); | 320 tabStripModel_ = browser_->tabstrip_model(); |
| 324 hoverTabSelector_.reset(new HoverTabSelector(tabStripModel_)); | 321 hoverTabSelector_.reset(new HoverTabSelector(tabStripModel_)); |
| 325 delegate_ = delegate; | 322 delegate_ = delegate; |
| 326 bridge_.reset(new TabStripModelObserverBridge(tabStripModel_, self)); | 323 bridge_.reset(new TabStripModelObserverBridge(tabStripModel_, self)); |
| 327 dragController_.reset( | 324 dragController_.reset( |
| 328 [[TabStripDragController alloc] initWithTabStripController:self]); | 325 [[TabStripDragController alloc] initWithTabStripController:self]); |
| 329 tabContentsArray_.reset([[NSMutableArray alloc] init]); | 326 tabContentsArray_.reset([[NSMutableArray alloc] init]); |
| 330 tabArray_.reset([[NSMutableArray alloc] init]); | 327 tabArray_.reset([[NSMutableArray alloc] init]); |
| 331 NSWindow* browserWindow = [view window]; | 328 NSWindow* browserWindow = [view window]; |
| 332 | 329 |
| 333 // Important note: any non-tab subviews not added to |permanentSubviews_| | 330 // Important note: any non-tab subviews not added to |permanentSubviews_| |
| 334 // (see |-addSubviewToPermanentList:|) will be wiped out. | 331 // (see |-addSubviewToPermanentList:|) will be wiped out. |
| 335 permanentSubviews_.reset([[NSMutableArray alloc] init]); | 332 permanentSubviews_.reset([[NSMutableArray alloc] init]); |
| 336 | 333 |
| 337 defaultFavicon_.reset( | 334 defaultFavicon_.reset( |
| 338 [gfx::GetCachedImageWithName(@"nav.pdf") retain]); | 335 [gfx::GetCachedImageWithName(@"nav.pdf") retain]); |
| 339 | 336 |
| 340 [self setIndentForControls:[[self class] defaultIndentForControls]]; | 337 [self setLeftIndentForControls:[[self class] defaultLeftIndentForControls]]; |
| 338 [self setRightIndentForControls:0]; |
| 341 | 339 |
| 342 // TODO(viettrungluu): WTF? "For some reason, if the view is present in the | 340 // TODO(viettrungluu): WTF? "For some reason, if the view is present in the |
| 343 // nib a priori, it draws correctly. If we create it in code and add it to | 341 // nib a priori, it draws correctly. If we create it in code and add it to |
| 344 // the tab view, it draws with all sorts of crazy artifacts." | 342 // the tab view, it draws with all sorts of crazy artifacts." |
| 345 newTabButton_ = [view getNewTabButton]; | 343 newTabButton_ = [view getNewTabButton]; |
| 346 [self addSubviewToPermanentList:newTabButton_]; | 344 [self addSubviewToPermanentList:newTabButton_]; |
| 347 [newTabButton_ setTarget:nil]; | 345 [newTabButton_ setTarget:nil]; |
| 348 [newTabButton_ setAction:@selector(commandDispatch:)]; | 346 [newTabButton_ setAction:@selector(commandDispatch:)]; |
| 349 [newTabButton_ setTag:IDC_NEW_TAB]; | 347 [newTabButton_ setTag:IDC_NEW_TAB]; |
| 350 | 348 |
| (...skipping 111 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 462 [[[view animationForKey:@"frameOrigin"] delegate] invalidate]; | 460 [[[view animationForKey:@"frameOrigin"] delegate] invalidate]; |
| 463 } | 461 } |
| 464 [[NSNotificationCenter defaultCenter] removeObserver:self]; | 462 [[NSNotificationCenter defaultCenter] removeObserver:self]; |
| 465 [super dealloc]; | 463 [super dealloc]; |
| 466 } | 464 } |
| 467 | 465 |
| 468 + (CGFloat)defaultTabHeight { | 466 + (CGFloat)defaultTabHeight { |
| 469 return 25.0; | 467 return 25.0; |
| 470 } | 468 } |
| 471 | 469 |
| 472 + (CGFloat)defaultIndentForControls { | 470 + (CGFloat)defaultLeftIndentForControls { |
| 473 // Default indentation leaves enough room so tabs don't overlap with the | 471 // Default indentation leaves enough room so tabs don't overlap with the |
| 474 // window controls. | 472 // window controls. |
| 475 return 70.0; | 473 return 70.0; |
| 476 } | 474 } |
| 477 | 475 |
| 478 // Finds the TabContentsController associated with the given index into the tab | 476 // Finds the TabContentsController associated with the given index into the tab |
| 479 // model and swaps out the sole child of the contentArea to display its | 477 // model and swaps out the sole child of the contentArea to display its |
| 480 // contents. | 478 // contents. |
| 481 - (void)swapInTabAtIndex:(NSInteger)modelIndex { | 479 - (void)swapInTabAtIndex:(NSInteger)modelIndex { |
| 482 DCHECK(modelIndex >= 0 && modelIndex < tabStripModel_->count()); | 480 DCHECK(modelIndex >= 0 && modelIndex < tabStripModel_->count()); |
| (...skipping 300 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 783 placeholderStretchiness_ = yStretchiness; | 781 placeholderStretchiness_ = yStretchiness; |
| 784 [self layoutTabsWithAnimation:initialLayoutComplete_ regenerateSubviews:NO]; | 782 [self layoutTabsWithAnimation:initialLayoutComplete_ regenerateSubviews:NO]; |
| 785 } | 783 } |
| 786 | 784 |
| 787 - (BOOL)isDragSessionActive { | 785 - (BOOL)isDragSessionActive { |
| 788 return placeholderTab_ != nil; | 786 return placeholderTab_ != nil; |
| 789 } | 787 } |
| 790 | 788 |
| 791 - (BOOL)isTabFullyVisible:(TabView*)tab { | 789 - (BOOL)isTabFullyVisible:(TabView*)tab { |
| 792 NSRect frame = [tab frame]; | 790 NSRect frame = [tab frame]; |
| 793 return NSMinX(frame) >= [self indentForControls] && | 791 return NSMinX(frame) >= [self leftIndentForControls] && |
| 794 NSMaxX(frame) <= NSMaxX([tabStripView_ frame]); | 792 NSMaxX(frame) <= (NSMaxX([tabStripView_ frame]) - |
| 793 [self rightIndentForControls]); |
| 795 } | 794 } |
| 796 | 795 |
| 797 - (void)showNewTabButton:(BOOL)show { | 796 - (void)showNewTabButton:(BOOL)show { |
| 798 forceNewTabButtonHidden_ = show ? NO : YES; | 797 forceNewTabButtonHidden_ = show ? NO : YES; |
| 799 if (forceNewTabButtonHidden_) | 798 if (forceNewTabButtonHidden_) |
| 800 [newTabButton_ setHidden:YES]; | 799 [newTabButton_ setHidden:YES]; |
| 801 } | 800 } |
| 802 | 801 |
| 803 // Lay out all tabs in the order of their TabContentsControllers, which matches | 802 // Lay out all tabs in the order of their TabContentsControllers, which matches |
| 804 // the ordering in the TabStripModel. This call isn't that expensive, though | 803 // the ordering in the TabStripModel. This call isn't that expensive, though |
| (...skipping 27 matching lines...) Expand all Loading... |
| 832 // (taken care of by |MAX()| when calculating tab sizes). | 831 // (taken care of by |MAX()| when calculating tab sizes). |
| 833 CGFloat availableSpace = 0; | 832 CGFloat availableSpace = 0; |
| 834 if (verticalLayout_) { | 833 if (verticalLayout_) { |
| 835 availableSpace = NSHeight([tabStripView_ bounds]); | 834 availableSpace = NSHeight([tabStripView_ bounds]); |
| 836 } else { | 835 } else { |
| 837 if ([self inRapidClosureMode]) { | 836 if ([self inRapidClosureMode]) { |
| 838 availableSpace = availableResizeWidth_; | 837 availableSpace = availableResizeWidth_; |
| 839 } else { | 838 } else { |
| 840 availableSpace = NSWidth([tabStripView_ frame]); | 839 availableSpace = NSWidth([tabStripView_ frame]); |
| 841 | 840 |
| 842 BrowserWindowController* controller = | 841 // Account for the width of the new tab button. |
| 843 (BrowserWindowController*)[[tabStripView_ window] windowController]; | |
| 844 | |
| 845 // Account for the widths of the new tab button or the avatar, if any/all | |
| 846 // are present. | |
| 847 availableSpace -= NSWidth([newTabButton_ frame]) + kNewTabButtonOffset; | 842 availableSpace -= NSWidth([newTabButton_ frame]) + kNewTabButtonOffset; |
| 848 if ([controller respondsToSelector:@selector(shouldShowAvatar)] && | |
| 849 [controller shouldShowAvatar]) { | |
| 850 availableSpace -= kAvatarTabStripShrink; | |
| 851 } | |
| 852 } | 843 } |
| 853 availableSpace -= [self indentForControls]; | 844 availableSpace -= [self leftIndentForControls]; |
| 845 availableSpace -= [self rightIndentForControls]; |
| 854 } | 846 } |
| 855 | 847 |
| 856 // This may be negative, but that's okay (taken care of by |MAX()| when | 848 // This may be negative, but that's okay (taken care of by |MAX()| when |
| 857 // calculating tab sizes). "mini" tabs in horizontal mode just get a special | 849 // calculating tab sizes). "mini" tabs in horizontal mode just get a special |
| 858 // section, they don't change size. | 850 // section, they don't change size. |
| 859 CGFloat availableSpaceForNonMini = availableSpace; | 851 CGFloat availableSpaceForNonMini = availableSpace; |
| 860 if (!verticalLayout_) { | 852 if (!verticalLayout_) { |
| 861 availableSpaceForNonMini -= | 853 availableSpaceForNonMini -= |
| 862 [self numberOfOpenMiniTabs] * (kMiniTabWidth - kTabOverlap); | 854 [self numberOfOpenMiniTabs] * (kMiniTabWidth - kTabOverlap); |
| 863 } | 855 } |
| 864 | 856 |
| 865 // Initialize |nonMiniTabWidth| in case there aren't any non-mini-tabs; this | 857 // Initialize |nonMiniTabWidth| in case there aren't any non-mini-tabs; this |
| 866 // value shouldn't actually be used. | 858 // value shouldn't actually be used. |
| 867 CGFloat nonMiniTabWidth = kMaxTabWidth; | 859 CGFloat nonMiniTabWidth = kMaxTabWidth; |
| 868 const NSInteger numberOfOpenNonMiniTabs = [self numberOfOpenNonMiniTabs]; | 860 const NSInteger numberOfOpenNonMiniTabs = [self numberOfOpenNonMiniTabs]; |
| 869 if (!verticalLayout_ && numberOfOpenNonMiniTabs) { | 861 if (!verticalLayout_ && numberOfOpenNonMiniTabs) { |
| 870 // Find the width of a non-mini-tab. This only applies to horizontal | 862 // Find the width of a non-mini-tab. This only applies to horizontal |
| 871 // mode. Add in the amount we "get back" from the tabs overlapping. | 863 // mode. Add in the amount we "get back" from the tabs overlapping. |
| 872 availableSpaceForNonMini += (numberOfOpenNonMiniTabs - 1) * kTabOverlap; | 864 availableSpaceForNonMini += (numberOfOpenNonMiniTabs - 1) * kTabOverlap; |
| 873 | 865 |
| 874 // Divide up the space between the non-mini-tabs. | 866 // Divide up the space between the non-mini-tabs. |
| 875 nonMiniTabWidth = availableSpaceForNonMini / numberOfOpenNonMiniTabs; | 867 nonMiniTabWidth = availableSpaceForNonMini / numberOfOpenNonMiniTabs; |
| 876 | 868 |
| 877 // Clamp the width between the max and min. | 869 // Clamp the width between the max and min. |
| 878 nonMiniTabWidth = MAX(MIN(nonMiniTabWidth, kMaxTabWidth), kMinTabWidth); | 870 nonMiniTabWidth = MAX(MIN(nonMiniTabWidth, kMaxTabWidth), kMinTabWidth); |
| 879 } | 871 } |
| 880 | 872 |
| 881 BOOL visible = [[tabStripView_ window] isVisible]; | 873 BOOL visible = [[tabStripView_ window] isVisible]; |
| 882 | 874 |
| 883 CGFloat offset = [self indentForControls]; | 875 CGFloat offset = [self leftIndentForControls]; |
| 884 bool hasPlaceholderGap = false; | 876 bool hasPlaceholderGap = false; |
| 885 for (TabController* tab in tabArray_.get()) { | 877 for (TabController* tab in tabArray_.get()) { |
| 886 // Ignore a tab that is going through a close animation. | 878 // Ignore a tab that is going through a close animation. |
| 887 if ([closingControllers_ containsObject:tab]) | 879 if ([closingControllers_ containsObject:tab]) |
| 888 continue; | 880 continue; |
| 889 | 881 |
| 890 BOOL isPlaceholder = [[tab view] isEqual:placeholderTab_]; | 882 BOOL isPlaceholder = [[tab view] isEqual:placeholderTab_]; |
| 891 NSRect tabFrame = [[tab view] frame]; | 883 NSRect tabFrame = [[tab view] frame]; |
| 892 tabFrame.size.height = [[self class] defaultTabHeight] + 1; | 884 tabFrame.size.height = [[self class] defaultTabHeight] + 1; |
| 893 if (verticalLayout_) { | 885 if (verticalLayout_) { |
| (...skipping 141 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1035 // Mark that we've successfully completed layout of at least one tab. | 1027 // Mark that we've successfully completed layout of at least one tab. |
| 1036 initialLayoutComplete_ = YES; | 1028 initialLayoutComplete_ = YES; |
| 1037 } | 1029 } |
| 1038 | 1030 |
| 1039 // When we're told to layout from the public API we usually want to animate, | 1031 // When we're told to layout from the public API we usually want to animate, |
| 1040 // except when it's the first time. | 1032 // except when it's the first time. |
| 1041 - (void)layoutTabs { | 1033 - (void)layoutTabs { |
| 1042 [self layoutTabsWithAnimation:initialLayoutComplete_ regenerateSubviews:YES]; | 1034 [self layoutTabsWithAnimation:initialLayoutComplete_ regenerateSubviews:YES]; |
| 1043 } | 1035 } |
| 1044 | 1036 |
| 1037 - (void)layoutTabsWithoutAnimation { |
| 1038 [self layoutTabsWithAnimation:NO regenerateSubviews:YES]; |
| 1039 } |
| 1040 |
| 1045 // Handles setting the title of the tab based on the given |contents|. Uses | 1041 // Handles setting the title of the tab based on the given |contents|. Uses |
| 1046 // a canned string if |contents| is NULL. | 1042 // a canned string if |contents| is NULL. |
| 1047 - (void)setTabTitle:(NSViewController*)tab withContents:(TabContents*)contents { | 1043 - (void)setTabTitle:(NSViewController*)tab withContents:(TabContents*)contents { |
| 1048 NSString* titleString = nil; | 1044 NSString* titleString = nil; |
| 1049 if (contents) | 1045 if (contents) |
| 1050 titleString = base::SysUTF16ToNSString(contents->GetTitle()); | 1046 titleString = base::SysUTF16ToNSString(contents->GetTitle()); |
| 1051 if (![titleString length]) { | 1047 if (![titleString length]) { |
| 1052 titleString = l10n_util::GetNSString(IDS_BROWSER_WINDOW_MAC_TAB_UNTITLED); | 1048 titleString = l10n_util::GetNSString(IDS_BROWSER_WINDOW_MAC_TAB_UNTITLED); |
| 1053 } | 1049 } |
| 1054 [tab setTitle:titleString]; | 1050 [tab setTitle:titleString]; |
| (...skipping 970 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 2025 NSInteger index = [self indexFromModelIndex:modelIndex]; | 2021 NSInteger index = [self indexFromModelIndex:modelIndex]; |
| 2026 BrowserWindowController* controller = | 2022 BrowserWindowController* controller = |
| 2027 (BrowserWindowController*)[[switchView_ window] windowController]; | 2023 (BrowserWindowController*)[[switchView_ window] windowController]; |
| 2028 DCHECK(index >= 0); | 2024 DCHECK(index >= 0); |
| 2029 if (index >= 0) { | 2025 if (index >= 0) { |
| 2030 [controller setTab:[self viewAtIndex:index] isDraggable:YES]; | 2026 [controller setTab:[self viewAtIndex:index] isDraggable:YES]; |
| 2031 } | 2027 } |
| 2032 } | 2028 } |
| 2033 | 2029 |
| 2034 @end | 2030 @end |
| OLD | NEW |