#include "base/i18n/rtl.h"
#include "base/mac/bundle_locations.h"
#include "base/mac/mac_util.h"
-#include "base/strings/sys_string_conversions.h"
#import "chrome/browser/themes/theme_properties.h"
#import "chrome/browser/themes/theme_service.h"
-#import "chrome/browser/ui/cocoa/tabs/media_indicator_view.h"
+#import "chrome/browser/ui/cocoa/sprite_view.h"
+#import "chrome/browser/ui/cocoa/tabs/media_indicator_button.h"
#import "chrome/browser/ui/cocoa/tabs/tab_controller_target.h"
#import "chrome/browser/ui/cocoa/tabs/tab_view.h"
#import "chrome/browser/ui/cocoa/themed_window.h"
-#import "chrome/common/extensions/extension.h"
-#include "grit/generated_resources.h"
-#import "third_party/GTM/AppKit/GTMFadeTruncatingTextFieldCell.h"
+#include "content/public/browser/user_metrics.h"
+#import "extensions/common/extension.h"
#import "ui/base/cocoa/menu_controller.h"
-#include "ui/base/l10n/l10n_util_mac.h"
@implementation TabController
owner_(owner) {}
// Overridden from ui::SimpleMenuModel::Delegate
- virtual bool IsCommandIdChecked(int command_id) const OVERRIDE {
- return false;
- }
- virtual bool IsCommandIdEnabled(int command_id) const OVERRIDE {
+ bool IsCommandIdChecked(int command_id) const override { return false; }
+ bool IsCommandIdEnabled(int command_id) const override {
TabStripModel::ContextMenuCommand command =
static_cast<TabStripModel::ContextMenuCommand>(command_id);
return [target_ isCommandEnabled:command forController:owner_];
}
- virtual bool GetAcceleratorForCommandId(
- int command_id,
- ui::Accelerator* accelerator) OVERRIDE { return false; }
- virtual void ExecuteCommand(int command_id, int event_flags) OVERRIDE {
+ bool GetAcceleratorForCommandId(int command_id,
+ ui::Accelerator* accelerator) override {
+ return false;
+ }
+ void ExecuteCommand(int command_id, int event_flags) override {
TabStripModel::ContextMenuCommand command =
static_cast<TabStripModel::ContextMenuCommand>(command_id);
[target_ commandDispatch:command forController:owner_];
// of the two tab edge bitmaps because these bitmaps have a few transparent
// pixels on the side. The selected tab width includes the close button width.
+ (CGFloat)minTabWidth { return 36; }
-+ (CGFloat)minSelectedTabWidth { return 52; }
++ (CGFloat)minActiveTabWidth { return 52; }
+ (CGFloat)maxTabWidth { return 214; }
+ (CGFloat)miniTabWidth { return 58; }
+ (CGFloat)appTabWidth { return 66; }
// 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_ = NSMakeRect(19, 5, 16, 16);
- iconView_.reset([[NSImageView alloc] initWithFrame:originalIconFrame_]);
- [iconView_ setAutoresizingMask:NSViewMaxXMargin];
+ iconView_.reset([[SpriteView alloc] initWithFrame:originalIconFrame_]);
+ [iconView_ setAutoresizingMask:NSViewMaxXMargin | NSViewMinYMargin];
// 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
+ // and contract the title frame 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 = NSMakeRect(35, 6, 92, 14);
- // Label.
- titleView_.reset([[NSTextField alloc] initWithFrame:titleFrame]);
- [titleView_ setAutoresizingMask:NSViewWidthSizable];
- base::scoped_nsobject<GTMFadeTruncatingTextFieldCell> labelCell(
- [[GTMFadeTruncatingTextFieldCell alloc] initTextCell:@"Label"]);
- [labelCell setControlSize:NSSmallControlSize];
- CGFloat fontSize = [NSFont systemFontSizeForControlSize:NSSmallControlSize];
- NSFont* font = [NSFont fontWithName:
- [[labelCell font] fontName] size:fontSize];
- [labelCell setFont:font];
- [titleView_ setCell:labelCell];
- titleViewCell_ = labelCell;
-
// Close button.
closeButton_.reset([[HoverCloseButton alloc] initWithFrame:
NSMakeRect(127, 4, 18, 18)]);
closeButton:closeButton_]);
[view setAutoresizingMask:NSViewMaxXMargin | NSViewMinYMargin];
[view addSubview:iconView_];
- [view addSubview:titleView_];
[view addSubview:closeButton_];
+ [view setTitleFrame:titleFrame];
[super setView:view];
isIconShowing_ = YES;
}
- (void)dealloc {
+ [mediaIndicatorButton_ setAnimationDoneTarget:nil withAction:nil];
+ [mediaIndicatorButton_ setClickTarget:nil withAction:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self];
[[self tabView] setController:nil];
[super dealloc];
// a re-draw.
- (void)internalSetSelected:(BOOL)selected {
TabView* tabView = [self tabView];
- DCHECK([tabView isKindOfClass:[TabView class]]);
- [tabView setState:selected];
- if ([self active])
+ if ([self active]) {
+ [tabView setState:NSOnState];
[tabView cancelAlert];
+ } else {
+ [tabView setState:selected ? NSMixedState : NSOffState];
+ }
[self updateVisibility];
[self updateTitleColor];
}
return [contextMenuController_ menu];
}
+- (void)toggleMute:(id)sender {
+ if ([[self target] respondsToSelector:@selector(toggleMute:)]) {
+ [[self target] performSelector:@selector(toggleMute:)
+ withObject:[self view]];
+ }
+}
+
- (void)closeTab:(id)sender {
+ using base::UserMetricsAction;
+
+ if (mediaIndicatorButton_ && ![mediaIndicatorButton_ isHidden]) {
+ if ([mediaIndicatorButton_ isEnabled]) {
+ content::RecordAction(UserMetricsAction("CloseTab_MuteToggleAvailable"));
+ } else if ([mediaIndicatorButton_ showingMediaState] ==
+ TAB_MEDIA_STATE_AUDIO_PLAYING) {
+ content::RecordAction(UserMetricsAction("CloseTab_AudioIndicator"));
+ } else {
+ content::RecordAction(UserMetricsAction("CloseTab_RecordingIndicator"));
+ }
+ } else {
+ content::RecordAction(UserMetricsAction("CloseTab_NoMediaIndicator"));
+ }
+
if ([[self target] respondsToSelector:@selector(closeTab:)]) {
[[self target] performSelector:@selector(closeTab:)
withObject:[self view]];
}
- (void)setTitle:(NSString*)title {
- [titleView_ setStringValue:title];
- base::string16 title16 = base::SysNSStringToUTF16(title);
- bool isRTL = base::i18n::GetFirstStrongCharacterDirection(title16) ==
- base::i18n::RIGHT_TO_LEFT;
- titleViewCell_.truncateMode = isRTL ? GTMFadeTruncatingHead
- : GTMFadeTruncatingTail;
-
- if ([self mini] && ![self selected]) {
- TabView* tabView = static_cast<TabView*>([self view]);
- DCHECK([tabView isKindOfClass:[TabView class]]);
+ if ([[self title] isEqualToString:title])
+ return;
+
+ TabView* tabView = [self tabView];
+ [tabView setTitle:title];
+
+ if ([self mini] && ![self active]) {
[tabView startAlert];
}
[super setTitle:title];
return selected_ || active_;
}
-- (NSView*)iconView {
+- (SpriteView*)iconView {
return iconView_;
}
-- (void)setIconView:(NSView*)iconView {
+- (void)setIconView:(SpriteView*)iconView {
[iconView_ removeFromSuperview];
iconView_.reset([iconView retain]);
- if ([self app] || [self mini]) {
- NSRect appIconFrame = [iconView frame];
- appIconFrame.origin = originalIconFrame_.origin;
-
- const CGFloat tabWidth = [self app] ? [TabController appTabWidth]
- : [TabController miniTabWidth];
-
- // Center the icon.
- appIconFrame.origin.x =
- std::floor((tabWidth - 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_];
}
-- (NSTextField*)titleView {
- return titleView_;
+- (MediaIndicatorButton*)mediaIndicatorButton {
+ return mediaIndicatorButton_;
}
-- (MediaIndicatorView*)mediaIndicatorView {
- return mediaIndicatorView_;
-}
-
-- (void)setMediaIndicatorView:(MediaIndicatorView*)mediaIndicatorView {
- [mediaIndicatorView_ removeFromSuperview];
- mediaIndicatorView_.reset([mediaIndicatorView retain]);
- [self updateVisibility];
- if (mediaIndicatorView_)
- [[self view] addSubview:mediaIndicatorView_];
+- (void)setMediaState:(TabMediaState)mediaState {
+ if (!mediaIndicatorButton_ && mediaState != TAB_MEDIA_STATE_NONE) {
+ mediaIndicatorButton_.reset([[MediaIndicatorButton alloc] init]);
+ [self updateVisibility]; // Do layout and visibility before adding subview.
+ [[self view] addSubview:mediaIndicatorButton_];
+ [mediaIndicatorButton_ setAnimationDoneTarget:self
+ withAction:@selector(updateVisibility)];
+ [mediaIndicatorButton_ setClickTarget:self
+ withAction:@selector(toggleMute:)];
+ }
+ [mediaIndicatorButton_ transitionToMediaState:mediaState];
}
- (HoverCloseButton*)closeButton {
- (BOOL)shouldShowIcon {
return chrome::ShouldTabShowFavicon(
- [self iconCapacity], [self mini], [self selected], iconView_ != nil,
- !mediaIndicatorView_ ? TAB_MEDIA_STATE_NONE :
- [mediaIndicatorView_ animatingMediaState]);
+ [self iconCapacity], [self mini], [self active], iconView_ != nil,
+ !mediaIndicatorButton_ ? TAB_MEDIA_STATE_NONE :
+ [mediaIndicatorButton_ showingMediaState]);
}
- (BOOL)shouldShowMediaIndicator {
- if (!mediaIndicatorView_)
- return NO;
return chrome::ShouldTabShowMediaIndicator(
- [self iconCapacity], [self mini], [self selected], iconView_ != nil,
- [mediaIndicatorView_ animatingMediaState]);
+ [self iconCapacity], [self mini], [self active], iconView_ != nil,
+ !mediaIndicatorButton_ ? TAB_MEDIA_STATE_NONE :
+ [mediaIndicatorButton_ showingMediaState]);
}
- (BOOL)shouldShowCloseButton {
return chrome::ShouldTabShowCloseButton(
- [self iconCapacity], [self mini], [self selected]);
+ [self iconCapacity], [self mini], [self active]);
+}
+
+- (void)setIconImage:(NSImage*)image {
+ [self setIconImage:image withToastAnimation:NO];
+}
+
+- (void)setIconImage:(NSImage*)image withToastAnimation:(BOOL)animate {
+ if (image == nil) {
+ [self setIconView:nil];
+ } else {
+ if (iconView_.get() == nil) {
+ base::scoped_nsobject<SpriteView> iconView([[SpriteView alloc] init]);
+ [iconView setAutoresizingMask:NSViewMaxXMargin | NSViewMinYMargin];
+ [self setIconView:iconView];
+ }
+
+ [iconView_ setImage:image withToastAnimation:animate];
+
+ if ([self app] || [self mini]) {
+ NSRect appIconFrame = [iconView_ frame];
+ appIconFrame.origin = originalIconFrame_.origin;
+
+ const CGFloat tabWidth = [self app] ? [TabController appTabWidth]
+ : [TabController miniTabWidth];
+
+ // Center the icon.
+ appIconFrame.origin.x =
+ std::floor((tabWidth - NSWidth(appIconFrame)) / 2.0);
+ [iconView_ setFrame:appIconFrame];
+ } else {
+ [iconView_ setFrame:originalIconFrame_];
+ }
+ }
}
- (void)updateVisibility {
isIconShowing_ = newShowIcon;
// If the tab is a mini-tab, hide the title.
- [titleView_ setHidden:[self mini]];
+ TabView* tabView = [self tabView];
+ [tabView setTitleHidden:[self mini]];
BOOL newShowCloseButton = [self shouldShowCloseButton];
BOOL newShowMediaIndicator = [self shouldShowMediaIndicator];
- [mediaIndicatorView_ setHidden:!newShowMediaIndicator];
+ [mediaIndicatorButton_ setHidden:!newShowMediaIndicator];
if (newShowMediaIndicator) {
- NSRect newFrame = [mediaIndicatorView_ frame];
+ NSRect newFrame = [mediaIndicatorButton_ frame];
+ newFrame.size = [[mediaIndicatorButton_ image] size];
if ([self app] || [self mini]) {
// Tab is pinned: Position the media indicator in the center.
const CGFloat tabWidth = [self app] ?
newFrame.origin.y = NSMinY(originalIconFrame_) -
std::floor((NSHeight(newFrame) - NSHeight(originalIconFrame_)) / 2);
} else {
- // The Frame for the mediaIndicatorView_ depends on whether iconView_
+ // The Frame for the mediaIndicatorButton_ depends on whether iconView_
// and/or closeButton_ are visible, and where they have been positioned.
const NSRect closeButtonFrame = [closeButton_ frame];
newFrame.origin.x = NSMinX(closeButtonFrame);
newFrame.origin.y = NSMinY(closeButtonFrame) -
std::floor((NSHeight(newFrame) - NSHeight(closeButtonFrame)) / 2);
}
- [mediaIndicatorView_ setFrame:newFrame];
+ [mediaIndicatorButton_ setFrame:newFrame];
}
// Adjust the title view based on changes to the icon's and close button's
// visibility.
- NSRect oldTitleFrame = [titleView_ frame];
+ NSRect oldTitleFrame = [tabView titleFrame];
NSRect newTitleFrame;
newTitleFrame.size.height = oldTitleFrame.size.height;
newTitleFrame.origin.y = oldTitleFrame.origin.y;
}
if (newShowMediaIndicator) {
- newTitleFrame.size.width = NSMinX([mediaIndicatorView_ frame]) -
+ newTitleFrame.size.width = NSMinX([mediaIndicatorButton_ frame]) -
newTitleFrame.origin.x;
} else if (newShowCloseButton) {
newTitleFrame.size.width = NSMinX([closeButton_ frame]) -
newTitleFrame.origin.x;
}
- [titleView_ setFrame:newTitleFrame];
+ [tabView setTitleFrame:newTitleFrame];
}
- (void)updateTitleColor {
// Default to the selected text color unless told otherwise.
if (theme && !titleColor)
titleColor = theme->GetNSColor(ThemeProperties::COLOR_TAB_TEXT);
- [titleView_ setTextColor:titleColor ? titleColor : [NSColor textColor]];
+ [[self tabView] setTitleColor:titleColor ? titleColor : [NSColor textColor]];
}
- (void)themeChangedNotification:(NSNotification*)notification {