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 #include "app/l10n_util_mac.h" | |
6 #include "base/mac/mac_util.h" | |
7 #import "chrome/browser/themes/browser_theme_provider.h" | |
8 #import "chrome/browser/ui/cocoa/menu_controller.h" | |
9 #import "chrome/browser/ui/cocoa/tab_controller.h" | |
10 #import "chrome/browser/ui/cocoa/tab_controller_target.h" | |
11 #import "chrome/browser/ui/cocoa/tab_view.h" | |
12 #import "chrome/browser/ui/cocoa/themed_window.h" | |
13 #import "chrome/common/extensions/extension.h" | |
14 #include "grit/generated_resources.h" | |
15 | |
16 @implementation TabController | |
17 | |
18 @synthesize action = action_; | |
19 @synthesize app = app_; | |
20 @synthesize loadingState = loadingState_; | |
21 @synthesize mini = mini_; | |
22 @synthesize pinned = pinned_; | |
23 @synthesize target = target_; | |
24 @synthesize iconView = iconView_; | |
25 @synthesize titleView = titleView_; | |
26 @synthesize closeButton = closeButton_; | |
27 | |
28 namespace TabControllerInternal { | |
29 | |
30 // A C++ delegate that handles enabling/disabling menu items and handling when | |
31 // a menu command is chosen. Also fixes up the menu item label for "pin/unpin | |
32 // tab". | |
33 class MenuDelegate : public ui::SimpleMenuModel::Delegate { | |
34 public: | |
35 explicit MenuDelegate(id<TabControllerTarget> target, TabController* owner) | |
36 : target_(target), | |
37 owner_(owner) {} | |
38 | |
39 // Overridden from ui::SimpleMenuModel::Delegate | |
40 virtual bool IsCommandIdChecked(int command_id) const { return false; } | |
41 virtual bool IsCommandIdEnabled(int command_id) const { | |
42 TabStripModel::ContextMenuCommand command = | |
43 static_cast<TabStripModel::ContextMenuCommand>(command_id); | |
44 return [target_ isCommandEnabled:command forController:owner_]; | |
45 } | |
46 virtual bool GetAcceleratorForCommandId( | |
47 int command_id, | |
48 ui::Accelerator* accelerator) { return false; } | |
49 virtual void ExecuteCommand(int command_id) { | |
50 TabStripModel::ContextMenuCommand command = | |
51 static_cast<TabStripModel::ContextMenuCommand>(command_id); | |
52 [target_ commandDispatch:command forController:owner_]; | |
53 } | |
54 | |
55 private: | |
56 id<TabControllerTarget> target_; // weak | |
57 TabController* owner_; // weak, owns me | |
58 }; | |
59 | |
60 } // TabControllerInternal namespace | |
61 | |
62 // The min widths match the windows values and are sums of left + right | |
63 // padding, of which we have no comparable constants (we draw using paths, not | |
64 // images). The selected tab width includes the close button width. | |
65 + (CGFloat)minTabWidth { return 31; } | |
66 + (CGFloat)minSelectedTabWidth { return 46; } | |
67 + (CGFloat)maxTabWidth { return 220; } | |
68 + (CGFloat)miniTabWidth { return 53; } | |
69 + (CGFloat)appTabWidth { return 66; } | |
70 | |
71 - (TabView*)tabView { | |
72 return static_cast<TabView*>([self view]); | |
73 } | |
74 | |
75 - (id)init { | |
76 self = [super initWithNibName:@"TabView" bundle:base::mac::MainAppBundle()]; | |
77 if (self != nil) { | |
78 isIconShowing_ = YES; | |
79 NSNotificationCenter* defaultCenter = [NSNotificationCenter defaultCenter]; | |
80 [defaultCenter addObserver:self | |
81 selector:@selector(viewResized:) | |
82 name:NSViewFrameDidChangeNotification | |
83 object:[self view]]; | |
84 [defaultCenter addObserver:self | |
85 selector:@selector(themeChangedNotification:) | |
86 name:kBrowserThemeDidChangeNotification | |
87 object:nil]; | |
88 } | |
89 return self; | |
90 } | |
91 | |
92 - (void)dealloc { | |
93 [[NSNotificationCenter defaultCenter] removeObserver:self]; | |
94 [[self tabView] setController:nil]; | |
95 [super dealloc]; | |
96 } | |
97 | |
98 // The internals of |-setSelected:| but doesn't check if we're already set | |
99 // to |selected|. Pass the selection change to the subviews that need it and | |
100 // mark ourselves as needing a redraw. | |
101 - (void)internalSetSelected:(BOOL)selected { | |
102 selected_ = selected; | |
103 TabView* tabView = static_cast<TabView*>([self view]); | |
104 DCHECK([tabView isKindOfClass:[TabView class]]); | |
105 [tabView setState:selected]; | |
106 [tabView cancelAlert]; | |
107 [self updateVisibility]; | |
108 [self updateTitleColor]; | |
109 } | |
110 | |
111 // Called when the tab's nib is done loading and all outlets are hooked up. | |
112 - (void)awakeFromNib { | |
113 // Remember the icon's frame, so that if the icon is ever removed, a new | |
114 // one can later replace it in the proper location. | |
115 originalIconFrame_ = [iconView_ frame]; | |
116 | |
117 // When the icon is removed, the title expands to the left to fill the space | |
118 // left by the icon. When the close button is removed, the title expands to | |
119 // the right to fill its space. These are the amounts to expand and contract | |
120 // titleView_ under those conditions. We don't have to explicilty save the | |
121 // offset between the title and the close button since we can just get that | |
122 // value for the close button's frame. | |
123 NSRect titleFrame = [titleView_ frame]; | |
124 iconTitleXOffset_ = NSMinX(titleFrame) - NSMinX(originalIconFrame_); | |
125 | |
126 [self internalSetSelected:selected_]; | |
127 } | |
128 | |
129 // Called when Cocoa wants to display the context menu. Lazily instantiate | |
130 // the menu based off of the cross-platform model. Re-create the menu and | |
131 // model every time to get the correct labels and enabling. | |
132 - (NSMenu*)menu { | |
133 contextMenuDelegate_.reset( | |
134 new TabControllerInternal::MenuDelegate(target_, self)); | |
135 contextMenuModel_.reset(new TabMenuModel(contextMenuDelegate_.get(), | |
136 [self pinned])); | |
137 contextMenuController_.reset( | |
138 [[MenuController alloc] initWithModel:contextMenuModel_.get() | |
139 useWithPopUpButtonCell:NO]); | |
140 return [contextMenuController_ menu]; | |
141 } | |
142 | |
143 - (IBAction)closeTab:(id)sender { | |
144 if ([[self target] respondsToSelector:@selector(closeTab:)]) { | |
145 [[self target] performSelector:@selector(closeTab:) | |
146 withObject:[self view]]; | |
147 } | |
148 } | |
149 | |
150 - (void)setTitle:(NSString*)title { | |
151 [[self view] setToolTip:title]; | |
152 if ([self mini] && ![self selected]) { | |
153 TabView* tabView = static_cast<TabView*>([self view]); | |
154 DCHECK([tabView isKindOfClass:[TabView class]]); | |
155 [tabView startAlert]; | |
156 } | |
157 [super setTitle:title]; | |
158 } | |
159 | |
160 - (void)setSelected:(BOOL)selected { | |
161 if (selected_ != selected) | |
162 [self internalSetSelected:selected]; | |
163 } | |
164 | |
165 - (BOOL)selected { | |
166 return selected_; | |
167 } | |
168 | |
169 - (void)setIconView:(NSView*)iconView { | |
170 [iconView_ removeFromSuperview]; | |
171 iconView_ = iconView; | |
172 if ([self app]) { | |
173 NSRect appIconFrame = [iconView frame]; | |
174 appIconFrame.origin = originalIconFrame_.origin; | |
175 // Center the icon. | |
176 appIconFrame.origin.x = ([TabController appTabWidth] - | |
177 NSWidth(appIconFrame)) / 2.0; | |
178 [iconView setFrame:appIconFrame]; | |
179 } else { | |
180 [iconView_ setFrame:originalIconFrame_]; | |
181 } | |
182 // Ensure that the icon is suppressed if no icon is set or if the tab is too | |
183 // narrow to display one. | |
184 [self updateVisibility]; | |
185 | |
186 if (iconView_) | |
187 [[self view] addSubview:iconView_]; | |
188 } | |
189 | |
190 - (NSString*)toolTip { | |
191 return [[self view] toolTip]; | |
192 } | |
193 | |
194 // Return a rough approximation of the number of icons we could fit in the | |
195 // tab. We never actually do this, but it's a helpful guide for determining | |
196 // how much space we have available. | |
197 - (int)iconCapacity { | |
198 CGFloat width = NSMaxX([closeButton_ frame]) - NSMinX(originalIconFrame_); | |
199 CGFloat iconWidth = NSWidth(originalIconFrame_); | |
200 | |
201 return width / iconWidth; | |
202 } | |
203 | |
204 // Returns YES if we should show the icon. When tabs get too small, we clip | |
205 // the favicon before the close button for selected tabs, and prefer the | |
206 // favicon for unselected tabs. The icon can also be suppressed more directly | |
207 // by clearing iconView_. | |
208 - (BOOL)shouldShowIcon { | |
209 if (!iconView_) | |
210 return NO; | |
211 | |
212 if ([self mini]) | |
213 return YES; | |
214 | |
215 int iconCapacity = [self iconCapacity]; | |
216 if ([self selected]) | |
217 return iconCapacity >= 2; | |
218 return iconCapacity >= 1; | |
219 } | |
220 | |
221 // Returns YES if we should be showing the close button. The selected tab | |
222 // always shows the close button. | |
223 - (BOOL)shouldShowCloseButton { | |
224 if ([self mini]) | |
225 return NO; | |
226 return ([self selected] || [self iconCapacity] >= 3); | |
227 } | |
228 | |
229 - (void)updateVisibility { | |
230 // iconView_ may have been replaced or it may be nil, so [iconView_ isHidden] | |
231 // won't work. Instead, the state of the icon is tracked separately in | |
232 // isIconShowing_. | |
233 BOOL newShowIcon = [self shouldShowIcon]; | |
234 | |
235 [iconView_ setHidden:!newShowIcon]; | |
236 isIconShowing_ = newShowIcon; | |
237 | |
238 // If the tab is a mini-tab, hide the title. | |
239 [titleView_ setHidden:[self mini]]; | |
240 | |
241 BOOL newShowCloseButton = [self shouldShowCloseButton]; | |
242 | |
243 [closeButton_ setHidden:!newShowCloseButton]; | |
244 | |
245 // Adjust the title view based on changes to the icon's and close button's | |
246 // visibility. | |
247 NSRect oldTitleFrame = [titleView_ frame]; | |
248 NSRect newTitleFrame; | |
249 newTitleFrame.size.height = oldTitleFrame.size.height; | |
250 newTitleFrame.origin.y = oldTitleFrame.origin.y; | |
251 | |
252 if (newShowIcon) { | |
253 newTitleFrame.origin.x = originalIconFrame_.origin.x + iconTitleXOffset_; | |
254 } else { | |
255 newTitleFrame.origin.x = originalIconFrame_.origin.x; | |
256 } | |
257 | |
258 if (newShowCloseButton) { | |
259 newTitleFrame.size.width = NSMinX([closeButton_ frame]) - | |
260 newTitleFrame.origin.x; | |
261 } else { | |
262 newTitleFrame.size.width = NSMaxX([closeButton_ frame]) - | |
263 newTitleFrame.origin.x; | |
264 } | |
265 | |
266 [titleView_ setFrame:newTitleFrame]; | |
267 } | |
268 | |
269 - (void)updateTitleColor { | |
270 NSColor* titleColor = nil; | |
271 ThemeProvider* theme = [[[self view] window] themeProvider]; | |
272 if (theme && ![self selected]) { | |
273 titleColor = | |
274 theme->GetNSColor(BrowserThemeProvider::COLOR_BACKGROUND_TAB_TEXT, | |
275 true); | |
276 } | |
277 // Default to the selected text color unless told otherwise. | |
278 if (theme && !titleColor) { | |
279 titleColor = theme->GetNSColor(BrowserThemeProvider::COLOR_TAB_TEXT, | |
280 true); | |
281 } | |
282 [titleView_ setTextColor:titleColor ? titleColor : [NSColor textColor]]; | |
283 } | |
284 | |
285 // Called when our view is resized. If it gets too small, start by hiding | |
286 // the close button and only show it if tab is selected. Eventually, hide the | |
287 // icon as well. We know that this is for our view because we only registered | |
288 // for notifications from our specific view. | |
289 - (void)viewResized:(NSNotification*)info { | |
290 [self updateVisibility]; | |
291 } | |
292 | |
293 - (void)themeChangedNotification:(NSNotification*)notification { | |
294 [self updateTitleColor]; | |
295 } | |
296 | |
297 // Called by the tabs to determine whether we are in rapid (tab) closure mode. | |
298 - (BOOL)inRapidClosureMode { | |
299 if ([[self target] respondsToSelector:@selector(inRapidClosureMode)]) { | |
300 return [[self target] performSelector:@selector(inRapidClosureMode)] ? | |
301 YES : NO; | |
302 } | |
303 return NO; | |
304 } | |
305 | |
306 @end | |
OLD | NEW |