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 |