| 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 |