summaryrefslogtreecommitdiffstats
path: root/chrome/browser/ui/cocoa/tab_controller.mm
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/ui/cocoa/tab_controller.mm')
-rw-r--r--chrome/browser/ui/cocoa/tab_controller.mm306
1 files changed, 306 insertions, 0 deletions
diff --git a/chrome/browser/ui/cocoa/tab_controller.mm b/chrome/browser/ui/cocoa/tab_controller.mm
new file mode 100644
index 0000000..2ae2454
--- /dev/null
+++ b/chrome/browser/ui/cocoa/tab_controller.mm
@@ -0,0 +1,306 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "app/l10n_util_mac.h"
+#include "base/mac_util.h"
+#import "chrome/browser/themes/browser_theme_provider.h"
+#import "chrome/browser/ui/cocoa/menu_controller.h"
+#import "chrome/browser/ui/cocoa/tab_controller.h"
+#import "chrome/browser/ui/cocoa/tab_controller_target.h"
+#import "chrome/browser/ui/cocoa/tab_view.h"
+#import "chrome/browser/ui/cocoa/themed_window.h"
+#import "chrome/common/extensions/extension.h"
+#include "grit/generated_resources.h"
+
+@implementation TabController
+
+@synthesize action = action_;
+@synthesize app = app_;
+@synthesize loadingState = loadingState_;
+@synthesize mini = mini_;
+@synthesize pinned = pinned_;
+@synthesize target = target_;
+@synthesize iconView = iconView_;
+@synthesize titleView = titleView_;
+@synthesize closeButton = closeButton_;
+
+namespace TabControllerInternal {
+
+// A C++ delegate that handles enabling/disabling menu items and handling when
+// a menu command is chosen. Also fixes up the menu item label for "pin/unpin
+// tab".
+class MenuDelegate : public menus::SimpleMenuModel::Delegate {
+ public:
+ explicit MenuDelegate(id<TabControllerTarget> target, TabController* owner)
+ : target_(target),
+ owner_(owner) {}
+
+ // Overridden from menus::SimpleMenuModel::Delegate
+ virtual bool IsCommandIdChecked(int command_id) const { return false; }
+ virtual bool IsCommandIdEnabled(int command_id) const {
+ TabStripModel::ContextMenuCommand command =
+ static_cast<TabStripModel::ContextMenuCommand>(command_id);
+ return [target_ isCommandEnabled:command forController:owner_];
+ }
+ virtual bool GetAcceleratorForCommandId(
+ int command_id,
+ menus::Accelerator* accelerator) { return false; }
+ virtual void ExecuteCommand(int command_id) {
+ TabStripModel::ContextMenuCommand command =
+ static_cast<TabStripModel::ContextMenuCommand>(command_id);
+ [target_ commandDispatch:command forController:owner_];
+ }
+
+ private:
+ id<TabControllerTarget> target_; // weak
+ TabController* owner_; // weak, owns me
+};
+
+} // TabControllerInternal namespace
+
+// The min widths match the windows values and are sums of left + right
+// padding, of which we have no comparable constants (we draw using paths, not
+// images). The selected tab width includes the close button width.
++ (CGFloat)minTabWidth { return 31; }
++ (CGFloat)minSelectedTabWidth { return 46; }
++ (CGFloat)maxTabWidth { return 220; }
++ (CGFloat)miniTabWidth { return 53; }
++ (CGFloat)appTabWidth { return 66; }
+
+- (TabView*)tabView {
+ return static_cast<TabView*>([self view]);
+}
+
+- (id)init {
+ self = [super initWithNibName:@"TabView" bundle:mac_util::MainAppBundle()];
+ if (self != nil) {
+ isIconShowing_ = YES;
+ NSNotificationCenter* defaultCenter = [NSNotificationCenter defaultCenter];
+ [defaultCenter addObserver:self
+ selector:@selector(viewResized:)
+ name:NSViewFrameDidChangeNotification
+ object:[self view]];
+ [defaultCenter addObserver:self
+ selector:@selector(themeChangedNotification:)
+ name:kBrowserThemeDidChangeNotification
+ object:nil];
+ }
+ return self;
+}
+
+- (void)dealloc {
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+ [[self tabView] setController:nil];
+ [super dealloc];
+}
+
+// The internals of |-setSelected:| but doesn't check if we're already set
+// to |selected|. Pass the selection change to the subviews that need it and
+// mark ourselves as needing a redraw.
+- (void)internalSetSelected:(BOOL)selected {
+ selected_ = selected;
+ TabView* tabView = static_cast<TabView*>([self view]);
+ DCHECK([tabView isKindOfClass:[TabView class]]);
+ [tabView setState:selected];
+ [tabView cancelAlert];
+ [self updateVisibility];
+ [self updateTitleColor];
+}
+
+// Called when the tab's nib is done loading and all outlets are hooked up.
+- (void)awakeFromNib {
+ // Remember the icon's frame, so that if the icon is ever removed, a new
+ // one can later replace it in the proper location.
+ originalIconFrame_ = [iconView_ frame];
+
+ // When the icon is removed, the title expands to the left to fill the space
+ // left by the icon. When the close button is removed, the title expands to
+ // the right to fill its space. These are the amounts to expand and contract
+ // titleView_ under those conditions. We don't have to explicilty save the
+ // offset between the title and the close button since we can just get that
+ // value for the close button's frame.
+ NSRect titleFrame = [titleView_ frame];
+ iconTitleXOffset_ = NSMinX(titleFrame) - NSMinX(originalIconFrame_);
+
+ [self internalSetSelected:selected_];
+}
+
+// Called when Cocoa wants to display the context menu. Lazily instantiate
+// the menu based off of the cross-platform model. Re-create the menu and
+// model every time to get the correct labels and enabling.
+- (NSMenu*)menu {
+ contextMenuDelegate_.reset(
+ new TabControllerInternal::MenuDelegate(target_, self));
+ contextMenuModel_.reset(new TabMenuModel(contextMenuDelegate_.get(),
+ [self pinned]));
+ contextMenuController_.reset(
+ [[MenuController alloc] initWithModel:contextMenuModel_.get()
+ useWithPopUpButtonCell:NO]);
+ return [contextMenuController_ menu];
+}
+
+- (IBAction)closeTab:(id)sender {
+ if ([[self target] respondsToSelector:@selector(closeTab:)]) {
+ [[self target] performSelector:@selector(closeTab:)
+ withObject:[self view]];
+ }
+}
+
+- (void)setTitle:(NSString*)title {
+ [[self view] setToolTip:title];
+ if ([self mini] && ![self selected]) {
+ TabView* tabView = static_cast<TabView*>([self view]);
+ DCHECK([tabView isKindOfClass:[TabView class]]);
+ [tabView startAlert];
+ }
+ [super setTitle:title];
+}
+
+- (void)setSelected:(BOOL)selected {
+ if (selected_ != selected)
+ [self internalSetSelected:selected];
+}
+
+- (BOOL)selected {
+ return selected_;
+}
+
+- (void)setIconView:(NSView*)iconView {
+ [iconView_ removeFromSuperview];
+ iconView_ = iconView;
+ if ([self app]) {
+ NSRect appIconFrame = [iconView frame];
+ appIconFrame.origin = originalIconFrame_.origin;
+ // Center the icon.
+ appIconFrame.origin.x = ([TabController appTabWidth] -
+ NSWidth(appIconFrame)) / 2.0;
+ [iconView setFrame:appIconFrame];
+ } else {
+ [iconView_ setFrame:originalIconFrame_];
+ }
+ // Ensure that the icon is suppressed if no icon is set or if the tab is too
+ // narrow to display one.
+ [self updateVisibility];
+
+ if (iconView_)
+ [[self view] addSubview:iconView_];
+}
+
+- (NSString*)toolTip {
+ return [[self view] toolTip];
+}
+
+// Return a rough approximation of the number of icons we could fit in the
+// tab. We never actually do this, but it's a helpful guide for determining
+// how much space we have available.
+- (int)iconCapacity {
+ CGFloat width = NSMaxX([closeButton_ frame]) - NSMinX(originalIconFrame_);
+ CGFloat iconWidth = NSWidth(originalIconFrame_);
+
+ return width / iconWidth;
+}
+
+// Returns YES if we should show the icon. When tabs get too small, we clip
+// the favicon before the close button for selected tabs, and prefer the
+// favicon for unselected tabs. The icon can also be suppressed more directly
+// by clearing iconView_.
+- (BOOL)shouldShowIcon {
+ if (!iconView_)
+ return NO;
+
+ if ([self mini])
+ return YES;
+
+ int iconCapacity = [self iconCapacity];
+ if ([self selected])
+ return iconCapacity >= 2;
+ return iconCapacity >= 1;
+}
+
+// Returns YES if we should be showing the close button. The selected tab
+// always shows the close button.
+- (BOOL)shouldShowCloseButton {
+ if ([self mini])
+ return NO;
+ return ([self selected] || [self iconCapacity] >= 3);
+}
+
+- (void)updateVisibility {
+ // iconView_ may have been replaced or it may be nil, so [iconView_ isHidden]
+ // won't work. Instead, the state of the icon is tracked separately in
+ // isIconShowing_.
+ BOOL newShowIcon = [self shouldShowIcon];
+
+ [iconView_ setHidden:!newShowIcon];
+ isIconShowing_ = newShowIcon;
+
+ // If the tab is a mini-tab, hide the title.
+ [titleView_ setHidden:[self mini]];
+
+ BOOL newShowCloseButton = [self shouldShowCloseButton];
+
+ [closeButton_ setHidden:!newShowCloseButton];
+
+ // Adjust the title view based on changes to the icon's and close button's
+ // visibility.
+ NSRect oldTitleFrame = [titleView_ frame];
+ NSRect newTitleFrame;
+ newTitleFrame.size.height = oldTitleFrame.size.height;
+ newTitleFrame.origin.y = oldTitleFrame.origin.y;
+
+ if (newShowIcon) {
+ newTitleFrame.origin.x = originalIconFrame_.origin.x + iconTitleXOffset_;
+ } else {
+ newTitleFrame.origin.x = originalIconFrame_.origin.x;
+ }
+
+ if (newShowCloseButton) {
+ newTitleFrame.size.width = NSMinX([closeButton_ frame]) -
+ newTitleFrame.origin.x;
+ } else {
+ newTitleFrame.size.width = NSMaxX([closeButton_ frame]) -
+ newTitleFrame.origin.x;
+ }
+
+ [titleView_ setFrame:newTitleFrame];
+}
+
+- (void)updateTitleColor {
+ NSColor* titleColor = nil;
+ ThemeProvider* theme = [[[self view] window] themeProvider];
+ if (theme && ![self selected]) {
+ titleColor =
+ theme->GetNSColor(BrowserThemeProvider::COLOR_BACKGROUND_TAB_TEXT,
+ true);
+ }
+ // Default to the selected text color unless told otherwise.
+ if (theme && !titleColor) {
+ titleColor = theme->GetNSColor(BrowserThemeProvider::COLOR_TAB_TEXT,
+ true);
+ }
+ [titleView_ setTextColor:titleColor ? titleColor : [NSColor textColor]];
+}
+
+// Called when our view is resized. If it gets too small, start by hiding
+// the close button and only show it if tab is selected. Eventually, hide the
+// icon as well. We know that this is for our view because we only registered
+// for notifications from our specific view.
+- (void)viewResized:(NSNotification*)info {
+ [self updateVisibility];
+}
+
+- (void)themeChangedNotification:(NSNotification*)notification {
+ [self updateTitleColor];
+}
+
+// Called by the tabs to determine whether we are in rapid (tab) closure mode.
+- (BOOL)inRapidClosureMode {
+ if ([[self target] respondsToSelector:@selector(inRapidClosureMode)]) {
+ return [[self target] performSelector:@selector(inRapidClosureMode)] ?
+ YES : NO;
+ }
+ return NO;
+}
+
+@end