| Index: chrome/browser/ui/cocoa/tab_strip_controller.mm
|
| ===================================================================
|
| --- chrome/browser/ui/cocoa/tab_strip_controller.mm (revision 71805)
|
| +++ chrome/browser/ui/cocoa/tab_strip_controller.mm (working copy)
|
| @@ -1,1904 +0,0 @@
|
| -// 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.
|
| -
|
| -#import "chrome/browser/ui/cocoa/tab_strip_controller.h"
|
| -
|
| -#import <QuartzCore/QuartzCore.h>
|
| -
|
| -#include <limits>
|
| -#include <string>
|
| -
|
| -#include "app/l10n_util.h"
|
| -#include "app/mac/nsimage_cache.h"
|
| -#include "app/resource_bundle.h"
|
| -#include "base/mac/mac_util.h"
|
| -#include "base/sys_string_conversions.h"
|
| -#include "chrome/app/chrome_command_ids.h"
|
| -#include "chrome/browser/autocomplete/autocomplete.h"
|
| -#include "chrome/browser/autocomplete/autocomplete_classifier.h"
|
| -#include "chrome/browser/autocomplete/autocomplete_match.h"
|
| -#include "chrome/browser/metrics/user_metrics.h"
|
| -#include "chrome/browser/profiles/profile.h"
|
| -#include "chrome/browser/debugger/devtools_window.h"
|
| -#include "chrome/browser/net/url_fixer_upper.h"
|
| -#include "chrome/browser/sidebar/sidebar_container.h"
|
| -#include "chrome/browser/sidebar/sidebar_manager.h"
|
| -#include "chrome/browser/tab_contents/navigation_controller.h"
|
| -#include "chrome/browser/tab_contents/navigation_entry.h"
|
| -#include "chrome/browser/tab_contents/tab_contents.h"
|
| -#include "chrome/browser/tab_contents/tab_contents_view.h"
|
| -#include "chrome/browser/tabs/tab_strip_model.h"
|
| -#include "chrome/browser/ui/browser.h"
|
| -#include "chrome/browser/ui/browser_navigator.h"
|
| -#import "chrome/browser/ui/cocoa/browser_window_controller.h"
|
| -#import "chrome/browser/ui/cocoa/constrained_window_mac.h"
|
| -#import "chrome/browser/ui/cocoa/new_tab_button.h"
|
| -#import "chrome/browser/ui/cocoa/tab_strip_view.h"
|
| -#import "chrome/browser/ui/cocoa/tab_contents_controller.h"
|
| -#import "chrome/browser/ui/cocoa/tab_controller.h"
|
| -#import "chrome/browser/ui/cocoa/tab_strip_model_observer_bridge.h"
|
| -#import "chrome/browser/ui/cocoa/tab_view.h"
|
| -#import "chrome/browser/ui/cocoa/throbber_view.h"
|
| -#include "chrome/browser/ui/find_bar/find_bar.h"
|
| -#include "chrome/browser/ui/find_bar/find_bar_controller.h"
|
| -#include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
|
| -#include "grit/app_resources.h"
|
| -#include "grit/generated_resources.h"
|
| -#include "grit/theme_resources.h"
|
| -#include "skia/ext/skia_utils_mac.h"
|
| -#import "third_party/GTM/AppKit/GTMNSAnimation+Duration.h"
|
| -
|
| -NSString* const kTabStripNumberOfTabsChanged = @"kTabStripNumberOfTabsChanged";
|
| -
|
| -namespace {
|
| -
|
| -// The images names used for different states of the new tab button.
|
| -NSString* const kNewTabHoverImage = @"newtab_h.pdf";
|
| -NSString* const kNewTabImage = @"newtab.pdf";
|
| -NSString* const kNewTabPressedImage = @"newtab_p.pdf";
|
| -
|
| -// A value to indicate tab layout should use the full available width of the
|
| -// view.
|
| -const CGFloat kUseFullAvailableWidth = -1.0;
|
| -
|
| -// The amount by which tabs overlap.
|
| -const CGFloat kTabOverlap = 20.0;
|
| -
|
| -// The width and height for a tab's icon.
|
| -const CGFloat kIconWidthAndHeight = 16.0;
|
| -
|
| -// The amount by which the new tab button is offset (from the tabs).
|
| -const CGFloat kNewTabButtonOffset = 8.0;
|
| -
|
| -// The amount by which to shrink the tab strip (on the right) when the
|
| -// incognito badge is present.
|
| -const CGFloat kIncognitoBadgeTabStripShrink = 18;
|
| -
|
| -// Time (in seconds) in which tabs animate to their final position.
|
| -const NSTimeInterval kAnimationDuration = 0.125;
|
| -
|
| -// Helper class for doing NSAnimationContext calls that takes a bool to disable
|
| -// all the work. Useful for code that wants to conditionally animate.
|
| -class ScopedNSAnimationContextGroup {
|
| - public:
|
| - explicit ScopedNSAnimationContextGroup(bool animate)
|
| - : animate_(animate) {
|
| - if (animate_) {
|
| - [NSAnimationContext beginGrouping];
|
| - }
|
| - }
|
| -
|
| - ~ScopedNSAnimationContextGroup() {
|
| - if (animate_) {
|
| - [NSAnimationContext endGrouping];
|
| - }
|
| - }
|
| -
|
| - void SetCurrentContextDuration(NSTimeInterval duration) {
|
| - if (animate_) {
|
| - [[NSAnimationContext currentContext] gtm_setDuration:duration
|
| - eventMask:NSLeftMouseUpMask];
|
| - }
|
| - }
|
| -
|
| - void SetCurrentContextShortestDuration() {
|
| - if (animate_) {
|
| - // The minimum representable time interval. This used to stop an
|
| - // in-progress animation as quickly as possible.
|
| - const NSTimeInterval kMinimumTimeInterval =
|
| - std::numeric_limits<NSTimeInterval>::min();
|
| - // Directly set the duration to be short, avoiding the Steve slowmotion
|
| - // ettect the gtm_setDuration: provides.
|
| - [[NSAnimationContext currentContext] setDuration:kMinimumTimeInterval];
|
| - }
|
| - }
|
| -
|
| -private:
|
| - bool animate_;
|
| - DISALLOW_COPY_AND_ASSIGN(ScopedNSAnimationContextGroup);
|
| -};
|
| -
|
| -} // namespace
|
| -
|
| -@interface TabStripController (Private)
|
| -- (void)installTrackingArea;
|
| -- (void)addSubviewToPermanentList:(NSView*)aView;
|
| -- (void)regenerateSubviewList;
|
| -- (NSInteger)indexForContentsView:(NSView*)view;
|
| -- (void)updateFavIconForContents:(TabContents*)contents
|
| - atIndex:(NSInteger)modelIndex;
|
| -- (void)layoutTabsWithAnimation:(BOOL)animate
|
| - regenerateSubviews:(BOOL)doUpdate;
|
| -- (void)animationDidStopForController:(TabController*)controller
|
| - finished:(BOOL)finished;
|
| -- (NSInteger)indexFromModelIndex:(NSInteger)index;
|
| -- (NSInteger)numberOfOpenTabs;
|
| -- (NSInteger)numberOfOpenMiniTabs;
|
| -- (NSInteger)numberOfOpenNonMiniTabs;
|
| -- (void)mouseMoved:(NSEvent*)event;
|
| -- (void)setTabTrackingAreasEnabled:(BOOL)enabled;
|
| -- (void)droppingURLsAt:(NSPoint)point
|
| - givesIndex:(NSInteger*)index
|
| - disposition:(WindowOpenDisposition*)disposition;
|
| -- (void)setNewTabButtonHoverState:(BOOL)showHover;
|
| -@end
|
| -
|
| -// A simple view class that prevents the Window Server from dragging the area
|
| -// behind tabs. Sometimes core animation confuses it. Unfortunately, it can also
|
| -// falsely pick up clicks during rapid tab closure, so we have to account for
|
| -// that.
|
| -@interface TabStripControllerDragBlockingView : NSView {
|
| - TabStripController* controller_; // weak; owns us
|
| -}
|
| -
|
| -- (id)initWithFrame:(NSRect)frameRect
|
| - controller:(TabStripController*)controller;
|
| -@end
|
| -
|
| -@implementation TabStripControllerDragBlockingView
|
| -- (BOOL)mouseDownCanMoveWindow {return NO;}
|
| -- (void)drawRect:(NSRect)rect {}
|
| -
|
| -- (id)initWithFrame:(NSRect)frameRect
|
| - controller:(TabStripController*)controller {
|
| - if ((self = [super initWithFrame:frameRect]))
|
| - controller_ = controller;
|
| - return self;
|
| -}
|
| -
|
| -// In "rapid tab closure" mode (i.e., the user is clicking close tab buttons in
|
| -// rapid succession), the animations confuse Cocoa's hit testing (which appears
|
| -// to use cached results, among other tricks), so this view can somehow end up
|
| -// getting a mouse down event. Thus we do an explicit hit test during rapid tab
|
| -// closure, and if we find that we got a mouse down we shouldn't have, we send
|
| -// it off to the appropriate view.
|
| -- (void)mouseDown:(NSEvent*)event {
|
| - if ([controller_ inRapidClosureMode]) {
|
| - NSView* superview = [self superview];
|
| - NSPoint hitLocation =
|
| - [[superview superview] convertPoint:[event locationInWindow]
|
| - fromView:nil];
|
| - NSView* hitView = [superview hitTest:hitLocation];
|
| - if (hitView != self) {
|
| - [hitView mouseDown:event];
|
| - return;
|
| - }
|
| - }
|
| - [super mouseDown:event];
|
| -}
|
| -@end
|
| -
|
| -#pragma mark -
|
| -
|
| -// A delegate, owned by the CAAnimation system, that is alerted when the
|
| -// animation to close a tab is completed. Calls back to the given tab strip
|
| -// to let it know that |controller_| is ready to be removed from the model.
|
| -// Since we only maintain weak references, the tab strip must call -invalidate:
|
| -// to prevent the use of dangling pointers.
|
| -@interface TabCloseAnimationDelegate : NSObject {
|
| - @private
|
| - TabStripController* strip_; // weak; owns us indirectly
|
| - TabController* controller_; // weak
|
| -}
|
| -
|
| -// Will tell |strip| when the animation for |controller|'s view has completed.
|
| -// These should not be nil, and will not be retained.
|
| -- (id)initWithTabStrip:(TabStripController*)strip
|
| - tabController:(TabController*)controller;
|
| -
|
| -// Invalidates this object so that no further calls will be made to
|
| -// |strip_|. This should be called when |strip_| is released, to
|
| -// prevent attempts to call into the released object.
|
| -- (void)invalidate;
|
| -
|
| -// CAAnimation delegate method
|
| -- (void)animationDidStop:(CAAnimation*)animation finished:(BOOL)finished;
|
| -
|
| -@end
|
| -
|
| -@implementation TabCloseAnimationDelegate
|
| -
|
| -- (id)initWithTabStrip:(TabStripController*)strip
|
| - tabController:(TabController*)controller {
|
| - if ((self == [super init])) {
|
| - DCHECK(strip && controller);
|
| - strip_ = strip;
|
| - controller_ = controller;
|
| - }
|
| - return self;
|
| -}
|
| -
|
| -- (void)invalidate {
|
| - strip_ = nil;
|
| - controller_ = nil;
|
| -}
|
| -
|
| -- (void)animationDidStop:(CAAnimation*)animation finished:(BOOL)finished {
|
| - [strip_ animationDidStopForController:controller_ finished:finished];
|
| -}
|
| -
|
| -@end
|
| -
|
| -#pragma mark -
|
| -
|
| -// In general, there is a one-to-one correspondence between TabControllers,
|
| -// TabViews, TabContentsControllers, and the TabContents in the TabStripModel.
|
| -// In the steady-state, the indices line up so an index coming from the model
|
| -// is directly mapped to the same index in the parallel arrays holding our
|
| -// views and controllers. This is also true when new tabs are created (even
|
| -// though there is a small period of animation) because the tab is present
|
| -// in the model while the TabView is animating into place. As a result, nothing
|
| -// special need be done to handle "new tab" animation.
|
| -//
|
| -// This all goes out the window with the "close tab" animation. The animation
|
| -// kicks off in |-tabDetachedWithContents:atIndex:| with the notification that
|
| -// the tab has been removed from the model. The simplest solution at this
|
| -// point would be to remove the views and controllers as well, however once
|
| -// the TabView is removed from the view list, the tab z-order code takes care of
|
| -// removing it from the tab strip and we'll get no animation. That means if
|
| -// there is to be any visible animation, the TabView needs to stay around until
|
| -// its animation is complete. In order to maintain consistency among the
|
| -// internal parallel arrays, this means all structures are kept around until
|
| -// the animation completes. At this point, though, the model and our internal
|
| -// structures are out of sync: the indices no longer line up. As a result,
|
| -// there is a concept of a "model index" which represents an index valid in
|
| -// the TabStripModel. During steady-state, the "model index" is just the same
|
| -// index as our parallel arrays (as above), but during tab close animations,
|
| -// it is different, offset by the number of tabs preceding the index which
|
| -// are undergoing tab closing animation. As a result, the caller needs to be
|
| -// careful to use the available conversion routines when accessing the internal
|
| -// parallel arrays (e.g., -indexFromModelIndex:). Care also needs to be taken
|
| -// during tab layout to ignore closing tabs in the total width calculations and
|
| -// in individual tab positioning (to avoid moving them right back to where they
|
| -// were).
|
| -//
|
| -// In order to prevent actions being taken on tabs which are closing, the tab
|
| -// itself gets marked as such so it no longer will send back its select action
|
| -// or allow itself to be dragged. In addition, drags on the tab strip as a
|
| -// whole are disabled while there are tabs closing.
|
| -
|
| -@implementation TabStripController
|
| -
|
| -@synthesize indentForControls = indentForControls_;
|
| -
|
| -- (id)initWithView:(TabStripView*)view
|
| - switchView:(NSView*)switchView
|
| - browser:(Browser*)browser
|
| - delegate:(id<TabStripControllerDelegate>)delegate {
|
| - DCHECK(view && switchView && browser && delegate);
|
| - if ((self = [super init])) {
|
| - tabStripView_.reset([view retain]);
|
| - switchView_ = switchView;
|
| - browser_ = browser;
|
| - tabStripModel_ = browser_->tabstrip_model();
|
| - delegate_ = delegate;
|
| - bridge_.reset(new TabStripModelObserverBridge(tabStripModel_, self));
|
| - tabContentsArray_.reset([[NSMutableArray alloc] init]);
|
| - tabArray_.reset([[NSMutableArray alloc] init]);
|
| -
|
| - // Important note: any non-tab subviews not added to |permanentSubviews_|
|
| - // (see |-addSubviewToPermanentList:|) will be wiped out.
|
| - permanentSubviews_.reset([[NSMutableArray alloc] init]);
|
| -
|
| - ResourceBundle& rb = ResourceBundle::GetSharedInstance();
|
| - defaultFavIcon_.reset([rb.GetNativeImageNamed(IDR_DEFAULT_FAVICON) retain]);
|
| -
|
| - [self setIndentForControls:[[self class] defaultIndentForControls]];
|
| -
|
| - // TODO(viettrungluu): WTF? "For some reason, if the view is present in the
|
| - // nib a priori, it draws correctly. If we create it in code and add it to
|
| - // the tab view, it draws with all sorts of crazy artifacts."
|
| - newTabButton_ = [view newTabButton];
|
| - [self addSubviewToPermanentList:newTabButton_];
|
| - [newTabButton_ setTarget:nil];
|
| - [newTabButton_ setAction:@selector(commandDispatch:)];
|
| - [newTabButton_ setTag:IDC_NEW_TAB];
|
| - // Set the images from code because Cocoa fails to find them in our sub
|
| - // bundle during tests.
|
| - [newTabButton_ setImage:app::mac::GetCachedImageWithName(kNewTabImage)];
|
| - [newTabButton_ setAlternateImage:
|
| - app::mac::GetCachedImageWithName(kNewTabPressedImage)];
|
| - newTabButtonShowingHoverImage_ = NO;
|
| - newTabTrackingArea_.reset(
|
| - [[NSTrackingArea alloc] initWithRect:[newTabButton_ bounds]
|
| - options:(NSTrackingMouseEnteredAndExited |
|
| - NSTrackingActiveAlways)
|
| - owner:self
|
| - userInfo:nil]);
|
| - [newTabButton_ addTrackingArea:newTabTrackingArea_.get()];
|
| - targetFrames_.reset([[NSMutableDictionary alloc] init]);
|
| -
|
| - dragBlockingView_.reset(
|
| - [[TabStripControllerDragBlockingView alloc] initWithFrame:NSZeroRect
|
| - controller:self]);
|
| - [self addSubviewToPermanentList:dragBlockingView_];
|
| -
|
| - newTabTargetFrame_ = NSMakeRect(0, 0, 0, 0);
|
| - availableResizeWidth_ = kUseFullAvailableWidth;
|
| -
|
| - closingControllers_.reset([[NSMutableSet alloc] init]);
|
| -
|
| - // Install the permanent subviews.
|
| - [self regenerateSubviewList];
|
| -
|
| - // Watch for notifications that the tab strip view has changed size so
|
| - // we can tell it to layout for the new size.
|
| - [[NSNotificationCenter defaultCenter]
|
| - addObserver:self
|
| - selector:@selector(tabViewFrameChanged:)
|
| - name:NSViewFrameDidChangeNotification
|
| - object:tabStripView_];
|
| -
|
| - trackingArea_.reset([[NSTrackingArea alloc]
|
| - initWithRect:NSZeroRect // Ignored by NSTrackingInVisibleRect
|
| - options:NSTrackingMouseEnteredAndExited |
|
| - NSTrackingMouseMoved |
|
| - NSTrackingActiveAlways |
|
| - NSTrackingInVisibleRect
|
| - owner:self
|
| - userInfo:nil]);
|
| - [tabStripView_ addTrackingArea:trackingArea_.get()];
|
| -
|
| - // Check to see if the mouse is currently in our bounds so we can
|
| - // enable the tracking areas. Otherwise we won't get hover states
|
| - // or tab gradients if we load the window up under the mouse.
|
| - NSPoint mouseLoc = [[view window] mouseLocationOutsideOfEventStream];
|
| - mouseLoc = [view convertPoint:mouseLoc fromView:nil];
|
| - if (NSPointInRect(mouseLoc, [view bounds])) {
|
| - [self setTabTrackingAreasEnabled:YES];
|
| - mouseInside_ = YES;
|
| - }
|
| -
|
| - // Set accessibility descriptions. http://openradar.appspot.com/7496255
|
| - NSString* description = l10n_util::GetNSStringWithFixup(IDS_ACCNAME_NEWTAB);
|
| - [[newTabButton_ cell]
|
| - accessibilitySetOverrideValue:description
|
| - forAttribute:NSAccessibilityDescriptionAttribute];
|
| -
|
| - // Controller may have been (re-)created by switching layout modes, which
|
| - // means the tab model is already fully formed with tabs. Need to walk the
|
| - // list and create the UI for each.
|
| - const int existingTabCount = tabStripModel_->count();
|
| - const TabContentsWrapper* selection =
|
| - tabStripModel_->GetSelectedTabContents();
|
| - for (int i = 0; i < existingTabCount; ++i) {
|
| - TabContentsWrapper* currentContents = tabStripModel_->GetTabContentsAt(i);
|
| - [self insertTabWithContents:currentContents
|
| - atIndex:i
|
| - inForeground:NO];
|
| - if (selection == currentContents) {
|
| - // Must manually force a selection since the model won't send
|
| - // selection messages in this scenario.
|
| - [self selectTabWithContents:currentContents
|
| - previousContents:NULL
|
| - atIndex:i
|
| - userGesture:NO];
|
| - }
|
| - }
|
| - // Don't lay out the tabs until after the controller has been fully
|
| - // constructed. The |verticalLayout_| flag has not been initialized by
|
| - // subclasses at this point, which would cause layout to potentially use
|
| - // the wrong mode.
|
| - if (existingTabCount) {
|
| - [self performSelectorOnMainThread:@selector(layoutTabs)
|
| - withObject:nil
|
| - waitUntilDone:NO];
|
| - }
|
| - }
|
| - return self;
|
| -}
|
| -
|
| -- (void)dealloc {
|
| - if (trackingArea_.get())
|
| - [tabStripView_ removeTrackingArea:trackingArea_.get()];
|
| -
|
| - [newTabButton_ removeTrackingArea:newTabTrackingArea_.get()];
|
| - // Invalidate all closing animations so they don't call back to us after
|
| - // we're gone.
|
| - for (TabController* controller in closingControllers_.get()) {
|
| - NSView* view = [controller view];
|
| - [[[view animationForKey:@"frameOrigin"] delegate] invalidate];
|
| - }
|
| - [[NSNotificationCenter defaultCenter] removeObserver:self];
|
| - [super dealloc];
|
| -}
|
| -
|
| -+ (CGFloat)defaultTabHeight {
|
| - return 25.0;
|
| -}
|
| -
|
| -+ (CGFloat)defaultIndentForControls {
|
| - // Default indentation leaves enough room so tabs don't overlap with the
|
| - // window controls.
|
| - return 68.0;
|
| -}
|
| -
|
| -// Finds the TabContentsController associated with the given index into the tab
|
| -// model and swaps out the sole child of the contentArea to display its
|
| -// contents.
|
| -- (void)swapInTabAtIndex:(NSInteger)modelIndex {
|
| - DCHECK(modelIndex >= 0 && modelIndex < tabStripModel_->count());
|
| - NSInteger index = [self indexFromModelIndex:modelIndex];
|
| - TabContentsController* controller = [tabContentsArray_ objectAtIndex:index];
|
| -
|
| - // Resize the new view to fit the window. Calling |view| may lazily
|
| - // instantiate the TabContentsController from the nib. Until we call
|
| - // |-ensureContentsVisible|, the controller doesn't install the RWHVMac into
|
| - // the view hierarchy. This is in order to avoid sending the renderer a
|
| - // spurious default size loaded from the nib during the call to |-view|.
|
| - NSView* newView = [controller view];
|
| -
|
| - // Turns content autoresizing off, so removing and inserting views won't
|
| - // trigger unnecessary content relayout.
|
| - [controller ensureContentsSizeDoesNotChange];
|
| -
|
| - // Remove the old view from the view hierarchy. We know there's only one
|
| - // child of |switchView_| because we're the one who put it there. There
|
| - // may not be any children in the case of a tab that's been closed, in
|
| - // which case there's no swapping going on.
|
| - NSArray* subviews = [switchView_ subviews];
|
| - if ([subviews count]) {
|
| - NSView* oldView = [subviews objectAtIndex:0];
|
| - // Set newView frame to the oldVew frame to prevent NSSplitView hosting
|
| - // sidebar and tab content from resizing sidebar's content view.
|
| - // ensureContentsVisible (see below) sets content size and autoresizing
|
| - // properties.
|
| - [newView setFrame:[oldView frame]];
|
| - [switchView_ replaceSubview:oldView with:newView];
|
| - } else {
|
| - [newView setFrame:[switchView_ bounds]];
|
| - [switchView_ addSubview:newView];
|
| - }
|
| -
|
| - // New content is in place, delegate should adjust itself accordingly.
|
| - [delegate_ onSelectTabWithContents:[controller tabContents]];
|
| -
|
| - // It also restores content autoresizing properties.
|
| - [controller ensureContentsVisible];
|
| -
|
| - // Make sure the new tabs's sheets are visible (necessary when a background
|
| - // tab opened a sheet while it was in the background and now becomes active).
|
| - TabContentsWrapper* newTab = tabStripModel_->GetTabContentsAt(modelIndex);
|
| - DCHECK(newTab);
|
| - if (newTab) {
|
| - TabContents::ConstrainedWindowList::iterator it, end;
|
| - end = newTab->tab_contents()->constrained_window_end();
|
| - NSWindowController* controller = [[newView window] windowController];
|
| - DCHECK([controller isKindOfClass:[BrowserWindowController class]]);
|
| -
|
| - for (it = newTab->tab_contents()->constrained_window_begin();
|
| - it != end;
|
| - ++it) {
|
| - ConstrainedWindow* constrainedWindow = *it;
|
| - static_cast<ConstrainedWindowMac*>(constrainedWindow)->Realize(
|
| - static_cast<BrowserWindowController*>(controller));
|
| - }
|
| - }
|
| -
|
| - // Tell per-tab sheet manager about currently selected tab.
|
| - if (sheetController_.get()) {
|
| - [sheetController_ setActiveView:newView];
|
| - }
|
| -}
|
| -
|
| -// Create a new tab view and set its cell correctly so it draws the way we want
|
| -// it to. It will be sized and positioned by |-layoutTabs| so there's no need to
|
| -// set the frame here. This also creates the view as hidden, it will be
|
| -// shown during layout.
|
| -- (TabController*)newTab {
|
| - TabController* controller = [[[TabController alloc] init] autorelease];
|
| - [controller setTarget:self];
|
| - [controller setAction:@selector(selectTab:)];
|
| - [[controller view] setHidden:YES];
|
| -
|
| - return controller;
|
| -}
|
| -
|
| -// (Private) Returns the number of open tabs in the tab strip. This is the
|
| -// number of TabControllers we know about (as there's a 1-to-1 mapping from
|
| -// these controllers to a tab) less the number of closing tabs.
|
| -- (NSInteger)numberOfOpenTabs {
|
| - return static_cast<NSInteger>(tabStripModel_->count());
|
| -}
|
| -
|
| -// (Private) Returns the number of open, mini-tabs.
|
| -- (NSInteger)numberOfOpenMiniTabs {
|
| - // Ask the model for the number of mini tabs. Note that tabs which are in
|
| - // the process of closing (i.e., whose controllers are in
|
| - // |closingControllers_|) have already been removed from the model.
|
| - return tabStripModel_->IndexOfFirstNonMiniTab();
|
| -}
|
| -
|
| -// (Private) Returns the number of open, non-mini tabs.
|
| -- (NSInteger)numberOfOpenNonMiniTabs {
|
| - NSInteger number = [self numberOfOpenTabs] - [self numberOfOpenMiniTabs];
|
| - DCHECK_GE(number, 0);
|
| - return number;
|
| -}
|
| -
|
| -// Given an index into the tab model, returns the index into the tab controller
|
| -// or tab contents controller array accounting for tabs that are currently
|
| -// closing. For example, if there are two tabs in the process of closing before
|
| -// |index|, this returns |index| + 2. If there are no closing tabs, this will
|
| -// return |index|.
|
| -- (NSInteger)indexFromModelIndex:(NSInteger)index {
|
| - DCHECK(index >= 0);
|
| - if (index < 0)
|
| - return index;
|
| -
|
| - NSInteger i = 0;
|
| - for (TabController* controller in tabArray_.get()) {
|
| - if ([closingControllers_ containsObject:controller]) {
|
| - DCHECK([(TabView*)[controller view] isClosing]);
|
| - ++index;
|
| - }
|
| - if (i == index) // No need to check anything after, it has no effect.
|
| - break;
|
| - ++i;
|
| - }
|
| - return index;
|
| -}
|
| -
|
| -
|
| -// Returns the index of the subview |view|. Returns -1 if not present. Takes
|
| -// closing tabs into account such that this index will correctly match the tab
|
| -// model. If |view| is in the process of closing, returns -1, as closing tabs
|
| -// are no longer in the model.
|
| -- (NSInteger)modelIndexForTabView:(NSView*)view {
|
| - NSInteger index = 0;
|
| - for (TabController* current in tabArray_.get()) {
|
| - // If |current| is closing, skip it.
|
| - if ([closingControllers_ containsObject:current])
|
| - continue;
|
| - else if ([current view] == view)
|
| - return index;
|
| - ++index;
|
| - }
|
| - return -1;
|
| -}
|
| -
|
| -// Returns the index of the contents subview |view|. Returns -1 if not present.
|
| -// Takes closing tabs into account such that this index will correctly match the
|
| -// tab model. If |view| is in the process of closing, returns -1, as closing
|
| -// tabs are no longer in the model.
|
| -- (NSInteger)modelIndexForContentsView:(NSView*)view {
|
| - NSInteger index = 0;
|
| - NSInteger i = 0;
|
| - for (TabContentsController* current in tabContentsArray_.get()) {
|
| - // If the TabController corresponding to |current| is closing, skip it.
|
| - TabController* controller = [tabArray_ objectAtIndex:i];
|
| - if ([closingControllers_ containsObject:controller]) {
|
| - ++i;
|
| - continue;
|
| - } else if ([current view] == view) {
|
| - return index;
|
| - }
|
| - ++index;
|
| - ++i;
|
| - }
|
| - return -1;
|
| -}
|
| -
|
| -
|
| -// Returns the view at the given index, using the array of TabControllers to
|
| -// get the associated view. Returns nil if out of range.
|
| -- (NSView*)viewAtIndex:(NSUInteger)index {
|
| - if (index >= [tabArray_ count])
|
| - return NULL;
|
| - return [[tabArray_ objectAtIndex:index] view];
|
| -}
|
| -
|
| -- (NSUInteger)viewsCount {
|
| - return [tabArray_ count];
|
| -}
|
| -
|
| -// Called when the user clicks a tab. Tell the model the selection has changed,
|
| -// which feeds back into us via a notification.
|
| -- (void)selectTab:(id)sender {
|
| - DCHECK([sender isKindOfClass:[NSView class]]);
|
| - int index = [self modelIndexForTabView:sender];
|
| - if (tabStripModel_->ContainsIndex(index))
|
| - tabStripModel_->SelectTabContentsAt(index, true);
|
| -}
|
| -
|
| -// Called when the user closes a tab. Asks the model to close the tab. |sender|
|
| -// is the TabView that is potentially going away.
|
| -- (void)closeTab:(id)sender {
|
| - DCHECK([sender isKindOfClass:[TabView class]]);
|
| - if ([hoveredTab_ isEqual:sender]) {
|
| - hoveredTab_ = nil;
|
| - }
|
| -
|
| - NSInteger index = [self modelIndexForTabView:sender];
|
| - if (!tabStripModel_->ContainsIndex(index))
|
| - return;
|
| -
|
| - TabContentsWrapper* contents = tabStripModel_->GetTabContentsAt(index);
|
| - if (contents)
|
| - UserMetrics::RecordAction(UserMetricsAction("CloseTab_Mouse"),
|
| - contents->tab_contents()->profile());
|
| - const NSInteger numberOfOpenTabs = [self numberOfOpenTabs];
|
| - if (numberOfOpenTabs > 1) {
|
| - bool isClosingLastTab = index == numberOfOpenTabs - 1;
|
| - if (!isClosingLastTab) {
|
| - // Limit the width available for laying out tabs so that tabs are not
|
| - // resized until a later time (when the mouse leaves the tab strip).
|
| - // However, if the tab being closed is a pinned tab, break out of
|
| - // rapid-closure mode since the mouse is almost guaranteed not to be over
|
| - // the closebox of the adjacent tab (due to the difference in widths).
|
| - // TODO(pinkerton): re-visit when handling tab overflow.
|
| - // http://crbug.com/188
|
| - if (tabStripModel_->IsTabPinned(index)) {
|
| - availableResizeWidth_ = kUseFullAvailableWidth;
|
| - } else {
|
| - NSView* penultimateTab = [self viewAtIndex:numberOfOpenTabs - 2];
|
| - availableResizeWidth_ = NSMaxX([penultimateTab frame]);
|
| - }
|
| - } else {
|
| - // If the rightmost tab is closed, change the available width so that
|
| - // another tab's close button lands below the cursor (assuming the tabs
|
| - // are currently below their maximum width and can grow).
|
| - NSView* lastTab = [self viewAtIndex:numberOfOpenTabs - 1];
|
| - availableResizeWidth_ = NSMaxX([lastTab frame]);
|
| - }
|
| - tabStripModel_->CloseTabContentsAt(
|
| - index,
|
| - TabStripModel::CLOSE_USER_GESTURE |
|
| - TabStripModel::CLOSE_CREATE_HISTORICAL_TAB);
|
| - } else {
|
| - // Use the standard window close if this is the last tab
|
| - // this prevents the tab from being removed from the model until after
|
| - // the window dissapears
|
| - [[tabStripView_ window] performClose:nil];
|
| - }
|
| -}
|
| -
|
| -// Dispatch context menu commands for the given tab controller.
|
| -- (void)commandDispatch:(TabStripModel::ContextMenuCommand)command
|
| - forController:(TabController*)controller {
|
| - int index = [self modelIndexForTabView:[controller view]];
|
| - if (tabStripModel_->ContainsIndex(index))
|
| - tabStripModel_->ExecuteContextMenuCommand(index, command);
|
| -}
|
| -
|
| -// Returns YES if the specificed command should be enabled for the given
|
| -// controller.
|
| -- (BOOL)isCommandEnabled:(TabStripModel::ContextMenuCommand)command
|
| - forController:(TabController*)controller {
|
| - int index = [self modelIndexForTabView:[controller view]];
|
| - if (!tabStripModel_->ContainsIndex(index))
|
| - return NO;
|
| - return tabStripModel_->IsContextMenuCommandEnabled(index, command) ? YES : NO;
|
| -}
|
| -
|
| -- (void)insertPlaceholderForTab:(TabView*)tab
|
| - frame:(NSRect)frame
|
| - yStretchiness:(CGFloat)yStretchiness {
|
| - placeholderTab_ = tab;
|
| - placeholderFrame_ = frame;
|
| - placeholderStretchiness_ = yStretchiness;
|
| - [self layoutTabsWithAnimation:initialLayoutComplete_ regenerateSubviews:NO];
|
| -}
|
| -
|
| -- (BOOL)isDragSessionActive {
|
| - return placeholderTab_ != nil;
|
| -}
|
| -
|
| -- (BOOL)isTabFullyVisible:(TabView*)tab {
|
| - NSRect frame = [tab frame];
|
| - return NSMinX(frame) >= [self indentForControls] &&
|
| - NSMaxX(frame) <= NSMaxX([tabStripView_ frame]);
|
| -}
|
| -
|
| -- (void)showNewTabButton:(BOOL)show {
|
| - forceNewTabButtonHidden_ = show ? NO : YES;
|
| - if (forceNewTabButtonHidden_)
|
| - [newTabButton_ setHidden:YES];
|
| -}
|
| -
|
| -// Lay out all tabs in the order of their TabContentsControllers, which matches
|
| -// the ordering in the TabStripModel. This call isn't that expensive, though
|
| -// it is O(n) in the number of tabs. Tabs will animate to their new position
|
| -// if the window is visible and |animate| is YES.
|
| -// TODO(pinkerton): Note this doesn't do too well when the number of min-sized
|
| -// tabs would cause an overflow. http://crbug.com/188
|
| -- (void)layoutTabsWithAnimation:(BOOL)animate
|
| - regenerateSubviews:(BOOL)doUpdate {
|
| - DCHECK([NSThread isMainThread]);
|
| - if (![tabArray_ count])
|
| - return;
|
| -
|
| - const CGFloat kMaxTabWidth = [TabController maxTabWidth];
|
| - const CGFloat kMinTabWidth = [TabController minTabWidth];
|
| - const CGFloat kMinSelectedTabWidth = [TabController minSelectedTabWidth];
|
| - const CGFloat kMiniTabWidth = [TabController miniTabWidth];
|
| - const CGFloat kAppTabWidth = [TabController appTabWidth];
|
| -
|
| - NSRect enclosingRect = NSZeroRect;
|
| - ScopedNSAnimationContextGroup mainAnimationGroup(animate);
|
| - mainAnimationGroup.SetCurrentContextDuration(kAnimationDuration);
|
| -
|
| - // Update the current subviews and their z-order if requested.
|
| - if (doUpdate)
|
| - [self regenerateSubviewList];
|
| -
|
| - // Compute the base width of tabs given how much room we're allowed. Note that
|
| - // mini-tabs have a fixed width. We may not be able to use the entire width
|
| - // if the user is quickly closing tabs. This may be negative, but that's okay
|
| - // (taken care of by |MAX()| when calculating tab sizes).
|
| - CGFloat availableSpace = 0;
|
| - if (verticalLayout_) {
|
| - availableSpace = NSHeight([tabStripView_ bounds]);
|
| - } else {
|
| - if ([self inRapidClosureMode]) {
|
| - availableSpace = availableResizeWidth_;
|
| - } else {
|
| - availableSpace = NSWidth([tabStripView_ frame]);
|
| - // Account for the new tab button and the incognito badge.
|
| - availableSpace -= NSWidth([newTabButton_ frame]) + kNewTabButtonOffset;
|
| - if (browser_->profile()->IsOffTheRecord())
|
| - availableSpace -= kIncognitoBadgeTabStripShrink;
|
| - }
|
| - availableSpace -= [self indentForControls];
|
| - }
|
| -
|
| - // This may be negative, but that's okay (taken care of by |MAX()| when
|
| - // calculating tab sizes). "mini" tabs in horizontal mode just get a special
|
| - // section, they don't change size.
|
| - CGFloat availableSpaceForNonMini = availableSpace;
|
| - if (!verticalLayout_) {
|
| - availableSpaceForNonMini -=
|
| - [self numberOfOpenMiniTabs] * (kMiniTabWidth - kTabOverlap);
|
| - }
|
| -
|
| - // Initialize |nonMiniTabWidth| in case there aren't any non-mini-tabs; this
|
| - // value shouldn't actually be used.
|
| - CGFloat nonMiniTabWidth = kMaxTabWidth;
|
| - const NSInteger numberOfOpenNonMiniTabs = [self numberOfOpenNonMiniTabs];
|
| - if (!verticalLayout_ && numberOfOpenNonMiniTabs) {
|
| - // Find the width of a non-mini-tab. This only applies to horizontal
|
| - // mode. Add in the amount we "get back" from the tabs overlapping.
|
| - availableSpaceForNonMini += (numberOfOpenNonMiniTabs - 1) * kTabOverlap;
|
| -
|
| - // Divide up the space between the non-mini-tabs.
|
| - nonMiniTabWidth = availableSpaceForNonMini / numberOfOpenNonMiniTabs;
|
| -
|
| - // Clamp the width between the max and min.
|
| - nonMiniTabWidth = MAX(MIN(nonMiniTabWidth, kMaxTabWidth), kMinTabWidth);
|
| - }
|
| -
|
| - BOOL visible = [[tabStripView_ window] isVisible];
|
| -
|
| - CGFloat offset = [self indentForControls];
|
| - bool hasPlaceholderGap = false;
|
| - for (TabController* tab in tabArray_.get()) {
|
| - // Ignore a tab that is going through a close animation.
|
| - if ([closingControllers_ containsObject:tab])
|
| - continue;
|
| -
|
| - BOOL isPlaceholder = [[tab view] isEqual:placeholderTab_];
|
| - NSRect tabFrame = [[tab view] frame];
|
| - tabFrame.size.height = [[self class] defaultTabHeight] + 1;
|
| - if (verticalLayout_) {
|
| - tabFrame.origin.y = availableSpace - tabFrame.size.height - offset;
|
| - tabFrame.origin.x = 0;
|
| - } else {
|
| - tabFrame.origin.y = 0;
|
| - tabFrame.origin.x = offset;
|
| - }
|
| - // If the tab is hidden, we consider it a new tab. We make it visible
|
| - // and animate it in.
|
| - BOOL newTab = [[tab view] isHidden];
|
| - if (newTab)
|
| - [[tab view] setHidden:NO];
|
| -
|
| - if (isPlaceholder) {
|
| - // Move the current tab to the correct location instantly.
|
| - // We need a duration or else it doesn't cancel an inflight animation.
|
| - ScopedNSAnimationContextGroup localAnimationGroup(animate);
|
| - localAnimationGroup.SetCurrentContextShortestDuration();
|
| - if (verticalLayout_)
|
| - tabFrame.origin.y = availableSpace - tabFrame.size.height - offset;
|
| - else
|
| - tabFrame.origin.x = placeholderFrame_.origin.x;
|
| - // TODO(alcor): reenable this
|
| - //tabFrame.size.height += 10.0 * placeholderStretchiness_;
|
| - id target = animate ? [[tab view] animator] : [tab view];
|
| - [target setFrame:tabFrame];
|
| -
|
| - // Store the frame by identifier to aviod redundant calls to animator.
|
| - NSValue* identifier = [NSValue valueWithPointer:[tab view]];
|
| - [targetFrames_ setObject:[NSValue valueWithRect:tabFrame]
|
| - forKey:identifier];
|
| - continue;
|
| - }
|
| -
|
| - if (placeholderTab_ && !hasPlaceholderGap) {
|
| - const CGFloat placeholderMin =
|
| - verticalLayout_ ? NSMinY(placeholderFrame_) :
|
| - NSMinX(placeholderFrame_);
|
| - if (verticalLayout_) {
|
| - if (NSMidY(tabFrame) > placeholderMin) {
|
| - hasPlaceholderGap = true;
|
| - offset += NSHeight(placeholderFrame_);
|
| - tabFrame.origin.y = availableSpace - tabFrame.size.height - offset;
|
| - }
|
| - } else {
|
| - // If the left edge is to the left of the placeholder's left, but the
|
| - // mid is to the right of it slide over to make space for it.
|
| - if (NSMidX(tabFrame) > placeholderMin) {
|
| - hasPlaceholderGap = true;
|
| - offset += NSWidth(placeholderFrame_);
|
| - offset -= kTabOverlap;
|
| - tabFrame.origin.x = offset;
|
| - }
|
| - }
|
| - }
|
| -
|
| - // Set the width. Selected tabs are slightly wider when things get really
|
| - // small and thus we enforce a different minimum width.
|
| - tabFrame.size.width = [tab mini] ?
|
| - ([tab app] ? kAppTabWidth : kMiniTabWidth) : nonMiniTabWidth;
|
| - if ([tab selected])
|
| - tabFrame.size.width = MAX(tabFrame.size.width, kMinSelectedTabWidth);
|
| -
|
| - // Animate a new tab in by putting it below the horizon unless told to put
|
| - // it in a specific location (i.e., from a drop).
|
| - // TODO(pinkerton): figure out vertical tab animations.
|
| - if (newTab && visible && animate) {
|
| - if (NSEqualRects(droppedTabFrame_, NSZeroRect)) {
|
| - [[tab view] setFrame:NSOffsetRect(tabFrame, 0, -NSHeight(tabFrame))];
|
| - } else {
|
| - [[tab view] setFrame:droppedTabFrame_];
|
| - droppedTabFrame_ = NSZeroRect;
|
| - }
|
| - }
|
| -
|
| - // Check the frame by identifier to avoid redundant calls to animator.
|
| - id frameTarget = visible && animate ? [[tab view] animator] : [tab view];
|
| - NSValue* identifier = [NSValue valueWithPointer:[tab view]];
|
| - NSValue* oldTargetValue = [targetFrames_ objectForKey:identifier];
|
| - if (!oldTargetValue ||
|
| - !NSEqualRects([oldTargetValue rectValue], tabFrame)) {
|
| - [frameTarget setFrame:tabFrame];
|
| - [targetFrames_ setObject:[NSValue valueWithRect:tabFrame]
|
| - forKey:identifier];
|
| - }
|
| -
|
| - enclosingRect = NSUnionRect(tabFrame, enclosingRect);
|
| -
|
| - if (verticalLayout_) {
|
| - offset += NSHeight(tabFrame);
|
| - } else {
|
| - offset += NSWidth(tabFrame);
|
| - offset -= kTabOverlap;
|
| - }
|
| - }
|
| -
|
| - // Hide the new tab button if we're explicitly told to. It may already
|
| - // be hidden, doing it again doesn't hurt. Otherwise position it
|
| - // appropriately, showing it if necessary.
|
| - if (forceNewTabButtonHidden_) {
|
| - [newTabButton_ setHidden:YES];
|
| - } else {
|
| - NSRect newTabNewFrame = [newTabButton_ frame];
|
| - // We've already ensured there's enough space for the new tab button
|
| - // so we don't have to check it against the available space. We do need
|
| - // to make sure we put it after any placeholder.
|
| - CGFloat maxTabX = MAX(offset, NSMaxX(placeholderFrame_) - kTabOverlap);
|
| - newTabNewFrame.origin = NSMakePoint(maxTabX + kNewTabButtonOffset, 0);
|
| - if ([tabContentsArray_ count])
|
| - [newTabButton_ setHidden:NO];
|
| -
|
| - if (!NSEqualRects(newTabTargetFrame_, newTabNewFrame)) {
|
| - // Set the new tab button image correctly based on where the cursor is.
|
| - NSWindow* window = [tabStripView_ window];
|
| - NSPoint currentMouse = [window mouseLocationOutsideOfEventStream];
|
| - currentMouse = [tabStripView_ convertPoint:currentMouse fromView:nil];
|
| -
|
| - BOOL shouldShowHover = [newTabButton_ pointIsOverButton:currentMouse];
|
| - [self setNewTabButtonHoverState:shouldShowHover];
|
| -
|
| - // Move the new tab button into place. We want to animate the new tab
|
| - // button if it's moving to the left (closing a tab), but not when it's
|
| - // moving to the right (inserting a new tab). If moving right, we need
|
| - // to use a very small duration to make sure we cancel any in-flight
|
| - // animation to the left.
|
| - if (visible && animate) {
|
| - ScopedNSAnimationContextGroup localAnimationGroup(true);
|
| - BOOL movingLeft = NSMinX(newTabNewFrame) < NSMinX(newTabTargetFrame_);
|
| - if (!movingLeft) {
|
| - localAnimationGroup.SetCurrentContextShortestDuration();
|
| - }
|
| - [[newTabButton_ animator] setFrame:newTabNewFrame];
|
| - newTabTargetFrame_ = newTabNewFrame;
|
| - } else {
|
| - [newTabButton_ setFrame:newTabNewFrame];
|
| - newTabTargetFrame_ = newTabNewFrame;
|
| - }
|
| - }
|
| - }
|
| -
|
| - [dragBlockingView_ setFrame:enclosingRect];
|
| -
|
| - // Mark that we've successfully completed layout of at least one tab.
|
| - initialLayoutComplete_ = YES;
|
| -}
|
| -
|
| -// When we're told to layout from the public API we usually want to animate,
|
| -// except when it's the first time.
|
| -- (void)layoutTabs {
|
| - [self layoutTabsWithAnimation:initialLayoutComplete_ regenerateSubviews:YES];
|
| -}
|
| -
|
| -// Handles setting the title of the tab based on the given |contents|. Uses
|
| -// a canned string if |contents| is NULL.
|
| -- (void)setTabTitle:(NSViewController*)tab withContents:(TabContents*)contents {
|
| - NSString* titleString = nil;
|
| - if (contents)
|
| - titleString = base::SysUTF16ToNSString(contents->GetTitle());
|
| - if (![titleString length]) {
|
| - titleString = l10n_util::GetNSString(IDS_BROWSER_WINDOW_MAC_TAB_UNTITLED);
|
| - }
|
| - [tab setTitle:titleString];
|
| -}
|
| -
|
| -// Called when a notification is received from the model to insert a new tab
|
| -// at |modelIndex|.
|
| -- (void)insertTabWithContents:(TabContentsWrapper*)contents
|
| - atIndex:(NSInteger)modelIndex
|
| - inForeground:(bool)inForeground {
|
| - DCHECK(contents);
|
| - DCHECK(modelIndex == TabStripModel::kNoTab ||
|
| - tabStripModel_->ContainsIndex(modelIndex));
|
| -
|
| - // Take closing tabs into account.
|
| - NSInteger index = [self indexFromModelIndex:modelIndex];
|
| -
|
| - // Make a new tab. Load the contents of this tab from the nib and associate
|
| - // the new controller with |contents| so it can be looked up later.
|
| - scoped_nsobject<TabContentsController> contentsController(
|
| - [[TabContentsController alloc] initWithContents:contents->tab_contents()
|
| - delegate:self]);
|
| - [tabContentsArray_ insertObject:contentsController atIndex:index];
|
| -
|
| - // Make a new tab and add it to the strip. Keep track of its controller.
|
| - TabController* newController = [self newTab];
|
| - [newController setMini:tabStripModel_->IsMiniTab(modelIndex)];
|
| - [newController setPinned:tabStripModel_->IsTabPinned(modelIndex)];
|
| - [newController setApp:tabStripModel_->IsAppTab(modelIndex)];
|
| - [tabArray_ insertObject:newController atIndex:index];
|
| - NSView* newView = [newController view];
|
| -
|
| - // Set the originating frame to just below the strip so that it animates
|
| - // upwards as it's being initially layed out. Oddly, this works while doing
|
| - // something similar in |-layoutTabs| confuses the window server.
|
| - [newView setFrame:NSOffsetRect([newView frame],
|
| - 0, -[[self class] defaultTabHeight])];
|
| -
|
| - [self setTabTitle:newController withContents:contents->tab_contents()];
|
| -
|
| - // If a tab is being inserted, we can again use the entire tab strip width
|
| - // for layout.
|
| - availableResizeWidth_ = kUseFullAvailableWidth;
|
| -
|
| - // We don't need to call |-layoutTabs| if the tab will be in the foreground
|
| - // because it will get called when the new tab is selected by the tab model.
|
| - // Whenever |-layoutTabs| is called, it'll also add the new subview.
|
| - if (!inForeground) {
|
| - [self layoutTabs];
|
| - }
|
| -
|
| - // During normal loading, we won't yet have a favicon and we'll get
|
| - // subsequent state change notifications to show the throbber, but when we're
|
| - // dragging a tab out into a new window, we have to put the tab's favicon
|
| - // into the right state up front as we won't be told to do it from anywhere
|
| - // else.
|
| - [self updateFavIconForContents:contents->tab_contents() atIndex:modelIndex];
|
| -
|
| - // Send a broadcast that the number of tabs have changed.
|
| - [[NSNotificationCenter defaultCenter]
|
| - postNotificationName:kTabStripNumberOfTabsChanged
|
| - object:self];
|
| -}
|
| -
|
| -// Called when a notification is received from the model to select a particular
|
| -// tab. Swaps in the toolbar and content area associated with |newContents|.
|
| -- (void)selectTabWithContents:(TabContentsWrapper*)newContents
|
| - previousContents:(TabContentsWrapper*)oldContents
|
| - atIndex:(NSInteger)modelIndex
|
| - userGesture:(bool)wasUserGesture {
|
| - // Take closing tabs into account.
|
| - NSInteger index = [self indexFromModelIndex:modelIndex];
|
| -
|
| - if (oldContents) {
|
| - int oldModelIndex =
|
| - browser_->GetIndexOfController(&(oldContents->controller()));
|
| - if (oldModelIndex != -1) { // When closing a tab, the old tab may be gone.
|
| - NSInteger oldIndex = [self indexFromModelIndex:oldModelIndex];
|
| - TabContentsController* oldController =
|
| - [tabContentsArray_ objectAtIndex:oldIndex];
|
| - [oldController willBecomeUnselectedTab];
|
| - oldContents->view()->StoreFocus();
|
| - oldContents->tab_contents()->WasHidden();
|
| - }
|
| - }
|
| -
|
| - // De-select all other tabs and select the new tab.
|
| - int i = 0;
|
| - for (TabController* current in tabArray_.get()) {
|
| - [current setSelected:(i == index) ? YES : NO];
|
| - ++i;
|
| - }
|
| -
|
| - // Tell the new tab contents it is about to become the selected tab. Here it
|
| - // can do things like make sure the toolbar is up to date.
|
| - TabContentsController* newController =
|
| - [tabContentsArray_ objectAtIndex:index];
|
| - [newController willBecomeSelectedTab];
|
| -
|
| - // Relayout for new tabs and to let the selected tab grow to be larger in
|
| - // size than surrounding tabs if the user has many. This also raises the
|
| - // selected tab to the top.
|
| - [self layoutTabs];
|
| -
|
| - // Swap in the contents for the new tab.
|
| - [self swapInTabAtIndex:modelIndex];
|
| -
|
| - if (newContents) {
|
| - newContents->tab_contents()->DidBecomeSelected();
|
| - newContents->view()->RestoreFocus();
|
| -
|
| - if (newContents->tab_contents()->find_ui_active())
|
| - browser_->GetFindBarController()->find_bar()->SetFocusAndSelection();
|
| - }
|
| -}
|
| -
|
| -- (void)tabReplacedWithContents:(TabContentsWrapper*)newContents
|
| - previousContents:(TabContentsWrapper*)oldContents
|
| - atIndex:(NSInteger)modelIndex {
|
| - NSInteger index = [self indexFromModelIndex:modelIndex];
|
| - TabContentsController* oldController =
|
| - [tabContentsArray_ objectAtIndex:index];
|
| - DCHECK_EQ(oldContents->tab_contents(), [oldController tabContents]);
|
| -
|
| - // Simply create a new TabContentsController for |newContents| and place it
|
| - // into the array, replacing |oldContents|. A TabSelectedAt notification will
|
| - // follow, at which point we will install the new view.
|
| - scoped_nsobject<TabContentsController> newController(
|
| - [[TabContentsController alloc]
|
| - initWithContents:newContents->tab_contents()
|
| - delegate:self]);
|
| -
|
| - // Bye bye, |oldController|.
|
| - [tabContentsArray_ replaceObjectAtIndex:index withObject:newController];
|
| -
|
| - [delegate_ onReplaceTabWithContents:newContents->tab_contents()];
|
| -
|
| - // Fake a tab changed notification to force tab titles and favicons to update.
|
| - [self tabChangedWithContents:newContents
|
| - atIndex:modelIndex
|
| - changeType:TabStripModelObserver::ALL];
|
| -}
|
| -
|
| -// Remove all knowledge about this tab and its associated controller, and remove
|
| -// the view from the strip.
|
| -- (void)removeTab:(TabController*)controller {
|
| - NSUInteger index = [tabArray_ indexOfObject:controller];
|
| -
|
| - // Release the tab contents controller so those views get destroyed. This
|
| - // will remove all the tab content Cocoa views from the hierarchy. A
|
| - // subsequent "select tab" notification will follow from the model. To
|
| - // tell us what to swap in in its absence.
|
| - [tabContentsArray_ removeObjectAtIndex:index];
|
| -
|
| - // Remove the view from the tab strip.
|
| - NSView* tab = [controller view];
|
| - [tab removeFromSuperview];
|
| -
|
| - // Remove ourself as an observer.
|
| - [[NSNotificationCenter defaultCenter]
|
| - removeObserver:self
|
| - name:NSViewDidUpdateTrackingAreasNotification
|
| - object:tab];
|
| -
|
| - // Clear the tab controller's target.
|
| - // TODO(viettrungluu): [crbug.com/23829] Find a better way to handle the tab
|
| - // controller's target.
|
| - [controller setTarget:nil];
|
| -
|
| - if ([hoveredTab_ isEqual:tab])
|
| - hoveredTab_ = nil;
|
| -
|
| - NSValue* identifier = [NSValue valueWithPointer:tab];
|
| - [targetFrames_ removeObjectForKey:identifier];
|
| -
|
| - // Once we're totally done with the tab, delete its controller
|
| - [tabArray_ removeObjectAtIndex:index];
|
| -}
|
| -
|
| -// Called by the CAAnimation delegate when the tab completes the closing
|
| -// animation.
|
| -- (void)animationDidStopForController:(TabController*)controller
|
| - finished:(BOOL)finished {
|
| - [closingControllers_ removeObject:controller];
|
| - [self removeTab:controller];
|
| -}
|
| -
|
| -// Save off which TabController is closing and tell its view's animator
|
| -// where to move the tab to. Registers a delegate to call back when the
|
| -// animation is complete in order to remove the tab from the model.
|
| -- (void)startClosingTabWithAnimation:(TabController*)closingTab {
|
| - DCHECK([NSThread isMainThread]);
|
| - // Save off the controller into the set of animating tabs. This alerts
|
| - // the layout method to not do anything with it and allows us to correctly
|
| - // calculate offsets when working with indices into the model.
|
| - [closingControllers_ addObject:closingTab];
|
| -
|
| - // Mark the tab as closing. This prevents it from generating any drags or
|
| - // selections while it's animating closed.
|
| - [(TabView*)[closingTab view] setClosing:YES];
|
| -
|
| - // Register delegate (owned by the animation system).
|
| - NSView* tabView = [closingTab view];
|
| - CAAnimation* animation = [[tabView animationForKey:@"frameOrigin"] copy];
|
| - [animation autorelease];
|
| - scoped_nsobject<TabCloseAnimationDelegate> delegate(
|
| - [[TabCloseAnimationDelegate alloc] initWithTabStrip:self
|
| - tabController:closingTab]);
|
| - [animation setDelegate:delegate.get()]; // Retains delegate.
|
| - NSMutableDictionary* animationDictionary =
|
| - [NSMutableDictionary dictionaryWithDictionary:[tabView animations]];
|
| - [animationDictionary setObject:animation forKey:@"frameOrigin"];
|
| - [tabView setAnimations:animationDictionary];
|
| -
|
| - // Periscope down! Animate the tab.
|
| - NSRect newFrame = [tabView frame];
|
| - newFrame = NSOffsetRect(newFrame, 0, -newFrame.size.height);
|
| - ScopedNSAnimationContextGroup animationGroup(true);
|
| - animationGroup.SetCurrentContextDuration(kAnimationDuration);
|
| - [[tabView animator] setFrame:newFrame];
|
| -}
|
| -
|
| -// Called when a notification is received from the model that the given tab
|
| -// has gone away. Start an animation then force a layout to put everything
|
| -// in motion.
|
| -- (void)tabDetachedWithContents:(TabContentsWrapper*)contents
|
| - atIndex:(NSInteger)modelIndex {
|
| - // Take closing tabs into account.
|
| - NSInteger index = [self indexFromModelIndex:modelIndex];
|
| -
|
| - TabController* tab = [tabArray_ objectAtIndex:index];
|
| - if (tabStripModel_->count() > 0) {
|
| - [self startClosingTabWithAnimation:tab];
|
| - [self layoutTabs];
|
| - } else {
|
| - [self removeTab:tab];
|
| - }
|
| -
|
| - // Send a broadcast that the number of tabs have changed.
|
| - [[NSNotificationCenter defaultCenter]
|
| - postNotificationName:kTabStripNumberOfTabsChanged
|
| - object:self];
|
| -
|
| - [delegate_ onTabDetachedWithContents:contents->tab_contents()];
|
| -}
|
| -
|
| -// A helper routine for creating an NSImageView to hold the fav icon or app icon
|
| -// for |contents|.
|
| -- (NSImageView*)iconImageViewForContents:(TabContents*)contents {
|
| - BOOL isApp = contents->is_app();
|
| - NSImage* image = nil;
|
| - // Favicons come from the renderer, and the renderer draws everything in the
|
| - // system color space.
|
| - CGColorSpaceRef colorSpace = base::mac::GetSystemColorSpace();
|
| - if (isApp) {
|
| - SkBitmap* icon = contents->GetExtensionAppIcon();
|
| - if (icon)
|
| - image = gfx::SkBitmapToNSImageWithColorSpace(*icon, colorSpace);
|
| - } else {
|
| - image = gfx::SkBitmapToNSImageWithColorSpace(contents->GetFavIcon(),
|
| - colorSpace);
|
| - }
|
| -
|
| - // Either we don't have a valid favicon or there was some issue converting it
|
| - // from an SkBitmap. Either way, just show the default.
|
| - if (!image)
|
| - image = defaultFavIcon_.get();
|
| - NSRect frame = NSMakeRect(0, 0, kIconWidthAndHeight, kIconWidthAndHeight);
|
| - NSImageView* view = [[[NSImageView alloc] initWithFrame:frame] autorelease];
|
| - [view setImage:image];
|
| - return view;
|
| -}
|
| -
|
| -// Updates the current loading state, replacing the icon view with a favicon,
|
| -// a throbber, the default icon, or nothing at all.
|
| -- (void)updateFavIconForContents:(TabContents*)contents
|
| - atIndex:(NSInteger)modelIndex {
|
| - if (!contents)
|
| - return;
|
| -
|
| - static NSImage* throbberWaitingImage =
|
| - [ResourceBundle::GetSharedInstance().GetNativeImageNamed(
|
| - IDR_THROBBER_WAITING) retain];
|
| - static NSImage* throbberLoadingImage =
|
| - [ResourceBundle::GetSharedInstance().GetNativeImageNamed(IDR_THROBBER)
|
| - retain];
|
| - static NSImage* sadFaviconImage =
|
| - [ResourceBundle::GetSharedInstance().GetNativeImageNamed(IDR_SAD_FAVICON)
|
| - retain];
|
| -
|
| - // Take closing tabs into account.
|
| - NSInteger index = [self indexFromModelIndex:modelIndex];
|
| - TabController* tabController = [tabArray_ objectAtIndex:index];
|
| -
|
| - bool oldHasIcon = [tabController iconView] != nil;
|
| - bool newHasIcon = contents->ShouldDisplayFavIcon() ||
|
| - tabStripModel_->IsMiniTab(modelIndex); // Always show icon if mini.
|
| -
|
| - TabLoadingState oldState = [tabController loadingState];
|
| - TabLoadingState newState = kTabDone;
|
| - NSImage* throbberImage = nil;
|
| - if (contents->is_crashed()) {
|
| - newState = kTabCrashed;
|
| - newHasIcon = true;
|
| - } else if (contents->waiting_for_response()) {
|
| - newState = kTabWaiting;
|
| - throbberImage = throbberWaitingImage;
|
| - } else if (contents->is_loading()) {
|
| - newState = kTabLoading;
|
| - throbberImage = throbberLoadingImage;
|
| - }
|
| -
|
| - if (oldState != newState)
|
| - [tabController setLoadingState:newState];
|
| -
|
| - // While loading, this function is called repeatedly with the same state.
|
| - // To avoid expensive unnecessary view manipulation, only make changes when
|
| - // the state is actually changing. When loading is complete (kTabDone),
|
| - // every call to this function is significant.
|
| - if (newState == kTabDone || oldState != newState ||
|
| - oldHasIcon != newHasIcon) {
|
| - NSView* iconView = nil;
|
| - if (newHasIcon) {
|
| - if (newState == kTabDone) {
|
| - iconView = [self iconImageViewForContents:contents];
|
| - } else if (newState == kTabCrashed) {
|
| - NSImage* oldImage = [[self iconImageViewForContents:contents] image];
|
| - NSRect frame =
|
| - NSMakeRect(0, 0, kIconWidthAndHeight, kIconWidthAndHeight);
|
| - iconView = [ThrobberView toastThrobberViewWithFrame:frame
|
| - beforeImage:oldImage
|
| - afterImage:sadFaviconImage];
|
| - } else {
|
| - NSRect frame =
|
| - NSMakeRect(0, 0, kIconWidthAndHeight, kIconWidthAndHeight);
|
| - iconView = [ThrobberView filmstripThrobberViewWithFrame:frame
|
| - image:throbberImage];
|
| - }
|
| - }
|
| -
|
| - [tabController setIconView:iconView];
|
| - }
|
| -}
|
| -
|
| -// Called when a notification is received from the model that the given tab
|
| -// has been updated. |loading| will be YES when we only want to update the
|
| -// throbber state, not anything else about the (partially) loading tab.
|
| -- (void)tabChangedWithContents:(TabContentsWrapper*)contents
|
| - atIndex:(NSInteger)modelIndex
|
| - changeType:(TabStripModelObserver::TabChangeType)change {
|
| - // Take closing tabs into account.
|
| - NSInteger index = [self indexFromModelIndex:modelIndex];
|
| -
|
| - if (modelIndex == tabStripModel_->selected_index())
|
| - [delegate_ onSelectedTabChange:change];
|
| -
|
| - if (change == TabStripModelObserver::TITLE_NOT_LOADING) {
|
| - // TODO(sky): make this work.
|
| - // We'll receive another notification of the change asynchronously.
|
| - return;
|
| - }
|
| -
|
| - TabController* tabController = [tabArray_ objectAtIndex:index];
|
| -
|
| - if (change != TabStripModelObserver::LOADING_ONLY)
|
| - [self setTabTitle:tabController withContents:contents->tab_contents()];
|
| -
|
| - [self updateFavIconForContents:contents->tab_contents() atIndex:modelIndex];
|
| -
|
| - TabContentsController* updatedController =
|
| - [tabContentsArray_ objectAtIndex:index];
|
| - [updatedController tabDidChange:contents->tab_contents()];
|
| -}
|
| -
|
| -// Called when a tab is moved (usually by drag&drop). Keep our parallel arrays
|
| -// in sync with the tab strip model. It can also be pinned/unpinned
|
| -// simultaneously, so we need to take care of that.
|
| -- (void)tabMovedWithContents:(TabContentsWrapper*)contents
|
| - fromIndex:(NSInteger)modelFrom
|
| - toIndex:(NSInteger)modelTo {
|
| - // Take closing tabs into account.
|
| - NSInteger from = [self indexFromModelIndex:modelFrom];
|
| - NSInteger to = [self indexFromModelIndex:modelTo];
|
| -
|
| - scoped_nsobject<TabContentsController> movedTabContentsController(
|
| - [[tabContentsArray_ objectAtIndex:from] retain]);
|
| - [tabContentsArray_ removeObjectAtIndex:from];
|
| - [tabContentsArray_ insertObject:movedTabContentsController.get()
|
| - atIndex:to];
|
| - scoped_nsobject<TabController> movedTabController(
|
| - [[tabArray_ objectAtIndex:from] retain]);
|
| - DCHECK([movedTabController isKindOfClass:[TabController class]]);
|
| - [tabArray_ removeObjectAtIndex:from];
|
| - [tabArray_ insertObject:movedTabController.get() atIndex:to];
|
| -
|
| - // The tab moved, which means that the mini-tab state may have changed.
|
| - if (tabStripModel_->IsMiniTab(modelTo) != [movedTabController mini])
|
| - [self tabMiniStateChangedWithContents:contents atIndex:modelTo];
|
| -
|
| - [self layoutTabs];
|
| -}
|
| -
|
| -// Called when a tab is pinned or unpinned without moving.
|
| -- (void)tabMiniStateChangedWithContents:(TabContentsWrapper*)contents
|
| - atIndex:(NSInteger)modelIndex {
|
| - // Take closing tabs into account.
|
| - NSInteger index = [self indexFromModelIndex:modelIndex];
|
| -
|
| - TabController* tabController = [tabArray_ objectAtIndex:index];
|
| - DCHECK([tabController isKindOfClass:[TabController class]]);
|
| -
|
| - // Don't do anything if the change was already picked up by the move event.
|
| - if (tabStripModel_->IsMiniTab(modelIndex) == [tabController mini])
|
| - return;
|
| -
|
| - [tabController setMini:tabStripModel_->IsMiniTab(modelIndex)];
|
| - [tabController setPinned:tabStripModel_->IsTabPinned(modelIndex)];
|
| - [tabController setApp:tabStripModel_->IsAppTab(modelIndex)];
|
| - [self updateFavIconForContents:contents->tab_contents() atIndex:modelIndex];
|
| - // If the tab is being restored and it's pinned, the mini state is set after
|
| - // the tab has already been rendered, so re-layout the tabstrip. In all other
|
| - // cases, the state is set before the tab is rendered so this isn't needed.
|
| - [self layoutTabs];
|
| -}
|
| -
|
| -- (void)setFrameOfSelectedTab:(NSRect)frame {
|
| - NSView* view = [self selectedTabView];
|
| - NSValue* identifier = [NSValue valueWithPointer:view];
|
| - [targetFrames_ setObject:[NSValue valueWithRect:frame]
|
| - forKey:identifier];
|
| - [view setFrame:frame];
|
| -}
|
| -
|
| -- (NSView*)selectedTabView {
|
| - int selectedIndex = tabStripModel_->selected_index();
|
| - // Take closing tabs into account. They can't ever be selected.
|
| - selectedIndex = [self indexFromModelIndex:selectedIndex];
|
| - return [self viewAtIndex:selectedIndex];
|
| -}
|
| -
|
| -// Find the model index based on the x coordinate of the placeholder. If there
|
| -// is no placeholder, this returns the end of the tab strip. Closing tabs are
|
| -// not considered in computing the index.
|
| -- (int)indexOfPlaceholder {
|
| - double placeholderX = placeholderFrame_.origin.x;
|
| - int index = 0;
|
| - int location = 0;
|
| - // Use |tabArray_| here instead of the tab strip count in order to get the
|
| - // correct index when there are closing tabs to the left of the placeholder.
|
| - const int count = [tabArray_ count];
|
| - while (index < count) {
|
| - // Ignore closing tabs for simplicity. The only drawback of this is that
|
| - // if the placeholder is placed right before one or several contiguous
|
| - // currently closing tabs, the associated TabController will start at the
|
| - // end of the closing tabs.
|
| - if ([closingControllers_ containsObject:[tabArray_ objectAtIndex:index]]) {
|
| - index++;
|
| - continue;
|
| - }
|
| - NSView* curr = [self viewAtIndex:index];
|
| - // The placeholder tab works by changing the frame of the tab being dragged
|
| - // to be the bounds of the placeholder, so we need to skip it while we're
|
| - // iterating, otherwise we'll end up off by one. Note This only effects
|
| - // dragging to the right, not to the left.
|
| - if (curr == placeholderTab_) {
|
| - index++;
|
| - continue;
|
| - }
|
| - if (placeholderX <= NSMinX([curr frame]))
|
| - break;
|
| - index++;
|
| - location++;
|
| - }
|
| - return location;
|
| -}
|
| -
|
| -// Move the given tab at index |from| in this window to the location of the
|
| -// current placeholder.
|
| -- (void)moveTabFromIndex:(NSInteger)from {
|
| - int toIndex = [self indexOfPlaceholder];
|
| - tabStripModel_->MoveTabContentsAt(from, toIndex, true);
|
| -}
|
| -
|
| -// Drop a given TabContents at the location of the current placeholder. If there
|
| -// is no placeholder, it will go at the end. Used when dragging from another
|
| -// window when we don't have access to the TabContents as part of our strip.
|
| -// |frame| is in the coordinate system of the tab strip view and represents
|
| -// where the user dropped the new tab so it can be animated into its correct
|
| -// location when the tab is added to the model. If the tab was pinned in its
|
| -// previous window, setting |pinned| to YES will propagate that state to the
|
| -// new window. Mini-tabs are either app or pinned tabs; the app state is stored
|
| -// by the |contents|, but the |pinned| state is the caller's responsibility.
|
| -- (void)dropTabContents:(TabContentsWrapper*)contents
|
| - withFrame:(NSRect)frame
|
| - asPinnedTab:(BOOL)pinned {
|
| - int modelIndex = [self indexOfPlaceholder];
|
| -
|
| - // Mark that the new tab being created should start at |frame|. It will be
|
| - // reset as soon as the tab has been positioned.
|
| - droppedTabFrame_ = frame;
|
| -
|
| - // Insert it into this tab strip. We want it in the foreground and to not
|
| - // inherit the current tab's group.
|
| - tabStripModel_->InsertTabContentsAt(
|
| - modelIndex, contents,
|
| - TabStripModel::ADD_SELECTED | (pinned ? TabStripModel::ADD_PINNED : 0));
|
| -}
|
| -
|
| -// Called when the tab strip view changes size. As we only registered for
|
| -// changes on our view, we know it's only for our view. Layout w/out
|
| -// animations since they are blocked by the resize nested runloop. We need
|
| -// the views to adjust immediately. Neither the tabs nor their z-order are
|
| -// changed, so we don't need to update the subviews.
|
| -- (void)tabViewFrameChanged:(NSNotification*)info {
|
| - [self layoutTabsWithAnimation:NO regenerateSubviews:NO];
|
| -}
|
| -
|
| -// Called when the tracking areas for any given tab are updated. This allows
|
| -// the individual tabs to update their hover states correctly.
|
| -// Only generates the event if the cursor is in the tab strip.
|
| -- (void)tabUpdateTracking:(NSNotification*)notification {
|
| - DCHECK([[notification object] isKindOfClass:[TabView class]]);
|
| - DCHECK(mouseInside_);
|
| - NSWindow* window = [tabStripView_ window];
|
| - NSPoint location = [window mouseLocationOutsideOfEventStream];
|
| - if (NSPointInRect(location, [tabStripView_ frame])) {
|
| - NSEvent* mouseEvent = [NSEvent mouseEventWithType:NSMouseMoved
|
| - location:location
|
| - modifierFlags:0
|
| - timestamp:0
|
| - windowNumber:[window windowNumber]
|
| - context:nil
|
| - eventNumber:0
|
| - clickCount:0
|
| - pressure:0];
|
| - [self mouseMoved:mouseEvent];
|
| - }
|
| -}
|
| -
|
| -- (BOOL)inRapidClosureMode {
|
| - return availableResizeWidth_ != kUseFullAvailableWidth;
|
| -}
|
| -
|
| -// Disable tab dragging when there are any pending animations.
|
| -- (BOOL)tabDraggingAllowed {
|
| - return [closingControllers_ count] == 0;
|
| -}
|
| -
|
| -- (void)mouseMoved:(NSEvent*)event {
|
| - // Use hit test to figure out what view we are hovering over.
|
| - NSView* targetView = [tabStripView_ hitTest:[event locationInWindow]];
|
| -
|
| - // Set the new tab button hover state iff the mouse is over the button.
|
| - BOOL shouldShowHoverImage = [targetView isKindOfClass:[NewTabButton class]];
|
| - [self setNewTabButtonHoverState:shouldShowHoverImage];
|
| -
|
| - TabView* tabView = (TabView*)targetView;
|
| - if (![tabView isKindOfClass:[TabView class]]) {
|
| - if ([[tabView superview] isKindOfClass:[TabView class]]) {
|
| - tabView = (TabView*)[targetView superview];
|
| - } else {
|
| - tabView = nil;
|
| - }
|
| - }
|
| -
|
| - if (hoveredTab_ != tabView) {
|
| - [hoveredTab_ mouseExited:nil]; // We don't pass event because moved events
|
| - [tabView mouseEntered:nil]; // don't have valid tracking areas
|
| - hoveredTab_ = tabView;
|
| - } else {
|
| - [hoveredTab_ mouseMoved:event];
|
| - }
|
| -}
|
| -
|
| -- (void)mouseEntered:(NSEvent*)event {
|
| - NSTrackingArea* area = [event trackingArea];
|
| - if ([area isEqual:trackingArea_]) {
|
| - mouseInside_ = YES;
|
| - [self setTabTrackingAreasEnabled:YES];
|
| - [self mouseMoved:event];
|
| - }
|
| -}
|
| -
|
| -// Called when the tracking area is in effect which means we're tracking to
|
| -// see if the user leaves the tab strip with their mouse. When they do,
|
| -// reset layout to use all available width.
|
| -- (void)mouseExited:(NSEvent*)event {
|
| - NSTrackingArea* area = [event trackingArea];
|
| - if ([area isEqual:trackingArea_]) {
|
| - mouseInside_ = NO;
|
| - [self setTabTrackingAreasEnabled:NO];
|
| - availableResizeWidth_ = kUseFullAvailableWidth;
|
| - [hoveredTab_ mouseExited:event];
|
| - hoveredTab_ = nil;
|
| - [self layoutTabs];
|
| - } else if ([area isEqual:newTabTrackingArea_]) {
|
| - // If the mouse is moved quickly enough, it is possible for the mouse to
|
| - // leave the tabstrip without sending any mouseMoved: messages at all.
|
| - // Since this would result in the new tab button incorrectly staying in the
|
| - // hover state, disable the hover image on every mouse exit.
|
| - [self setNewTabButtonHoverState:NO];
|
| - }
|
| -}
|
| -
|
| -// Enable/Disable the tracking areas for the tabs. They are only enabled
|
| -// when the mouse is in the tabstrip.
|
| -- (void)setTabTrackingAreasEnabled:(BOOL)enabled {
|
| - NSNotificationCenter* defaultCenter = [NSNotificationCenter defaultCenter];
|
| - for (TabController* controller in tabArray_.get()) {
|
| - TabView* tabView = [controller tabView];
|
| - if (enabled) {
|
| - // Set self up to observe tabs so hover states will be correct.
|
| - [defaultCenter addObserver:self
|
| - selector:@selector(tabUpdateTracking:)
|
| - name:NSViewDidUpdateTrackingAreasNotification
|
| - object:tabView];
|
| - } else {
|
| - [defaultCenter removeObserver:self
|
| - name:NSViewDidUpdateTrackingAreasNotification
|
| - object:tabView];
|
| - }
|
| - [tabView setTrackingEnabled:enabled];
|
| - }
|
| -}
|
| -
|
| -// Sets the new tab button's image based on the current hover state. Does
|
| -// nothing if the hover state is already correct.
|
| -- (void)setNewTabButtonHoverState:(BOOL)shouldShowHover {
|
| - if (shouldShowHover && !newTabButtonShowingHoverImage_) {
|
| - newTabButtonShowingHoverImage_ = YES;
|
| - [newTabButton_ setImage:
|
| - app::mac::GetCachedImageWithName(kNewTabHoverImage)];
|
| - } else if (!shouldShowHover && newTabButtonShowingHoverImage_) {
|
| - newTabButtonShowingHoverImage_ = NO;
|
| - [newTabButton_ setImage:app::mac::GetCachedImageWithName(kNewTabImage)];
|
| - }
|
| -}
|
| -
|
| -// Adds the given subview to (the end of) the list of permanent subviews
|
| -// (specified from bottom up). These subviews will always be below the
|
| -// transitory subviews (tabs). |-regenerateSubviewList| must be called to
|
| -// effectuate the addition.
|
| -- (void)addSubviewToPermanentList:(NSView*)aView {
|
| - if (aView)
|
| - [permanentSubviews_ addObject:aView];
|
| -}
|
| -
|
| -// Update the subviews, keeping the permanent ones (or, more correctly, putting
|
| -// in the ones listed in permanentSubviews_), and putting in the current tabs in
|
| -// the correct z-order. Any current subviews which is neither in the permanent
|
| -// list nor a (current) tab will be removed. So if you add such a subview, you
|
| -// should call |-addSubviewToPermanentList:| (or better yet, call that and then
|
| -// |-regenerateSubviewList| to actually add it).
|
| -- (void)regenerateSubviewList {
|
| - // Remove self as an observer from all the old tabs before a new set of
|
| - // potentially different tabs is put in place.
|
| - [self setTabTrackingAreasEnabled:NO];
|
| -
|
| - // Subviews to put in (in bottom-to-top order), beginning with the permanent
|
| - // ones.
|
| - NSMutableArray* subviews = [NSMutableArray arrayWithArray:permanentSubviews_];
|
| -
|
| - NSView* selectedTabView = nil;
|
| - // Go through tabs in reverse order, since |subviews| is bottom-to-top.
|
| - for (TabController* tab in [tabArray_ reverseObjectEnumerator]) {
|
| - NSView* tabView = [tab view];
|
| - if ([tab selected]) {
|
| - DCHECK(!selectedTabView);
|
| - selectedTabView = tabView;
|
| - } else {
|
| - [subviews addObject:tabView];
|
| - }
|
| - }
|
| - if (selectedTabView) {
|
| - [subviews addObject:selectedTabView];
|
| - }
|
| - [tabStripView_ setSubviews:subviews];
|
| - [self setTabTrackingAreasEnabled:mouseInside_];
|
| -}
|
| -
|
| -// Get the index and disposition for a potential URL(s) drop given a point (in
|
| -// the |TabStripView|'s coordinates). It considers only the x-coordinate of the
|
| -// given point. If it's in the "middle" of a tab, it drops on that tab. If it's
|
| -// to the left, it inserts to the left, and similarly for the right.
|
| -- (void)droppingURLsAt:(NSPoint)point
|
| - givesIndex:(NSInteger*)index
|
| - disposition:(WindowOpenDisposition*)disposition {
|
| - // Proportion of the tab which is considered the "middle" (and causes things
|
| - // to drop on that tab).
|
| - const double kMiddleProportion = 0.5;
|
| - const double kLRProportion = (1.0 - kMiddleProportion) / 2.0;
|
| -
|
| - DCHECK(index && disposition);
|
| - NSInteger i = 0;
|
| - for (TabController* tab in tabArray_.get()) {
|
| - NSView* view = [tab view];
|
| - DCHECK([view isKindOfClass:[TabView class]]);
|
| -
|
| - // Recall that |-[NSView frame]| is in its superview's coordinates, so a
|
| - // |TabView|'s frame is in the coordinates of the |TabStripView| (which
|
| - // matches the coordinate system of |point|).
|
| - NSRect frame = [view frame];
|
| -
|
| - // Modify the frame to make it "unoverlapped".
|
| - frame.origin.x += kTabOverlap / 2.0;
|
| - frame.size.width -= kTabOverlap;
|
| - if (frame.size.width < 1.0)
|
| - frame.size.width = 1.0; // try to avoid complete failure
|
| -
|
| - // Drop in a new tab to the left of tab |i|?
|
| - if (point.x < (frame.origin.x + kLRProportion * frame.size.width)) {
|
| - *index = i;
|
| - *disposition = NEW_FOREGROUND_TAB;
|
| - return;
|
| - }
|
| -
|
| - // Drop on tab |i|?
|
| - if (point.x <= (frame.origin.x +
|
| - (1.0 - kLRProportion) * frame.size.width)) {
|
| - *index = i;
|
| - *disposition = CURRENT_TAB;
|
| - return;
|
| - }
|
| -
|
| - // (Dropping in a new tab to the right of tab |i| will be taken care of in
|
| - // the next iteration.)
|
| - i++;
|
| - }
|
| -
|
| - // If we've made it here, we want to append a new tab to the end.
|
| - *index = -1;
|
| - *disposition = NEW_FOREGROUND_TAB;
|
| -}
|
| -
|
| -- (void)openURL:(GURL*)url inView:(NSView*)view at:(NSPoint)point {
|
| - // Get the index and disposition.
|
| - NSInteger index;
|
| - WindowOpenDisposition disposition;
|
| - [self droppingURLsAt:point
|
| - givesIndex:&index
|
| - disposition:&disposition];
|
| -
|
| - // Either insert a new tab or open in a current tab.
|
| - switch (disposition) {
|
| - case NEW_FOREGROUND_TAB: {
|
| - UserMetrics::RecordAction(UserMetricsAction("Tab_DropURLBetweenTabs"),
|
| - browser_->profile());
|
| - browser::NavigateParams params(browser_, *url, PageTransition::TYPED);
|
| - params.disposition = disposition;
|
| - params.tabstrip_index = index;
|
| - params.tabstrip_add_types =
|
| - TabStripModel::ADD_SELECTED | TabStripModel::ADD_FORCE_INDEX;
|
| - browser::Navigate(¶ms);
|
| - break;
|
| - }
|
| - case CURRENT_TAB:
|
| - UserMetrics::RecordAction(UserMetricsAction("Tab_DropURLOnTab"),
|
| - browser_->profile());
|
| - tabStripModel_->GetTabContentsAt(index)
|
| - ->tab_contents()->OpenURL(*url, GURL(), CURRENT_TAB,
|
| - PageTransition::TYPED);
|
| - tabStripModel_->SelectTabContentsAt(index, true);
|
| - break;
|
| - default:
|
| - NOTIMPLEMENTED();
|
| - }
|
| -}
|
| -
|
| -// (URLDropTargetController protocol)
|
| -- (void)dropURLs:(NSArray*)urls inView:(NSView*)view at:(NSPoint)point {
|
| - DCHECK_EQ(view, tabStripView_.get());
|
| -
|
| - if ([urls count] < 1) {
|
| - NOTREACHED();
|
| - return;
|
| - }
|
| -
|
| - //TODO(viettrungluu): dropping multiple URLs.
|
| - if ([urls count] > 1)
|
| - NOTIMPLEMENTED();
|
| -
|
| - // Get the first URL and fix it up.
|
| - GURL url(GURL(URLFixerUpper::FixupURL(
|
| - base::SysNSStringToUTF8([urls objectAtIndex:0]), std::string())));
|
| -
|
| - [self openURL:&url inView:view at:point];
|
| -}
|
| -
|
| -// (URLDropTargetController protocol)
|
| -- (void)dropText:(NSString*)text inView:(NSView*)view at:(NSPoint)point {
|
| - DCHECK_EQ(view, tabStripView_.get());
|
| -
|
| - // If the input is plain text, classify the input and make the URL.
|
| - AutocompleteMatch match;
|
| - browser_->profile()->GetAutocompleteClassifier()->Classify(
|
| - base::SysNSStringToWide(text),
|
| - std::wstring(), false, &match, NULL);
|
| - GURL url(match.destination_url);
|
| -
|
| - [self openURL:&url inView:view at:point];
|
| -}
|
| -
|
| -// (URLDropTargetController protocol)
|
| -- (void)indicateDropURLsInView:(NSView*)view at:(NSPoint)point {
|
| - DCHECK_EQ(view, tabStripView_.get());
|
| -
|
| - // The minimum y-coordinate at which one should consider place the arrow.
|
| - const CGFloat arrowBaseY = 25;
|
| -
|
| - NSInteger index;
|
| - WindowOpenDisposition disposition;
|
| - [self droppingURLsAt:point
|
| - givesIndex:&index
|
| - disposition:&disposition];
|
| -
|
| - NSPoint arrowPos = NSMakePoint(0, arrowBaseY);
|
| - if (index == -1) {
|
| - // Append a tab at the end.
|
| - DCHECK(disposition == NEW_FOREGROUND_TAB);
|
| - NSInteger lastIndex = [tabArray_ count] - 1;
|
| - NSRect overRect = [[[tabArray_ objectAtIndex:lastIndex] view] frame];
|
| - arrowPos.x = overRect.origin.x + overRect.size.width - kTabOverlap / 2.0;
|
| - } else {
|
| - NSRect overRect = [[[tabArray_ objectAtIndex:index] view] frame];
|
| - switch (disposition) {
|
| - case NEW_FOREGROUND_TAB:
|
| - // Insert tab (to the left of the given tab).
|
| - arrowPos.x = overRect.origin.x + kTabOverlap / 2.0;
|
| - break;
|
| - case CURRENT_TAB:
|
| - // Overwrite the given tab.
|
| - arrowPos.x = overRect.origin.x + overRect.size.width / 2.0;
|
| - break;
|
| - default:
|
| - NOTREACHED();
|
| - }
|
| - }
|
| -
|
| - [tabStripView_ setDropArrowPosition:arrowPos];
|
| - [tabStripView_ setDropArrowShown:YES];
|
| - [tabStripView_ setNeedsDisplay:YES];
|
| -}
|
| -
|
| -// (URLDropTargetController protocol)
|
| -- (void)hideDropURLsIndicatorInView:(NSView*)view {
|
| - DCHECK_EQ(view, tabStripView_.get());
|
| -
|
| - if ([tabStripView_ dropArrowShown]) {
|
| - [tabStripView_ setDropArrowShown:NO];
|
| - [tabStripView_ setNeedsDisplay:YES];
|
| - }
|
| -}
|
| -
|
| -- (GTMWindowSheetController*)sheetController {
|
| - if (!sheetController_.get())
|
| - sheetController_.reset([[GTMWindowSheetController alloc]
|
| - initWithWindow:[switchView_ window] delegate:self]);
|
| - return sheetController_.get();
|
| -}
|
| -
|
| -- (void)destroySheetController {
|
| - // Make sure there are no open sheets.
|
| - DCHECK_EQ(0U, [[sheetController_ viewsWithAttachedSheets] count]);
|
| - sheetController_.reset();
|
| -}
|
| -
|
| -// TabContentsControllerDelegate protocol.
|
| -- (void)tabContentsViewFrameWillChange:(TabContentsController*)source
|
| - frameRect:(NSRect)frameRect {
|
| - id<TabContentsControllerDelegate> controller =
|
| - [[switchView_ window] windowController];
|
| - [controller tabContentsViewFrameWillChange:source frameRect:frameRect];
|
| -}
|
| -
|
| -- (TabContentsController*)activeTabContentsController {
|
| - int modelIndex = tabStripModel_->selected_index();
|
| - if (modelIndex < 0)
|
| - return nil;
|
| - NSInteger index = [self indexFromModelIndex:modelIndex];
|
| - if (index < 0 ||
|
| - index >= (NSInteger)[tabContentsArray_ count])
|
| - return nil;
|
| - return [tabContentsArray_ objectAtIndex:index];
|
| -}
|
| -
|
| -- (void)gtm_systemRequestsVisibilityForView:(NSView*)view {
|
| - // This implementation is required by GTMWindowSheetController.
|
| -
|
| - // Raise window...
|
| - [[switchView_ window] makeKeyAndOrderFront:self];
|
| -
|
| - // ...and raise a tab with a sheet.
|
| - NSInteger index = [self modelIndexForContentsView:view];
|
| - DCHECK(index >= 0);
|
| - if (index >= 0)
|
| - tabStripModel_->SelectTabContentsAt(index, false /* not a user gesture */);
|
| -}
|
| -
|
| -- (void)attachConstrainedWindow:(ConstrainedWindowMac*)window {
|
| - // TODO(thakis, avi): Figure out how to make this work when tabs are dragged
|
| - // out or if fullscreen mode is toggled.
|
| -
|
| - // View hierarchy of the contents view:
|
| - // NSView -- switchView, same for all tabs
|
| - // +- NSView -- TabContentsController's view
|
| - // +- TabContentsViewCocoa
|
| - // Changing it? Do not forget to modify removeConstrainedWindow too.
|
| - // We use the TabContentsController's view in |swapInTabAtIndex|, so we have
|
| - // to pass it to the sheet controller here.
|
| - NSView* tabContentsView = [window->owner()->GetNativeView() superview];
|
| - window->delegate()->RunSheet([self sheetController], tabContentsView);
|
| -
|
| - // TODO(avi, thakis): GTMWindowSheetController has no api to move tabsheets
|
| - // between windows. Until then, we have to prevent having to move a tabsheet
|
| - // between windows, e.g. no tearing off of tabs.
|
| - NSInteger modelIndex = [self modelIndexForContentsView:tabContentsView];
|
| - NSInteger index = [self indexFromModelIndex:modelIndex];
|
| - BrowserWindowController* controller =
|
| - (BrowserWindowController*)[[switchView_ window] windowController];
|
| - DCHECK(controller != nil);
|
| - DCHECK(index >= 0);
|
| - if (index >= 0) {
|
| - [controller setTab:[self viewAtIndex:index] isDraggable:NO];
|
| - }
|
| -}
|
| -
|
| -- (void)removeConstrainedWindow:(ConstrainedWindowMac*)window {
|
| - NSView* tabContentsView = [window->owner()->GetNativeView() superview];
|
| -
|
| - // TODO(avi, thakis): GTMWindowSheetController has no api to move tabsheets
|
| - // between windows. Until then, we have to prevent having to move a tabsheet
|
| - // between windows, e.g. no tearing off of tabs.
|
| - NSInteger modelIndex = [self modelIndexForContentsView:tabContentsView];
|
| - NSInteger index = [self indexFromModelIndex:modelIndex];
|
| - BrowserWindowController* controller =
|
| - (BrowserWindowController*)[[switchView_ window] windowController];
|
| - DCHECK(index >= 0);
|
| - if (index >= 0) {
|
| - [controller setTab:[self viewAtIndex:index] isDraggable:YES];
|
| - }
|
| -}
|
| -
|
| -@end
|
|
|