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_controller.h" |
| 6 |
| 7 #import <Cocoa/Cocoa.h> |
| 8 |
5 #include "app/l10n_util_mac.h" | 9 #include "app/l10n_util_mac.h" |
6 #include "base/mac_util.h" | 10 #include "base/mac_util.h" |
7 #import "chrome/browser/browser_theme_provider.h" | 11 #import "chrome/browser/browser_theme_provider.h" |
8 #import "chrome/browser/cocoa/menu_controller.h" | 12 #import "chrome/browser/cocoa/menu_controller.h" |
9 #import "chrome/browser/cocoa/tab_controller.h" | |
10 #import "chrome/browser/cocoa/tab_controller_target.h" | 13 #import "chrome/browser/cocoa/tab_controller_target.h" |
11 #import "chrome/browser/cocoa/tab_view.h" | 14 #import "chrome/browser/cocoa/tab_view.h" |
12 #import "chrome/browser/cocoa/themed_window.h" | 15 #import "chrome/browser/cocoa/themed_window.h" |
| 16 #import "chrome/browser/cocoa/throbber_view.h" |
13 #include "grit/generated_resources.h" | 17 #include "grit/generated_resources.h" |
14 | 18 |
15 @implementation TabController | 19 @implementation TabController |
16 | 20 |
17 @synthesize loadingState = loadingState_; | 21 @synthesize loadingState = loadingState_; |
18 @synthesize pinned = pinned_; | 22 @synthesize pinned = pinned_; |
19 @synthesize target = target_; | 23 @synthesize target = target_; |
20 @synthesize action = action_; | 24 @synthesize action = action_; |
21 | 25 |
22 namespace TabControllerInternal { | 26 namespace TabControllerInternal { |
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
68 | 72 |
69 // The min widths match the windows values and are sums of left + right | 73 // The min widths match the windows values and are sums of left + right |
70 // padding, of which we have no comparable constants (we draw using paths, not | 74 // padding, of which we have no comparable constants (we draw using paths, not |
71 // images). The selected tab width includes the close button width. | 75 // images). The selected tab width includes the close button width. |
72 + (CGFloat)minTabWidth { return 31; } | 76 + (CGFloat)minTabWidth { return 31; } |
73 + (CGFloat)minSelectedTabWidth { return 47; } | 77 + (CGFloat)minSelectedTabWidth { return 47; } |
74 + (CGFloat)maxTabWidth { return 220; } | 78 + (CGFloat)maxTabWidth { return 220; } |
75 + (CGFloat)pinnedTabWidth { return 53; } | 79 + (CGFloat)pinnedTabWidth { return 53; } |
76 | 80 |
77 - (TabView*)tabView { | 81 - (TabView*)tabView { |
| 82 DCHECK([[self view] isKindOfClass:[TabView class]]); |
78 return static_cast<TabView*>([self view]); | 83 return static_cast<TabView*>([self view]); |
79 } | 84 } |
80 | 85 |
81 - (id)init { | 86 - (id)init { |
82 self = [super initWithNibName:@"TabView" bundle:mac_util::MainAppBundle()]; | 87 self = [super initWithNibName:@"TabView" bundle:mac_util::MainAppBundle()]; |
83 if (self != nil) { | 88 if (self != nil) { |
84 isIconShowing_ = YES; | 89 isIconShowing_ = YES; |
85 NSNotificationCenter* defaultCenter = [NSNotificationCenter defaultCenter]; | 90 NSNotificationCenter* defaultCenter = [NSNotificationCenter defaultCenter]; |
86 [defaultCenter addObserver:self | 91 [defaultCenter addObserver:self |
87 selector:@selector(viewResized:) | 92 selector:@selector(viewResized:) |
(...skipping 10 matching lines...) Expand all Loading... |
98 - (void)dealloc { | 103 - (void)dealloc { |
99 [[NSNotificationCenter defaultCenter] removeObserver:self]; | 104 [[NSNotificationCenter defaultCenter] removeObserver:self]; |
100 [super dealloc]; | 105 [super dealloc]; |
101 } | 106 } |
102 | 107 |
103 // The internals of |-setSelected:| but doesn't check if we're already set | 108 // The internals of |-setSelected:| but doesn't check if we're already set |
104 // to |selected|. Pass the selection change to the subviews that need it and | 109 // to |selected|. Pass the selection change to the subviews that need it and |
105 // mark ourselves as needing a redraw. | 110 // mark ourselves as needing a redraw. |
106 - (void)internalSetSelected:(BOOL)selected { | 111 - (void)internalSetSelected:(BOOL)selected { |
107 selected_ = selected; | 112 selected_ = selected; |
108 TabView* tabView = static_cast<TabView*>([self view]); | 113 TabView* tabView = [self tabView]; |
109 DCHECK([tabView isKindOfClass:[TabView class]]); | |
110 [tabView setState:selected]; | 114 [tabView setState:selected]; |
111 [tabView cancelAlert]; | 115 [tabView cancelAlert]; |
112 [self updateVisibility]; | 116 [self updateVisibility]; |
113 [self updateTitleColor]; | 117 [self updateTitleColor]; |
114 } | 118 } |
115 | 119 |
116 // Called when the tab's nib is done loading and all outlets are hooked up. | 120 // Called when the tab's nib is done loading and all outlets are hooked up. |
117 - (void)awakeFromNib { | 121 - (void)awakeFromNib { |
118 // Remember the icon's frame, so that if the icon is ever removed, a new | 122 // Remember the icon's frame, so that if the icon is ever removed, a new |
119 // one can later replace it in the proper location. | 123 // one can later replace it in the proper location. |
120 originalIconFrame_ = [iconView_ frame]; | 124 originalIconFrame_ = [iconView_ frame]; |
121 | 125 |
122 // When the icon is removed, the title expands to the left to fill the space | 126 // When the icon is removed, the title expands to the left to fill the space |
123 // left by the icon. When the close button is removed, the title expands to | 127 // left by the icon. This is the amounts to expand and contracttitleView_ |
124 // the right to fill its space. These are the amounts to expand and contract | 128 // under those conditions. FIXME: update comment, simplify logic |
125 // titleView_ under those conditions. | |
126 NSRect titleFrame = [titleView_ frame]; | 129 NSRect titleFrame = [titleView_ frame]; |
127 iconTitleXOffset_ = NSMinX(titleFrame) - NSMinX(originalIconFrame_); | 130 // iconTitleXOffset_ = NSMinX(titleFrame) - NSMinX(originalIconFrame_); |
128 titleCloseWidthOffset_ = NSMaxX([closeButton_ frame]) - NSMaxX(titleFrame); | 131 iconTitleXOffset_ = NSMinX(titleFrame) - NSMaxX(originalIconFrame_); |
| 132 titleTabOffset_ = NSWidth([[self view] frame]) - NSMaxX(titleFrame); |
| 133 |
129 | 134 |
130 [self internalSetSelected:selected_]; | 135 [self internalSetSelected:selected_]; |
131 } | 136 } |
132 | 137 |
133 // Called when Cocoa wants to display the context menu. Lazily instantiate | 138 // Called when Cocoa wants to display the context menu. Lazily instantiate |
134 // the menu based off of the cross-platform model. Re-create the menu and | 139 // the menu based off of the cross-platform model. Re-create the menu and |
135 // model every time to get the correct labels and enabling. | 140 // model every time to get the correct labels and enabling. |
136 - (NSMenu*)menu { | 141 - (NSMenu*)menu { |
137 contextMenuDelegate_.reset( | 142 contextMenuDelegate_.reset( |
138 new TabControllerInternal::MenuDelegate(target_, self)); | 143 new TabControllerInternal::MenuDelegate(target_, self)); |
139 contextMenuModel_.reset(new TabMenuModel(contextMenuDelegate_.get())); | 144 contextMenuModel_.reset(new TabMenuModel(contextMenuDelegate_.get())); |
140 contextMenuController_.reset( | 145 contextMenuController_.reset( |
141 [[MenuController alloc] initWithModel:contextMenuModel_.get() | 146 [[MenuController alloc] initWithModel:contextMenuModel_.get() |
142 useWithPopUpButtonCell:NO]); | 147 useWithPopUpButtonCell:NO]); |
143 return [contextMenuController_ menu]; | 148 return [contextMenuController_ menu]; |
144 } | 149 } |
145 | 150 |
146 - (IBAction)closeTab:(id)sender { | 151 - (IBAction)closeTab:(id)sender { |
| 152 if (![self closeButtonActive]) { |
| 153 |
| 154 NSEvent* down = [NSApp currentEvent]; |
| 155 NSEvent* fakeUp = [NSEvent mouseEventWithType:NSLeftMouseUp |
| 156 location:[down locationInWindow] |
| 157 modifierFlags:[down modifierFlags] |
| 158 timestamp:[down timestamp] |
| 159 windowNumber:[down windowNumber] |
| 160 context:[down context] |
| 161 eventNumber:0 // FIXME |
| 162 clickCount:1 |
| 163 pressure:0]; |
| 164 |
| 165 [closeButton_ mouseUp:fakeUp]; // clear "pressed" state |
| 166 [closeButton_ setState:NSOffState]; |
| 167 [[self view] mouseDown:[NSApp currentEvent]]; |
| 168 return; |
| 169 } |
| 170 |
147 if ([[self target] respondsToSelector:@selector(closeTab:)]) { | 171 if ([[self target] respondsToSelector:@selector(closeTab:)]) { |
148 [[self target] performSelector:@selector(closeTab:) | 172 [[self target] performSelector:@selector(closeTab:) |
149 withObject:[self view]]; | 173 withObject:[self view]]; |
150 } | 174 } |
151 } | 175 } |
152 | 176 |
153 - (void)setTitle:(NSString*)title { | 177 - (void)setTitle:(NSString*)title { |
154 [[self view] setToolTip:title]; | 178 [[self view] setToolTip:title]; |
155 if ([self pinned] && ![self selected]) { | 179 if ([self pinned] && ![self selected]) |
156 TabView* tabView = static_cast<TabView*>([self view]); | 180 [[self tabView] startAlert]; |
157 DCHECK([tabView isKindOfClass:[TabView class]]); | |
158 [tabView startAlert]; | |
159 } | |
160 [super setTitle:title]; | 181 [super setTitle:title]; |
161 } | 182 } |
162 | 183 |
163 - (void)setSelected:(BOOL)selected { | 184 - (void)setSelected:(BOOL)selected { |
164 if (selected_ != selected) | 185 if (selected_ != selected) |
165 [self internalSetSelected:selected]; | 186 [self internalSetSelected:selected]; |
166 } | 187 } |
167 | 188 |
168 - (BOOL)selected { | 189 - (BOOL)selected { |
169 return selected_; | 190 return selected_; |
(...skipping 13 matching lines...) Expand all Loading... |
183 } | 204 } |
184 | 205 |
185 - (NSView*)iconView { | 206 - (NSView*)iconView { |
186 return iconView_; | 207 return iconView_; |
187 } | 208 } |
188 | 209 |
189 - (NSString*)toolTip { | 210 - (NSString*)toolTip { |
190 return [[self view] toolTip]; | 211 return [[self view] toolTip]; |
191 } | 212 } |
192 | 213 |
193 // Return a rough approximation of the number of icons we could fit in the | 214 - (void)updateVisibility { |
194 // tab. We never actually do this, but it's a helpful guide for determining | 215 // Cocoa apparently gets confused if the title autoresizes too small, so |
195 // how much space we have available. | 216 // handle this manually. FIXME: comment slightly wrong |
196 - (int)iconCapacity { | 217 CGFloat titleWidth = NSWidth([[self view] frame]) - |
197 CGFloat width = NSMaxX([closeButton_ frame]) - NSMinX(originalIconFrame_); | 218 titleTabOffset_ - |
198 CGFloat iconWidth = NSWidth(originalIconFrame_); | 219 NSMaxX(originalIconFrame_) - |
| 220 iconTitleXOffset_; |
199 | 221 |
200 return width / iconWidth; | 222 bool showClose = NO; |
201 } | 223 if ([[self tabView] isMouseInside]) { |
202 | 224 if ([self selected]) { |
203 // Returns YES if we should show the icon. When tabs get too small, we clip | 225 showClose = YES; |
204 // the favicon before the close button for selected tabs, and prefer the | |
205 // favicon for unselected tabs. The icon can also be suppressed more directly | |
206 // by clearing iconView_. | |
207 - (BOOL)shouldShowIcon { | |
208 if (!iconView_) | |
209 return NO; | |
210 | |
211 if ([self pinned]) | |
212 return YES; | |
213 | |
214 int iconCapacity = [self iconCapacity]; | |
215 if ([self selected]) | |
216 return iconCapacity >= 2; | |
217 return iconCapacity >= 1; | |
218 } | |
219 | |
220 // Returns YES if we should be showing the close button. The selected tab | |
221 // always shows the close button. | |
222 - (BOOL)shouldShowCloseButton { | |
223 if ([self pinned]) | |
224 return NO; | |
225 return ([self selected] || [self iconCapacity] >= 3); | |
226 } | |
227 | |
228 - (void)updateVisibility { | |
229 // iconView_ may have been replaced or it may be nil, so [iconView_ isHidden] | |
230 // won't work. Instead, the state of the icon is tracked separately in | |
231 // isIconShowing_. | |
232 BOOL oldShowIcon = isIconShowing_ ? YES : NO; | |
233 BOOL newShowIcon = [self shouldShowIcon] ? YES : NO; | |
234 | |
235 [iconView_ setHidden:newShowIcon ? NO : YES]; | |
236 isIconShowing_ = newShowIcon; | |
237 | |
238 // If the tab is pinned, hide the title. | |
239 [titleView_ setHidden:[self pinned]]; | |
240 | |
241 BOOL oldShowCloseButton = [closeButton_ isHidden] ? NO : YES; | |
242 BOOL newShowCloseButton = [self shouldShowCloseButton] ? YES : NO; | |
243 | |
244 [closeButton_ setHidden:newShowCloseButton ? NO : YES]; | |
245 | |
246 // Adjust the title view based on changes to the icon's and close button's | |
247 // visibility. | |
248 NSRect titleFrame = [titleView_ frame]; | |
249 | |
250 if (oldShowIcon != newShowIcon) { | |
251 // Adjust the left edge of the title view according to the presence or | |
252 // absence of the icon view. | |
253 | |
254 if (newShowIcon) { | |
255 titleFrame.origin.x += iconTitleXOffset_; | |
256 titleFrame.size.width -= iconTitleXOffset_; | |
257 } else { | 226 } else { |
258 titleFrame.origin.x -= iconTitleXOffset_; | 227 // To make accidental tab closure less likely, only show the close button |
259 titleFrame.size.width += iconTitleXOffset_; | 228 // on hover in background tabs if they are "wide enough". |
| 229 showClose = titleWidth >= 2*NSWidth(originalIconFrame_) || !iconView_; |
260 } | 230 } |
| 231 } else if ([self selected]) { |
| 232 // FIXME: this is a bit of a hack; use an explicit method |
| 233 BOOL isLoading = [iconView_ isKindOfClass:[ThrobberView class]]; |
| 234 showClose = isLoading ? NO : YES; |
| 235 } else { |
| 236 // NTP should always show close, even if in background (it doesn't have |
| 237 // an icon). |
| 238 showClose = iconView_ ? NO : YES; |
261 } | 239 } |
262 | 240 |
263 if (oldShowCloseButton != newShowCloseButton) { | 241 if (titleWidth > 0) { |
264 // Adjust the right edge of the title view according to the presence or | 242 NSRect titleFrame = [titleView_ frame]; |
265 // absence of the close button. | 243 titleFrame.size.width = titleWidth; |
266 if (newShowCloseButton) | 244 [titleView_ setHidden:NO]; |
267 titleFrame.size.width -= titleCloseWidthOffset_; | 245 [titleView_ setFrame:titleFrame]; |
268 else | 246 } else { |
269 titleFrame.size.width += titleCloseWidthOffset_; | 247 [titleView_ setHidden:YES]; |
270 } | 248 } |
271 | 249 |
272 [titleView_ setFrame:titleFrame]; | 250 // Don't show icon if tab is not active and tab is very small. The tab strip |
| 251 // controller takes care to always keep the active tab wide enough for a close |
| 252 // button. |
| 253 if (NSWidth([[self view] frame]) >= [TabController minSelectedTabWidth]) { |
| 254 if (showClose && [closeButton_ isHidden]) { |
| 255 closeButtonRevealTime_ = [NSDate timeIntervalSinceReferenceDate]; |
| 256 } |
| 257 |
| 258 [closeButton_ setHidden:showClose ? NO : YES]; |
| 259 [iconView_ setHidden:showClose ? YES : NO]; |
| 260 } else { |
| 261 [closeButton_ setHidden:YES]; |
| 262 [iconView_ setHidden:YES]; |
| 263 } |
273 } | 264 } |
274 | 265 |
275 - (void)updateTitleColor { | 266 - (void)updateTitleColor { |
276 NSColor* titleColor = nil; | 267 NSColor* titleColor = nil; |
277 ThemeProvider* theme = [[[self view] window] themeProvider]; | 268 ThemeProvider* theme = [[[self view] window] themeProvider]; |
278 if (theme && ![self selected]) { | 269 if (theme && ![self selected]) { |
279 titleColor = | 270 titleColor = |
280 theme->GetNSColor(BrowserThemeProvider::COLOR_BACKGROUND_TAB_TEXT, | 271 theme->GetNSColor(BrowserThemeProvider::COLOR_BACKGROUND_TAB_TEXT, |
281 true); | 272 true); |
282 } | 273 } |
283 // Default to the selected text color unless told otherwise. | 274 // Default to the selected text color unless told otherwise. |
284 if (theme && !titleColor) { | 275 if (theme && !titleColor) { |
285 titleColor = theme->GetNSColor(BrowserThemeProvider::COLOR_TAB_TEXT, | 276 titleColor = theme->GetNSColor(BrowserThemeProvider::COLOR_TAB_TEXT, |
286 true); | 277 true); |
287 } | 278 } |
288 [titleView_ setTextColor:titleColor ? titleColor : [NSColor textColor]]; | 279 [titleView_ setTextColor:titleColor ? titleColor : [NSColor textColor]]; |
289 } | 280 } |
290 | 281 |
| 282 - (BOOL)closeButtonActive { |
| 283 if ([closeButton_ isHidden]) return false; |
| 284 if ([self selected] || [self inRapidClosureMode]) return true; |
| 285 NSTimeInterval closeButtonScreenTime = |
| 286 [NSDate timeIntervalSinceReferenceDate] - closeButtonRevealTime_; |
| 287 fprintf(stderr, " %f\n", closeButtonScreenTime); |
| 288 return ![closeButton_ isHidden] && closeButtonScreenTime > 1.0; |
| 289 |
| 290 } |
| 291 |
291 // Called when our view is resized. If it gets too small, start by hiding | 292 // Called when our view is resized. If it gets too small, start by hiding |
292 // the close button and only show it if tab is selected. Eventually, hide the | 293 // the close button and only show it if tab is selected. Eventually, hide the |
293 // icon as well. We know that this is for our view because we only registered | 294 // icon as well. We know that this is for our view because we only registered |
294 // for notifications from our specific view. | 295 // for notifications from our specific view. FIXME: update comment |
295 - (void)viewResized:(NSNotification*)info { | 296 - (void)viewResized:(NSNotification*)info { |
296 [self updateVisibility]; | 297 [self updateVisibility]; |
297 } | 298 } |
298 | 299 |
299 - (void)themeChangedNotification:(NSNotification*)notification { | 300 - (void)themeChangedNotification:(NSNotification*)notification { |
300 [self updateTitleColor]; | 301 [self updateTitleColor]; |
301 } | 302 } |
302 | 303 |
303 // Called by the tabs to determine whether we are in rapid (tab) closure mode. | 304 // Called by the tabs to determine whether we are in rapid (tab) closure mode. |
304 - (BOOL)inRapidClosureMode { | 305 - (BOOL)inRapidClosureMode { |
305 if ([[self target] respondsToSelector:@selector(inRapidClosureMode)]) { | 306 if ([[self target] respondsToSelector:@selector(inRapidClosureMode)]) { |
306 return [[self target] performSelector:@selector(inRapidClosureMode)] ? | 307 return [[self target] performSelector:@selector(inRapidClosureMode)] ? |
307 YES : NO; | 308 YES : NO; |
308 } | 309 } |
309 return NO; | 310 return NO; |
310 } | 311 } |
311 | 312 |
312 @end | 313 @end |
OLD | NEW |