• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (c) 2011 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#import "chrome/browser/ui/cocoa/tabs/tab_strip_controller.h"
6
7#import <QuartzCore/QuartzCore.h>
8
9#include <limits>
10#include <string>
11
12#include "app/mac/nsimage_cache.h"
13#include "base/command_line.h"
14#include "base/mac/mac_util.h"
15#include "base/sys_string_conversions.h"
16#include "chrome/app/chrome_command_ids.h"
17#include "chrome/browser/autocomplete/autocomplete.h"
18#include "chrome/browser/autocomplete/autocomplete_classifier.h"
19#include "chrome/browser/autocomplete/autocomplete_match.h"
20#include "chrome/browser/extensions/extension_tab_helper.h"
21#include "chrome/browser/metrics/user_metrics.h"
22#include "chrome/browser/prefs/pref_service.h"
23#include "chrome/browser/profiles/profile.h"
24#include "chrome/browser/debugger/devtools_window.h"
25#include "chrome/browser/net/url_fixer_upper.h"
26#include "chrome/browser/sidebar/sidebar_container.h"
27#include "chrome/browser/sidebar/sidebar_manager.h"
28#include "chrome/browser/tabs/tab_strip_model.h"
29#include "chrome/browser/ui/browser.h"
30#include "chrome/browser/ui/browser_navigator.h"
31#include "chrome/browser/ui/find_bar/find_tab_helper.h"
32#import "chrome/browser/ui/cocoa/browser_window_controller.h"
33#import "chrome/browser/ui/cocoa/constrained_window_mac.h"
34#import "chrome/browser/ui/cocoa/new_tab_button.h"
35#import "chrome/browser/ui/cocoa/profile_menu_button.h"
36#import "chrome/browser/ui/cocoa/tab_contents/favicon_util.h"
37#import "chrome/browser/ui/cocoa/tabs/tab_controller.h"
38#import "chrome/browser/ui/cocoa/tabs/tab_strip_model_observer_bridge.h"
39#import "chrome/browser/ui/cocoa/tabs/tab_strip_view.h"
40#import "chrome/browser/ui/cocoa/tabs/tab_view.h"
41#import "chrome/browser/ui/cocoa/tabs/throbber_view.h"
42#import "chrome/browser/ui/cocoa/tracking_area.h"
43#include "chrome/browser/ui/find_bar/find_bar.h"
44#include "chrome/browser/ui/find_bar/find_bar_controller.h"
45#include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
46#include "chrome/common/chrome_switches.h"
47#include "chrome/common/pref_names.h"
48#include "content/browser/tab_contents/navigation_controller.h"
49#include "content/browser/tab_contents/navigation_entry.h"
50#include "content/browser/tab_contents/tab_contents.h"
51#include "content/browser/tab_contents/tab_contents_view.h"
52#include "content/common/notification_service.h"
53#include "grit/app_resources.h"
54#include "grit/generated_resources.h"
55#include "grit/theme_resources.h"
56#include "skia/ext/skia_utils_mac.h"
57#import "third_party/GTM/AppKit/GTMNSAnimation+Duration.h"
58#include "ui/base/l10n/l10n_util.h"
59#include "ui/base/resource/resource_bundle.h"
60#include "ui/gfx/image.h"
61
62NSString* const kTabStripNumberOfTabsChanged = @"kTabStripNumberOfTabsChanged";
63
64// 10.7 adds public APIs for full-screen support. Provide the declaration so it
65// can be called below when building with the 10.5 SDK.
66#if !defined(MAC_OS_X_VERSION_10_7) || \
67MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7
68
69@interface NSWindow (LionSDKDeclarations)
70- (void)toggleFullScreen:(id)sender;
71@end
72
73enum {
74  NSWindowFullScreenButton = 7
75};
76
77#endif  // MAC_OS_X_VERSION_10_7
78
79namespace {
80
81// The images names used for different states of the new tab button.
82NSString* const kNewTabHoverImage = @"newtab_h.pdf";
83NSString* const kNewTabImage = @"newtab.pdf";
84NSString* const kNewTabPressedImage = @"newtab_p.pdf";
85
86// A value to indicate tab layout should use the full available width of the
87// view.
88const CGFloat kUseFullAvailableWidth = -1.0;
89
90// The amount by which tabs overlap.
91const CGFloat kTabOverlap = 20.0;
92
93// The width and height for a tab's icon.
94const CGFloat kIconWidthAndHeight = 16.0;
95
96// The amount by which the new tab button is offset (from the tabs).
97const CGFloat kNewTabButtonOffset = 8.0;
98
99// The amount by which to shrink the tab strip (on the right) when the
100// incognito badge is present.
101const CGFloat kIncognitoBadgeTabStripShrink = 18;
102
103// Time (in seconds) in which tabs animate to their final position.
104const NSTimeInterval kAnimationDuration = 0.125;
105
106// The amount by wich the profile menu button is offset (from tab tabs or new
107// tab button).
108const CGFloat kProfileMenuButtonOffset = 6.0;
109
110// Helper class for doing NSAnimationContext calls that takes a bool to disable
111// all the work.  Useful for code that wants to conditionally animate.
112class ScopedNSAnimationContextGroup {
113 public:
114  explicit ScopedNSAnimationContextGroup(bool animate)
115      : animate_(animate) {
116    if (animate_) {
117      [NSAnimationContext beginGrouping];
118    }
119  }
120
121  ~ScopedNSAnimationContextGroup() {
122    if (animate_) {
123      [NSAnimationContext endGrouping];
124    }
125  }
126
127  void SetCurrentContextDuration(NSTimeInterval duration) {
128    if (animate_) {
129      [[NSAnimationContext currentContext] gtm_setDuration:duration
130                                                 eventMask:NSLeftMouseUpMask];
131    }
132  }
133
134  void SetCurrentContextShortestDuration() {
135    if (animate_) {
136      // The minimum representable time interval.  This used to stop an
137      // in-progress animation as quickly as possible.
138      const NSTimeInterval kMinimumTimeInterval =
139          std::numeric_limits<NSTimeInterval>::min();
140      // Directly set the duration to be short, avoiding the Steve slowmotion
141      // ettect the gtm_setDuration: provides.
142      [[NSAnimationContext currentContext] setDuration:kMinimumTimeInterval];
143    }
144  }
145
146private:
147  bool animate_;
148  DISALLOW_COPY_AND_ASSIGN(ScopedNSAnimationContextGroup);
149};
150
151}  // namespace
152
153@interface TabStripController (Private)
154- (void)addSubviewToPermanentList:(NSView*)aView;
155- (void)regenerateSubviewList;
156- (NSInteger)indexForContentsView:(NSView*)view;
157- (void)updateFaviconForContents:(TabContents*)contents
158                         atIndex:(NSInteger)modelIndex;
159- (void)layoutTabsWithAnimation:(BOOL)animate
160             regenerateSubviews:(BOOL)doUpdate;
161- (void)animationDidStopForController:(TabController*)controller
162                             finished:(BOOL)finished;
163- (NSInteger)indexFromModelIndex:(NSInteger)index;
164- (NSInteger)numberOfOpenTabs;
165- (NSInteger)numberOfOpenMiniTabs;
166- (NSInteger)numberOfOpenNonMiniTabs;
167- (void)mouseMoved:(NSEvent*)event;
168- (void)setTabTrackingAreasEnabled:(BOOL)enabled;
169- (void)droppingURLsAt:(NSPoint)point
170            givesIndex:(NSInteger*)index
171           disposition:(WindowOpenDisposition*)disposition;
172- (void)setNewTabButtonHoverState:(BOOL)showHover;
173- (BOOL)shouldShowProfileMenuButton;
174- (void)updateProfileMenuButton;
175@end
176
177// A simple view class that prevents the Window Server from dragging the area
178// behind tabs. Sometimes core animation confuses it. Unfortunately, it can also
179// falsely pick up clicks during rapid tab closure, so we have to account for
180// that.
181@interface TabStripControllerDragBlockingView : NSView {
182  TabStripController* controller_;  // weak; owns us
183}
184
185- (id)initWithFrame:(NSRect)frameRect
186         controller:(TabStripController*)controller;
187@end
188
189@implementation TabStripControllerDragBlockingView
190- (BOOL)mouseDownCanMoveWindow {return NO;}
191- (void)drawRect:(NSRect)rect {}
192
193- (id)initWithFrame:(NSRect)frameRect
194         controller:(TabStripController*)controller {
195  if ((self = [super initWithFrame:frameRect]))
196    controller_ = controller;
197  return self;
198}
199
200// In "rapid tab closure" mode (i.e., the user is clicking close tab buttons in
201// rapid succession), the animations confuse Cocoa's hit testing (which appears
202// to use cached results, among other tricks), so this view can somehow end up
203// getting a mouse down event. Thus we do an explicit hit test during rapid tab
204// closure, and if we find that we got a mouse down we shouldn't have, we send
205// it off to the appropriate view.
206- (void)mouseDown:(NSEvent*)event {
207  if ([controller_ inRapidClosureMode]) {
208    NSView* superview = [self superview];
209    NSPoint hitLocation =
210        [[superview superview] convertPoint:[event locationInWindow]
211                                   fromView:nil];
212    NSView* hitView = [superview hitTest:hitLocation];
213    if (hitView != self) {
214      [hitView mouseDown:event];
215      return;
216    }
217  }
218  [super mouseDown:event];
219}
220@end
221
222#pragma mark -
223
224// A delegate, owned by the CAAnimation system, that is alerted when the
225// animation to close a tab is completed. Calls back to the given tab strip
226// to let it know that |controller_| is ready to be removed from the model.
227// Since we only maintain weak references, the tab strip must call -invalidate:
228// to prevent the use of dangling pointers.
229@interface TabCloseAnimationDelegate : NSObject {
230 @private
231  TabStripController* strip_;  // weak; owns us indirectly
232  TabController* controller_;  // weak
233}
234
235// Will tell |strip| when the animation for |controller|'s view has completed.
236// These should not be nil, and will not be retained.
237- (id)initWithTabStrip:(TabStripController*)strip
238         tabController:(TabController*)controller;
239
240// Invalidates this object so that no further calls will be made to
241// |strip_|.  This should be called when |strip_| is released, to
242// prevent attempts to call into the released object.
243- (void)invalidate;
244
245// CAAnimation delegate method
246- (void)animationDidStop:(CAAnimation*)animation finished:(BOOL)finished;
247
248@end
249
250@implementation TabCloseAnimationDelegate
251
252- (id)initWithTabStrip:(TabStripController*)strip
253         tabController:(TabController*)controller {
254  if ((self = [super init])) {
255    DCHECK(strip && controller);
256    strip_ = strip;
257    controller_ = controller;
258  }
259  return self;
260}
261
262- (void)invalidate {
263  strip_ = nil;
264  controller_ = nil;
265}
266
267- (void)animationDidStop:(CAAnimation*)animation finished:(BOOL)finished {
268  [strip_ animationDidStopForController:controller_ finished:finished];
269}
270
271@end
272
273namespace TabStripControllerInternal {
274
275// Bridges C++ notifications back to the TabStripController.
276class NotificationBridge : public NotificationObserver {
277 public:
278  explicit NotificationBridge(TabStripController* controller,
279                              PrefService* prefService)
280      : controller_(controller) {
281    DCHECK(prefService);
282    usernamePref_.Init(prefs::kGoogleServicesUsername, prefService, this);
283  }
284
285  // Overridden from NotificationObserver:
286  virtual void Observe(NotificationType type,
287                       const NotificationSource& source,
288                       const NotificationDetails& details) {
289    DCHECK_EQ(NotificationType::PREF_CHANGED, type.value);
290    std::string* name = Details<std::string>(details).ptr();
291    if (prefs::kGoogleServicesUsername == *name) {
292      [controller_ updateProfileMenuButton];
293      [controller_ layoutTabsWithAnimation:NO regenerateSubviews:NO];
294    }
295  }
296
297 private:
298  TabStripController* controller_;  // weak, owns us
299
300  // The Google services user name associated with this BrowserView's profile.
301  StringPrefMember usernamePref_;
302};
303
304} // namespace TabStripControllerInternal
305
306#pragma mark -
307
308// In general, there is a one-to-one correspondence between TabControllers,
309// TabViews, TabContentsControllers, and the TabContents in the TabStripModel.
310// In the steady-state, the indices line up so an index coming from the model
311// is directly mapped to the same index in the parallel arrays holding our
312// views and controllers. This is also true when new tabs are created (even
313// though there is a small period of animation) because the tab is present
314// in the model while the TabView is animating into place. As a result, nothing
315// special need be done to handle "new tab" animation.
316//
317// This all goes out the window with the "close tab" animation. The animation
318// kicks off in |-tabDetachedWithContents:atIndex:| with the notification that
319// the tab has been removed from the model. The simplest solution at this
320// point would be to remove the views and controllers as well, however once
321// the TabView is removed from the view list, the tab z-order code takes care of
322// removing it from the tab strip and we'll get no animation. That means if
323// there is to be any visible animation, the TabView needs to stay around until
324// its animation is complete. In order to maintain consistency among the
325// internal parallel arrays, this means all structures are kept around until
326// the animation completes. At this point, though, the model and our internal
327// structures are out of sync: the indices no longer line up. As a result,
328// there is a concept of a "model index" which represents an index valid in
329// the TabStripModel. During steady-state, the "model index" is just the same
330// index as our parallel arrays (as above), but during tab close animations,
331// it is different, offset by the number of tabs preceding the index which
332// are undergoing tab closing animation. As a result, the caller needs to be
333// careful to use the available conversion routines when accessing the internal
334// parallel arrays (e.g., -indexFromModelIndex:). Care also needs to be taken
335// during tab layout to ignore closing tabs in the total width calculations and
336// in individual tab positioning (to avoid moving them right back to where they
337// were).
338//
339// In order to prevent actions being taken on tabs which are closing, the tab
340// itself gets marked as such so it no longer will send back its select action
341// or allow itself to be dragged. In addition, drags on the tab strip as a
342// whole are disabled while there are tabs closing.
343
344@implementation TabStripController
345
346@synthesize indentForControls = indentForControls_;
347
348- (id)initWithView:(TabStripView*)view
349        switchView:(NSView*)switchView
350           browser:(Browser*)browser
351          delegate:(id<TabStripControllerDelegate>)delegate {
352  DCHECK(view && switchView && browser && delegate);
353  if ((self = [super init])) {
354    tabStripView_.reset([view retain]);
355    switchView_ = switchView;
356    browser_ = browser;
357    tabStripModel_ = browser_->tabstrip_model();
358    delegate_ = delegate;
359    bridge_.reset(new TabStripModelObserverBridge(tabStripModel_, self));
360    tabContentsArray_.reset([[NSMutableArray alloc] init]);
361    tabArray_.reset([[NSMutableArray alloc] init]);
362    NSWindow* browserWindow = [view window];
363
364    // Important note: any non-tab subviews not added to |permanentSubviews_|
365    // (see |-addSubviewToPermanentList:|) will be wiped out.
366    permanentSubviews_.reset([[NSMutableArray alloc] init]);
367
368    defaultFavicon_.reset(
369        [app::mac::GetCachedImageWithName(@"nav.pdf") retain]);
370
371    [self setIndentForControls:[[self class] defaultIndentForControls]];
372
373    // TODO(viettrungluu): WTF? "For some reason, if the view is present in the
374    // nib a priori, it draws correctly. If we create it in code and add it to
375    // the tab view, it draws with all sorts of crazy artifacts."
376    newTabButton_ = [view newTabButton];
377    [self addSubviewToPermanentList:newTabButton_];
378    [newTabButton_ setTarget:nil];
379    [newTabButton_ setAction:@selector(commandDispatch:)];
380    [newTabButton_ setTag:IDC_NEW_TAB];
381
382    profileMenuButton_ = [view profileMenuButton];
383    [self addSubviewToPermanentList:profileMenuButton_];
384    [self updateProfileMenuButton];
385    // Register pref observers for profile name.
386    notificationBridge_.reset(
387        new TabStripControllerInternal::NotificationBridge(
388            self, browser_->profile()->GetPrefs()));
389
390    // Set the images from code because Cocoa fails to find them in our sub
391    // bundle during tests.
392    [newTabButton_ setImage:app::mac::GetCachedImageWithName(kNewTabImage)];
393    [newTabButton_ setAlternateImage:
394        app::mac::GetCachedImageWithName(kNewTabPressedImage)];
395    newTabButtonShowingHoverImage_ = NO;
396    newTabTrackingArea_.reset(
397        [[CrTrackingArea alloc] initWithRect:[newTabButton_ bounds]
398                                     options:(NSTrackingMouseEnteredAndExited |
399                                              NSTrackingActiveAlways)
400                                proxiedOwner:self
401                                    userInfo:nil]);
402    if (browserWindow)  // Nil for Browsers without a tab strip (e.g. popups).
403      [newTabTrackingArea_ clearOwnerWhenWindowWillClose:browserWindow];
404    [newTabButton_ addTrackingArea:newTabTrackingArea_.get()];
405    targetFrames_.reset([[NSMutableDictionary alloc] init]);
406
407    dragBlockingView_.reset(
408        [[TabStripControllerDragBlockingView alloc] initWithFrame:NSZeroRect
409                                                       controller:self]);
410    [self addSubviewToPermanentList:dragBlockingView_];
411
412    newTabTargetFrame_ = NSMakeRect(0, 0, 0, 0);
413    availableResizeWidth_ = kUseFullAvailableWidth;
414
415    closingControllers_.reset([[NSMutableSet alloc] init]);
416
417    // Install the permanent subviews.
418    [self regenerateSubviewList];
419
420    // Watch for notifications that the tab strip view has changed size so
421    // we can tell it to layout for the new size.
422    [[NSNotificationCenter defaultCenter]
423        addObserver:self
424           selector:@selector(tabViewFrameChanged:)
425               name:NSViewFrameDidChangeNotification
426             object:tabStripView_];
427
428    trackingArea_.reset([[CrTrackingArea alloc]
429        initWithRect:NSZeroRect  // Ignored by NSTrackingInVisibleRect
430             options:NSTrackingMouseEnteredAndExited |
431                     NSTrackingMouseMoved |
432                     NSTrackingActiveAlways |
433                     NSTrackingInVisibleRect
434        proxiedOwner:self
435            userInfo:nil]);
436    if (browserWindow)  // Nil for Browsers without a tab strip (e.g. popups).
437      [trackingArea_ clearOwnerWhenWindowWillClose:browserWindow];
438    [tabStripView_ addTrackingArea:trackingArea_.get()];
439
440    // Check to see if the mouse is currently in our bounds so we can
441    // enable the tracking areas.  Otherwise we won't get hover states
442    // or tab gradients if we load the window up under the mouse.
443    NSPoint mouseLoc = [[view window] mouseLocationOutsideOfEventStream];
444    mouseLoc = [view convertPoint:mouseLoc fromView:nil];
445    if (NSPointInRect(mouseLoc, [view bounds])) {
446      [self setTabTrackingAreasEnabled:YES];
447      mouseInside_ = YES;
448    }
449
450    // Set accessibility descriptions. http://openradar.appspot.com/7496255
451    NSString* description = l10n_util::GetNSStringWithFixup(IDS_ACCNAME_NEWTAB);
452    [[newTabButton_ cell]
453        accessibilitySetOverrideValue:description
454                         forAttribute:NSAccessibilityDescriptionAttribute];
455
456    // Controller may have been (re-)created by switching layout modes, which
457    // means the tab model is already fully formed with tabs. Need to walk the
458    // list and create the UI for each.
459    const int existingTabCount = tabStripModel_->count();
460    const TabContentsWrapper* selection =
461        tabStripModel_->GetSelectedTabContents();
462    for (int i = 0; i < existingTabCount; ++i) {
463      TabContentsWrapper* currentContents = tabStripModel_->GetTabContentsAt(i);
464      [self insertTabWithContents:currentContents
465                          atIndex:i
466                     inForeground:NO];
467      if (selection == currentContents) {
468        // Must manually force a selection since the model won't send
469        // selection messages in this scenario.
470        [self selectTabWithContents:currentContents
471                   previousContents:NULL
472                            atIndex:i
473                        userGesture:NO];
474      }
475    }
476    // Don't lay out the tabs until after the controller has been fully
477    // constructed. The |verticalLayout_| flag has not been initialized by
478    // subclasses at this point, which would cause layout to potentially use
479    // the wrong mode.
480    if (existingTabCount) {
481      [self performSelectorOnMainThread:@selector(layoutTabs)
482                             withObject:nil
483                          waitUntilDone:NO];
484    }
485  }
486  return self;
487}
488
489- (void)dealloc {
490  if (trackingArea_.get())
491    [tabStripView_ removeTrackingArea:trackingArea_.get()];
492
493  [newTabButton_ removeTrackingArea:newTabTrackingArea_.get()];
494  // Invalidate all closing animations so they don't call back to us after
495  // we're gone.
496  for (TabController* controller in closingControllers_.get()) {
497    NSView* view = [controller view];
498    [[[view animationForKey:@"frameOrigin"] delegate] invalidate];
499  }
500  [[NSNotificationCenter defaultCenter] removeObserver:self];
501  [super dealloc];
502}
503
504+ (CGFloat)defaultTabHeight {
505  return 25.0;
506}
507
508+ (CGFloat)defaultIndentForControls {
509  // Default indentation leaves enough room so tabs don't overlap with the
510  // window controls.
511  return 70.0;
512}
513
514// Finds the TabContentsController associated with the given index into the tab
515// model and swaps out the sole child of the contentArea to display its
516// contents.
517- (void)swapInTabAtIndex:(NSInteger)modelIndex {
518  DCHECK(modelIndex >= 0 && modelIndex < tabStripModel_->count());
519  NSInteger index = [self indexFromModelIndex:modelIndex];
520  TabContentsController* controller = [tabContentsArray_ objectAtIndex:index];
521
522  // Resize the new view to fit the window. Calling |view| may lazily
523  // instantiate the TabContentsController from the nib. Until we call
524  // |-ensureContentsVisible|, the controller doesn't install the RWHVMac into
525  // the view hierarchy. This is in order to avoid sending the renderer a
526  // spurious default size loaded from the nib during the call to |-view|.
527  NSView* newView = [controller view];
528
529  // Turns content autoresizing off, so removing and inserting views won't
530  // trigger unnecessary content relayout.
531  [controller ensureContentsSizeDoesNotChange];
532
533  // Remove the old view from the view hierarchy. We know there's only one
534  // child of |switchView_| because we're the one who put it there. There
535  // may not be any children in the case of a tab that's been closed, in
536  // which case there's no swapping going on.
537  NSArray* subviews = [switchView_ subviews];
538  if ([subviews count]) {
539    NSView* oldView = [subviews objectAtIndex:0];
540    // Set newView frame to the oldVew frame to prevent NSSplitView hosting
541    // sidebar and tab content from resizing sidebar's content view.
542    // ensureContentsVisible (see below) sets content size and autoresizing
543    // properties.
544    [newView setFrame:[oldView frame]];
545    [switchView_ replaceSubview:oldView with:newView];
546  } else {
547    [newView setFrame:[switchView_ bounds]];
548    [switchView_ addSubview:newView];
549  }
550
551  // New content is in place, delegate should adjust itself accordingly.
552  [delegate_ onSelectTabWithContents:[controller tabContents]];
553
554  // It also restores content autoresizing properties.
555  [controller ensureContentsVisible];
556
557  // Tell per-tab sheet manager about currently selected tab.
558  if (sheetController_.get()) {
559    [sheetController_ setActiveView:newView];
560  }
561
562  // Make sure the new tabs's sheets are visible (necessary when a background
563  // tab opened a sheet while it was in the background and now becomes active).
564  TabContentsWrapper* newTab = tabStripModel_->GetTabContentsAt(modelIndex);
565  DCHECK(newTab);
566  if (newTab) {
567    TabContents::ConstrainedWindowList::iterator it, end;
568    end = newTab->tab_contents()->constrained_window_end();
569    NSWindowController* controller = [[newView window] windowController];
570    DCHECK([controller isKindOfClass:[BrowserWindowController class]]);
571
572    for (it = newTab->tab_contents()->constrained_window_begin();
573         it != end;
574         ++it) {
575      ConstrainedWindow* constrainedWindow = *it;
576      static_cast<ConstrainedWindowMac*>(constrainedWindow)->Realize(
577          static_cast<BrowserWindowController*>(controller));
578    }
579  }
580}
581
582// Create a new tab view and set its cell correctly so it draws the way we want
583// it to. It will be sized and positioned by |-layoutTabs| so there's no need to
584// set the frame here. This also creates the view as hidden, it will be
585// shown during layout.
586- (TabController*)newTab {
587  TabController* controller = [[[TabController alloc] init] autorelease];
588  [controller setTarget:self];
589  [controller setAction:@selector(selectTab:)];
590  [[controller view] setHidden:YES];
591
592  return controller;
593}
594
595// (Private) Returns the number of open tabs in the tab strip. This is the
596// number of TabControllers we know about (as there's a 1-to-1 mapping from
597// these controllers to a tab) less the number of closing tabs.
598- (NSInteger)numberOfOpenTabs {
599  return static_cast<NSInteger>(tabStripModel_->count());
600}
601
602// (Private) Returns the number of open, mini-tabs.
603- (NSInteger)numberOfOpenMiniTabs {
604  // Ask the model for the number of mini tabs. Note that tabs which are in
605  // the process of closing (i.e., whose controllers are in
606  // |closingControllers_|) have already been removed from the model.
607  return tabStripModel_->IndexOfFirstNonMiniTab();
608}
609
610// (Private) Returns the number of open, non-mini tabs.
611- (NSInteger)numberOfOpenNonMiniTabs {
612  NSInteger number = [self numberOfOpenTabs] - [self numberOfOpenMiniTabs];
613  DCHECK_GE(number, 0);
614  return number;
615}
616
617// Given an index into the tab model, returns the index into the tab controller
618// or tab contents controller array accounting for tabs that are currently
619// closing. For example, if there are two tabs in the process of closing before
620// |index|, this returns |index| + 2. If there are no closing tabs, this will
621// return |index|.
622- (NSInteger)indexFromModelIndex:(NSInteger)index {
623  DCHECK(index >= 0);
624  if (index < 0)
625    return index;
626
627  NSInteger i = 0;
628  for (TabController* controller in tabArray_.get()) {
629    if ([closingControllers_ containsObject:controller]) {
630      DCHECK([(TabView*)[controller view] isClosing]);
631      ++index;
632    }
633    if (i == index)  // No need to check anything after, it has no effect.
634      break;
635    ++i;
636  }
637  return index;
638}
639
640
641// Returns the index of the subview |view|. Returns -1 if not present. Takes
642// closing tabs into account such that this index will correctly match the tab
643// model. If |view| is in the process of closing, returns -1, as closing tabs
644// are no longer in the model.
645- (NSInteger)modelIndexForTabView:(NSView*)view {
646  NSInteger index = 0;
647  for (TabController* current in tabArray_.get()) {
648    // If |current| is closing, skip it.
649    if ([closingControllers_ containsObject:current])
650      continue;
651    else if ([current view] == view)
652      return index;
653    ++index;
654  }
655  return -1;
656}
657
658// Returns the index of the contents subview |view|. Returns -1 if not present.
659// Takes closing tabs into account such that this index will correctly match the
660// tab model. If |view| is in the process of closing, returns -1, as closing
661// tabs are no longer in the model.
662- (NSInteger)modelIndexForContentsView:(NSView*)view {
663  NSInteger index = 0;
664  NSInteger i = 0;
665  for (TabContentsController* current in tabContentsArray_.get()) {
666    // If the TabController corresponding to |current| is closing, skip it.
667    TabController* controller = [tabArray_ objectAtIndex:i];
668    if ([closingControllers_ containsObject:controller]) {
669      ++i;
670      continue;
671    } else if ([current view] == view) {
672      return index;
673    }
674    ++index;
675    ++i;
676  }
677  return -1;
678}
679
680
681// Returns the view at the given index, using the array of TabControllers to
682// get the associated view. Returns nil if out of range.
683- (NSView*)viewAtIndex:(NSUInteger)index {
684  if (index >= [tabArray_ count])
685    return NULL;
686  return [[tabArray_ objectAtIndex:index] view];
687}
688
689- (NSUInteger)viewsCount {
690  return [tabArray_ count];
691}
692
693// Called when the user clicks a tab. Tell the model the selection has changed,
694// which feeds back into us via a notification.
695- (void)selectTab:(id)sender {
696  DCHECK([sender isKindOfClass:[NSView class]]);
697  int index = [self modelIndexForTabView:sender];
698  if (tabStripModel_->ContainsIndex(index))
699    tabStripModel_->ActivateTabAt(index, true);
700}
701
702// Called when the user closes a tab. Asks the model to close the tab. |sender|
703// is the TabView that is potentially going away.
704- (void)closeTab:(id)sender {
705  DCHECK([sender isKindOfClass:[TabView class]]);
706  if ([hoveredTab_ isEqual:sender]) {
707    hoveredTab_ = nil;
708  }
709
710  NSInteger index = [self modelIndexForTabView:sender];
711  if (!tabStripModel_->ContainsIndex(index))
712    return;
713
714  TabContentsWrapper* contents = tabStripModel_->GetTabContentsAt(index);
715  if (contents)
716    UserMetrics::RecordAction(UserMetricsAction("CloseTab_Mouse"),
717                              contents->tab_contents()->profile());
718  const NSInteger numberOfOpenTabs = [self numberOfOpenTabs];
719  if (numberOfOpenTabs > 1) {
720    bool isClosingLastTab = index == numberOfOpenTabs - 1;
721    if (!isClosingLastTab) {
722      // Limit the width available for laying out tabs so that tabs are not
723      // resized until a later time (when the mouse leaves the tab strip).
724      // However, if the tab being closed is a pinned tab, break out of
725      // rapid-closure mode since the mouse is almost guaranteed not to be over
726      // the closebox of the adjacent tab (due to the difference in widths).
727      // TODO(pinkerton): re-visit when handling tab overflow.
728      // http://crbug.com/188
729      if (tabStripModel_->IsTabPinned(index)) {
730        availableResizeWidth_ = kUseFullAvailableWidth;
731      } else {
732        NSView* penultimateTab = [self viewAtIndex:numberOfOpenTabs - 2];
733        availableResizeWidth_ = NSMaxX([penultimateTab frame]);
734      }
735    } else {
736      // If the rightmost tab is closed, change the available width so that
737      // another tab's close button lands below the cursor (assuming the tabs
738      // are currently below their maximum width and can grow).
739      NSView* lastTab = [self viewAtIndex:numberOfOpenTabs - 1];
740      availableResizeWidth_ = NSMaxX([lastTab frame]);
741    }
742    tabStripModel_->CloseTabContentsAt(
743        index,
744        TabStripModel::CLOSE_USER_GESTURE |
745        TabStripModel::CLOSE_CREATE_HISTORICAL_TAB);
746  } else {
747    // Use the standard window close if this is the last tab
748    // this prevents the tab from being removed from the model until after
749    // the window dissapears
750    [[tabStripView_ window] performClose:nil];
751  }
752}
753
754// Dispatch context menu commands for the given tab controller.
755- (void)commandDispatch:(TabStripModel::ContextMenuCommand)command
756          forController:(TabController*)controller {
757  int index = [self modelIndexForTabView:[controller view]];
758  if (tabStripModel_->ContainsIndex(index))
759    tabStripModel_->ExecuteContextMenuCommand(index, command);
760}
761
762// Returns YES if the specificed command should be enabled for the given
763// controller.
764- (BOOL)isCommandEnabled:(TabStripModel::ContextMenuCommand)command
765           forController:(TabController*)controller {
766  int index = [self modelIndexForTabView:[controller view]];
767  if (!tabStripModel_->ContainsIndex(index))
768    return NO;
769  return tabStripModel_->IsContextMenuCommandEnabled(index, command) ? YES : NO;
770}
771
772- (void)insertPlaceholderForTab:(TabView*)tab
773                          frame:(NSRect)frame
774                  yStretchiness:(CGFloat)yStretchiness {
775  placeholderTab_ = tab;
776  placeholderFrame_ = frame;
777  placeholderStretchiness_ = yStretchiness;
778  [self layoutTabsWithAnimation:initialLayoutComplete_ regenerateSubviews:NO];
779}
780
781- (BOOL)isDragSessionActive {
782  return placeholderTab_ != nil;
783}
784
785- (BOOL)isTabFullyVisible:(TabView*)tab {
786  NSRect frame = [tab frame];
787  return NSMinX(frame) >= [self indentForControls] &&
788      NSMaxX(frame) <= NSMaxX([tabStripView_ frame]);
789}
790
791- (void)showNewTabButton:(BOOL)show {
792  forceNewTabButtonHidden_ = show ? NO : YES;
793  if (forceNewTabButtonHidden_)
794    [newTabButton_ setHidden:YES];
795}
796
797// Lay out all tabs in the order of their TabContentsControllers, which matches
798// the ordering in the TabStripModel. This call isn't that expensive, though
799// it is O(n) in the number of tabs. Tabs will animate to their new position
800// if the window is visible and |animate| is YES.
801// TODO(pinkerton): Note this doesn't do too well when the number of min-sized
802// tabs would cause an overflow. http://crbug.com/188
803- (void)layoutTabsWithAnimation:(BOOL)animate
804             regenerateSubviews:(BOOL)doUpdate {
805  DCHECK([NSThread isMainThread]);
806  if (![tabArray_ count])
807    return;
808
809  const CGFloat kMaxTabWidth = [TabController maxTabWidth];
810  const CGFloat kMinTabWidth = [TabController minTabWidth];
811  const CGFloat kMinSelectedTabWidth = [TabController minSelectedTabWidth];
812  const CGFloat kMiniTabWidth = [TabController miniTabWidth];
813  const CGFloat kAppTabWidth = [TabController appTabWidth];
814
815  NSRect enclosingRect = NSZeroRect;
816  ScopedNSAnimationContextGroup mainAnimationGroup(animate);
817  mainAnimationGroup.SetCurrentContextDuration(kAnimationDuration);
818
819  // Update the current subviews and their z-order if requested.
820  if (doUpdate)
821    [self regenerateSubviewList];
822
823  // Compute the base width of tabs given how much room we're allowed. Note that
824  // mini-tabs have a fixed width. We may not be able to use the entire width
825  // if the user is quickly closing tabs. This may be negative, but that's okay
826  // (taken care of by |MAX()| when calculating tab sizes).
827  CGFloat availableSpace = 0;
828  if (verticalLayout_) {
829    availableSpace = NSHeight([tabStripView_ bounds]);
830  } else {
831    if ([self inRapidClosureMode]) {
832      availableSpace = availableResizeWidth_;
833    } else {
834      availableSpace = NSWidth([tabStripView_ frame]);
835
836      // Account for the widths of the new tab button, the incognito badge, and
837      // the fullscreen button if any/all are present.
838      availableSpace -= NSWidth([newTabButton_ frame]) + kNewTabButtonOffset;
839      if (browser_->profile()->IsOffTheRecord())
840        availableSpace -= kIncognitoBadgeTabStripShrink;
841      if ([[tabStripView_ window]
842          respondsToSelector:@selector(toggleFullScreen:)]) {
843        NSButton* fullscreenButton = [[tabStripView_ window]
844            standardWindowButton:NSWindowFullScreenButton];
845        if (fullscreenButton)
846          availableSpace -= [fullscreenButton frame].size.width;
847      }
848    }
849    availableSpace -= [self indentForControls];
850  }
851
852  // This may be negative, but that's okay (taken care of by |MAX()| when
853  // calculating tab sizes). "mini" tabs in horizontal mode just get a special
854  // section, they don't change size.
855  CGFloat availableSpaceForNonMini = availableSpace;
856  if (!verticalLayout_) {
857      availableSpaceForNonMini -=
858          [self numberOfOpenMiniTabs] * (kMiniTabWidth - kTabOverlap);
859  }
860
861  // Initialize |nonMiniTabWidth| in case there aren't any non-mini-tabs; this
862  // value shouldn't actually be used.
863  CGFloat nonMiniTabWidth = kMaxTabWidth;
864  const NSInteger numberOfOpenNonMiniTabs = [self numberOfOpenNonMiniTabs];
865  if (!verticalLayout_ && numberOfOpenNonMiniTabs) {
866    // Find the width of a non-mini-tab. This only applies to horizontal
867    // mode. Add in the amount we "get back" from the tabs overlapping.
868    availableSpaceForNonMini += (numberOfOpenNonMiniTabs - 1) * kTabOverlap;
869
870    // Divide up the space between the non-mini-tabs.
871    nonMiniTabWidth = availableSpaceForNonMini / numberOfOpenNonMiniTabs;
872
873    // Clamp the width between the max and min.
874    nonMiniTabWidth = MAX(MIN(nonMiniTabWidth, kMaxTabWidth), kMinTabWidth);
875  }
876
877  BOOL visible = [[tabStripView_ window] isVisible];
878
879  CGFloat offset = [self indentForControls];
880  bool hasPlaceholderGap = false;
881  for (TabController* tab in tabArray_.get()) {
882    // Ignore a tab that is going through a close animation.
883    if ([closingControllers_ containsObject:tab])
884      continue;
885
886    BOOL isPlaceholder = [[tab view] isEqual:placeholderTab_];
887    NSRect tabFrame = [[tab view] frame];
888    tabFrame.size.height = [[self class] defaultTabHeight] + 1;
889    if (verticalLayout_) {
890      tabFrame.origin.y = availableSpace - tabFrame.size.height - offset;
891      tabFrame.origin.x = 0;
892    } else {
893      tabFrame.origin.y = 0;
894      tabFrame.origin.x = offset;
895    }
896    // If the tab is hidden, we consider it a new tab. We make it visible
897    // and animate it in.
898    BOOL newTab = [[tab view] isHidden];
899    if (newTab)
900      [[tab view] setHidden:NO];
901
902    if (isPlaceholder) {
903      // Move the current tab to the correct location instantly.
904      // We need a duration or else it doesn't cancel an inflight animation.
905      ScopedNSAnimationContextGroup localAnimationGroup(animate);
906      localAnimationGroup.SetCurrentContextShortestDuration();
907      if (verticalLayout_)
908        tabFrame.origin.y = availableSpace - tabFrame.size.height - offset;
909      else
910        tabFrame.origin.x = placeholderFrame_.origin.x;
911      // TODO(alcor): reenable this
912      //tabFrame.size.height += 10.0 * placeholderStretchiness_;
913      id target = animate ? [[tab view] animator] : [tab view];
914      [target setFrame:tabFrame];
915
916      // Store the frame by identifier to aviod redundant calls to animator.
917      NSValue* identifier = [NSValue valueWithPointer:[tab view]];
918      [targetFrames_ setObject:[NSValue valueWithRect:tabFrame]
919                        forKey:identifier];
920      continue;
921    }
922
923    if (placeholderTab_ && !hasPlaceholderGap) {
924      const CGFloat placeholderMin =
925          verticalLayout_ ? NSMinY(placeholderFrame_) :
926                            NSMinX(placeholderFrame_);
927      if (verticalLayout_) {
928        if (NSMidY(tabFrame) > placeholderMin) {
929          hasPlaceholderGap = true;
930          offset += NSHeight(placeholderFrame_);
931          tabFrame.origin.y = availableSpace - tabFrame.size.height - offset;
932        }
933      } else {
934        // If the left edge is to the left of the placeholder's left, but the
935        // mid is to the right of it slide over to make space for it.
936        if (NSMidX(tabFrame) > placeholderMin) {
937          hasPlaceholderGap = true;
938          offset += NSWidth(placeholderFrame_);
939          offset -= kTabOverlap;
940          tabFrame.origin.x = offset;
941        }
942      }
943    }
944
945    // Set the width. Selected tabs are slightly wider when things get really
946    // small and thus we enforce a different minimum width.
947    tabFrame.size.width = [tab mini] ?
948        ([tab app] ? kAppTabWidth : kMiniTabWidth) : nonMiniTabWidth;
949    if ([tab selected])
950      tabFrame.size.width = MAX(tabFrame.size.width, kMinSelectedTabWidth);
951
952    // Animate a new tab in by putting it below the horizon unless told to put
953    // it in a specific location (i.e., from a drop).
954    // TODO(pinkerton): figure out vertical tab animations.
955    if (newTab && visible && animate) {
956      if (NSEqualRects(droppedTabFrame_, NSZeroRect)) {
957        [[tab view] setFrame:NSOffsetRect(tabFrame, 0, -NSHeight(tabFrame))];
958      } else {
959        [[tab view] setFrame:droppedTabFrame_];
960        droppedTabFrame_ = NSZeroRect;
961      }
962    }
963
964    // Check the frame by identifier to avoid redundant calls to animator.
965    id frameTarget = visible && animate ? [[tab view] animator] : [tab view];
966    NSValue* identifier = [NSValue valueWithPointer:[tab view]];
967    NSValue* oldTargetValue = [targetFrames_ objectForKey:identifier];
968    if (!oldTargetValue ||
969        !NSEqualRects([oldTargetValue rectValue], tabFrame)) {
970      [frameTarget setFrame:tabFrame];
971      [targetFrames_ setObject:[NSValue valueWithRect:tabFrame]
972                        forKey:identifier];
973    }
974
975    enclosingRect = NSUnionRect(tabFrame, enclosingRect);
976
977    if (verticalLayout_) {
978      offset += NSHeight(tabFrame);
979    } else {
980      offset += NSWidth(tabFrame);
981      offset -= kTabOverlap;
982    }
983  }
984
985  // Hide the new tab button if we're explicitly told to. It may already
986  // be hidden, doing it again doesn't hurt. Otherwise position it
987  // appropriately, showing it if necessary.
988  if (forceNewTabButtonHidden_) {
989    [newTabButton_ setHidden:YES];
990  } else {
991    NSRect newTabNewFrame = [newTabButton_ frame];
992    // We've already ensured there's enough space for the new tab button
993    // so we don't have to check it against the available space. We do need
994    // to make sure we put it after any placeholder.
995    CGFloat maxTabX = MAX(offset, NSMaxX(placeholderFrame_) - kTabOverlap);
996    newTabNewFrame.origin = NSMakePoint(maxTabX + kNewTabButtonOffset, 0);
997    if ([tabContentsArray_ count])
998      [newTabButton_ setHidden:NO];
999
1000    if (!NSEqualRects(newTabTargetFrame_, newTabNewFrame)) {
1001      // Set the new tab button image correctly based on where the cursor is.
1002      NSWindow* window = [tabStripView_ window];
1003      NSPoint currentMouse = [window mouseLocationOutsideOfEventStream];
1004      currentMouse = [tabStripView_ convertPoint:currentMouse fromView:nil];
1005
1006      BOOL shouldShowHover = [newTabButton_ pointIsOverButton:currentMouse];
1007      [self setNewTabButtonHoverState:shouldShowHover];
1008
1009      // Move the new tab button into place. We want to animate the new tab
1010      // button if it's moving to the left (closing a tab), but not when it's
1011      // moving to the right (inserting a new tab). If moving right, we need
1012      // to use a very small duration to make sure we cancel any in-flight
1013      // animation to the left.
1014      if (visible && animate) {
1015        ScopedNSAnimationContextGroup localAnimationGroup(true);
1016        BOOL movingLeft = NSMinX(newTabNewFrame) < NSMinX(newTabTargetFrame_);
1017        if (!movingLeft) {
1018          localAnimationGroup.SetCurrentContextShortestDuration();
1019        }
1020        [[newTabButton_ animator] setFrame:newTabNewFrame];
1021        newTabTargetFrame_ = newTabNewFrame;
1022      } else {
1023        [newTabButton_ setFrame:newTabNewFrame];
1024        newTabTargetFrame_ = newTabNewFrame;
1025      }
1026    }
1027  }
1028
1029  if (profileMenuButton_ && ![profileMenuButton_ isHidden]) {
1030    CGFloat maxX;
1031    if ([newTabButton_ isHidden]) {
1032      maxX = std::max(offset, NSMaxX(placeholderFrame_) - kTabOverlap);
1033    } else {
1034      maxX = NSMaxX(newTabTargetFrame_);
1035    }
1036    NSRect profileMenuButtonFrame = [profileMenuButton_ frame];
1037    NSSize minSize = [profileMenuButton_ minControlSize];
1038
1039    // Make room for the full screen button if necessary.
1040    if (!hasUpdatedProfileMenuButtonXOffset_) {
1041      hasUpdatedProfileMenuButtonXOffset_ = YES;
1042      if ([[profileMenuButton_ window]
1043          respondsToSelector:@selector(toggleFullScreen:)]) {
1044        NSButton* fullscreenButton = [[profileMenuButton_ window]
1045            standardWindowButton:NSWindowFullScreenButton];
1046        if (fullscreenButton) {
1047          profileMenuButtonFrame.origin.x = NSMinX([fullscreenButton frame]) -
1048              NSWidth(profileMenuButtonFrame) - kProfileMenuButtonOffset;
1049        }
1050      }
1051    }
1052
1053    // TODO(sail): Animate this.
1054    CGFloat availableWidth = NSMaxX(profileMenuButtonFrame) - maxX -
1055                             kProfileMenuButtonOffset;
1056    if (availableWidth > minSize.width) {
1057      [profileMenuButton_ setShouldShowProfileDisplayName:YES];
1058    } else {
1059      [profileMenuButton_ setShouldShowProfileDisplayName:NO];
1060    }
1061
1062    NSSize desiredSize = [profileMenuButton_ desiredControlSize];
1063    NSRect rect;
1064    rect.size.width = std::min(desiredSize.width,
1065                               std::max(availableWidth, minSize.width));
1066    rect.size.height = desiredSize.height;
1067    rect.origin.y = NSMaxY(profileMenuButtonFrame) - rect.size.height;
1068    rect.origin.x = NSMaxX(profileMenuButtonFrame) - rect.size.width;
1069    [profileMenuButton_ setFrame:rect];
1070  }
1071
1072  [dragBlockingView_ setFrame:enclosingRect];
1073
1074  // Mark that we've successfully completed layout of at least one tab.
1075  initialLayoutComplete_ = YES;
1076}
1077
1078// When we're told to layout from the public API we usually want to animate,
1079// except when it's the first time.
1080- (void)layoutTabs {
1081  [self layoutTabsWithAnimation:initialLayoutComplete_ regenerateSubviews:YES];
1082}
1083
1084// Handles setting the title of the tab based on the given |contents|. Uses
1085// a canned string if |contents| is NULL.
1086- (void)setTabTitle:(NSViewController*)tab withContents:(TabContents*)contents {
1087  NSString* titleString = nil;
1088  if (contents)
1089    titleString = base::SysUTF16ToNSString(contents->GetTitle());
1090  if (![titleString length]) {
1091    titleString = l10n_util::GetNSString(IDS_BROWSER_WINDOW_MAC_TAB_UNTITLED);
1092  }
1093  [tab setTitle:titleString];
1094}
1095
1096// Called when a notification is received from the model to insert a new tab
1097// at |modelIndex|.
1098- (void)insertTabWithContents:(TabContentsWrapper*)contents
1099                      atIndex:(NSInteger)modelIndex
1100                 inForeground:(bool)inForeground {
1101  DCHECK(contents);
1102  DCHECK(modelIndex == TabStripModel::kNoTab ||
1103         tabStripModel_->ContainsIndex(modelIndex));
1104
1105  // Take closing tabs into account.
1106  NSInteger index = [self indexFromModelIndex:modelIndex];
1107
1108  // Make a new tab. Load the contents of this tab from the nib and associate
1109  // the new controller with |contents| so it can be looked up later.
1110  scoped_nsobject<TabContentsController> contentsController(
1111      [[TabContentsController alloc] initWithContents:contents->tab_contents()
1112                                             delegate:self]);
1113  [tabContentsArray_ insertObject:contentsController atIndex:index];
1114
1115  // Make a new tab and add it to the strip. Keep track of its controller.
1116  TabController* newController = [self newTab];
1117  [newController setMini:tabStripModel_->IsMiniTab(modelIndex)];
1118  [newController setPinned:tabStripModel_->IsTabPinned(modelIndex)];
1119  [newController setApp:tabStripModel_->IsAppTab(modelIndex)];
1120  [newController setUrl:contents->tab_contents()->GetURL()];
1121  [tabArray_ insertObject:newController atIndex:index];
1122  NSView* newView = [newController view];
1123
1124  // Set the originating frame to just below the strip so that it animates
1125  // upwards as it's being initially layed out. Oddly, this works while doing
1126  // something similar in |-layoutTabs| confuses the window server.
1127  [newView setFrame:NSOffsetRect([newView frame],
1128                                 0, -[[self class] defaultTabHeight])];
1129
1130  [self setTabTitle:newController withContents:contents->tab_contents()];
1131
1132  // If a tab is being inserted, we can again use the entire tab strip width
1133  // for layout.
1134  availableResizeWidth_ = kUseFullAvailableWidth;
1135
1136  // We don't need to call |-layoutTabs| if the tab will be in the foreground
1137  // because it will get called when the new tab is selected by the tab model.
1138  // Whenever |-layoutTabs| is called, it'll also add the new subview.
1139  if (!inForeground) {
1140    [self layoutTabs];
1141  }
1142
1143  // During normal loading, we won't yet have a favicon and we'll get
1144  // subsequent state change notifications to show the throbber, but when we're
1145  // dragging a tab out into a new window, we have to put the tab's favicon
1146  // into the right state up front as we won't be told to do it from anywhere
1147  // else.
1148  [self updateFaviconForContents:contents->tab_contents() atIndex:modelIndex];
1149
1150  // Send a broadcast that the number of tabs have changed.
1151  [[NSNotificationCenter defaultCenter]
1152      postNotificationName:kTabStripNumberOfTabsChanged
1153                    object:self];
1154}
1155
1156// Called when a notification is received from the model to select a particular
1157// tab. Swaps in the toolbar and content area associated with |newContents|.
1158- (void)selectTabWithContents:(TabContentsWrapper*)newContents
1159             previousContents:(TabContentsWrapper*)oldContents
1160                      atIndex:(NSInteger)modelIndex
1161                  userGesture:(bool)wasUserGesture {
1162  // Take closing tabs into account.
1163  NSInteger index = [self indexFromModelIndex:modelIndex];
1164
1165  if (oldContents && oldContents != newContents) {
1166    int oldModelIndex =
1167        browser_->GetIndexOfController(&(oldContents->controller()));
1168    if (oldModelIndex != -1) {  // When closing a tab, the old tab may be gone.
1169      NSInteger oldIndex = [self indexFromModelIndex:oldModelIndex];
1170      TabContentsController* oldController =
1171          [tabContentsArray_ objectAtIndex:oldIndex];
1172      [oldController willBecomeUnselectedTab];
1173      oldContents->view()->StoreFocus();
1174      oldContents->tab_contents()->WasHidden();
1175    }
1176  }
1177
1178  // De-select all other tabs and select the new tab.
1179  int i = 0;
1180  for (TabController* current in tabArray_.get()) {
1181    [current setSelected:(i == index) ? YES : NO];
1182    ++i;
1183  }
1184
1185  // Tell the new tab contents it is about to become the selected tab. Here it
1186  // can do things like make sure the toolbar is up to date.
1187  TabContentsController* newController =
1188      [tabContentsArray_ objectAtIndex:index];
1189  [newController willBecomeSelectedTab];
1190
1191  // Relayout for new tabs and to let the selected tab grow to be larger in
1192  // size than surrounding tabs if the user has many. This also raises the
1193  // selected tab to the top.
1194  [self layoutTabs];
1195
1196  // Swap in the contents for the new tab.
1197  [self swapInTabAtIndex:modelIndex];
1198
1199  if (newContents) {
1200    newContents->tab_contents()->DidBecomeSelected();
1201    newContents->view()->RestoreFocus();
1202
1203    if (newContents->find_tab_helper()->find_ui_active())
1204      browser_->GetFindBarController()->find_bar()->SetFocusAndSelection();
1205  }
1206}
1207
1208- (void)tabReplacedWithContents:(TabContentsWrapper*)newContents
1209               previousContents:(TabContentsWrapper*)oldContents
1210                        atIndex:(NSInteger)modelIndex {
1211  NSInteger index = [self indexFromModelIndex:modelIndex];
1212  TabContentsController* oldController =
1213      [tabContentsArray_ objectAtIndex:index];
1214  DCHECK_EQ(oldContents->tab_contents(), [oldController tabContents]);
1215
1216  // Simply create a new TabContentsController for |newContents| and place it
1217  // into the array, replacing |oldContents|.  A TabSelectedAt notification will
1218  // follow, at which point we will install the new view.
1219  scoped_nsobject<TabContentsController> newController(
1220      [[TabContentsController alloc]
1221          initWithContents:newContents->tab_contents()
1222                  delegate:self]);
1223
1224  // Bye bye, |oldController|.
1225  [tabContentsArray_ replaceObjectAtIndex:index withObject:newController];
1226
1227  [delegate_ onReplaceTabWithContents:newContents->tab_contents()];
1228
1229  // Fake a tab changed notification to force tab titles and favicons to update.
1230  [self tabChangedWithContents:newContents
1231                       atIndex:modelIndex
1232                    changeType:TabStripModelObserver::ALL];
1233}
1234
1235// Remove all knowledge about this tab and its associated controller, and remove
1236// the view from the strip.
1237- (void)removeTab:(TabController*)controller {
1238  NSUInteger index = [tabArray_ indexOfObject:controller];
1239
1240  // Release the tab contents controller so those views get destroyed. This
1241  // will remove all the tab content Cocoa views from the hierarchy. A
1242  // subsequent "select tab" notification will follow from the model. To
1243  // tell us what to swap in in its absence.
1244  [tabContentsArray_ removeObjectAtIndex:index];
1245
1246  // Remove the view from the tab strip.
1247  NSView* tab = [controller view];
1248  [tab removeFromSuperview];
1249
1250  // Remove ourself as an observer.
1251  [[NSNotificationCenter defaultCenter]
1252      removeObserver:self
1253                name:NSViewDidUpdateTrackingAreasNotification
1254              object:tab];
1255
1256  // Clear the tab controller's target.
1257  // TODO(viettrungluu): [crbug.com/23829] Find a better way to handle the tab
1258  // controller's target.
1259  [controller setTarget:nil];
1260
1261  if ([hoveredTab_ isEqual:tab])
1262    hoveredTab_ = nil;
1263
1264  NSValue* identifier = [NSValue valueWithPointer:tab];
1265  [targetFrames_ removeObjectForKey:identifier];
1266
1267  // Once we're totally done with the tab, delete its controller
1268  [tabArray_ removeObjectAtIndex:index];
1269}
1270
1271// Called by the CAAnimation delegate when the tab completes the closing
1272// animation.
1273- (void)animationDidStopForController:(TabController*)controller
1274                             finished:(BOOL)finished {
1275  [closingControllers_ removeObject:controller];
1276  [self removeTab:controller];
1277}
1278
1279// Save off which TabController is closing and tell its view's animator
1280// where to move the tab to. Registers a delegate to call back when the
1281// animation is complete in order to remove the tab from the model.
1282- (void)startClosingTabWithAnimation:(TabController*)closingTab {
1283  DCHECK([NSThread isMainThread]);
1284  // Save off the controller into the set of animating tabs. This alerts
1285  // the layout method to not do anything with it and allows us to correctly
1286  // calculate offsets when working with indices into the model.
1287  [closingControllers_ addObject:closingTab];
1288
1289  // Mark the tab as closing. This prevents it from generating any drags or
1290  // selections while it's animating closed.
1291  [(TabView*)[closingTab view] setClosing:YES];
1292
1293  // Register delegate (owned by the animation system).
1294  NSView* tabView = [closingTab view];
1295  CAAnimation* animation = [[tabView animationForKey:@"frameOrigin"] copy];
1296  [animation autorelease];
1297  scoped_nsobject<TabCloseAnimationDelegate> delegate(
1298    [[TabCloseAnimationDelegate alloc] initWithTabStrip:self
1299                                          tabController:closingTab]);
1300  [animation setDelegate:delegate.get()];  // Retains delegate.
1301  NSMutableDictionary* animationDictionary =
1302      [NSMutableDictionary dictionaryWithDictionary:[tabView animations]];
1303  [animationDictionary setObject:animation forKey:@"frameOrigin"];
1304  [tabView setAnimations:animationDictionary];
1305
1306  // Periscope down! Animate the tab.
1307  NSRect newFrame = [tabView frame];
1308  newFrame = NSOffsetRect(newFrame, 0, -newFrame.size.height);
1309  ScopedNSAnimationContextGroup animationGroup(true);
1310  animationGroup.SetCurrentContextDuration(kAnimationDuration);
1311  [[tabView animator] setFrame:newFrame];
1312}
1313
1314// Called when a notification is received from the model that the given tab
1315// has gone away. Start an animation then force a layout to put everything
1316// in motion.
1317- (void)tabDetachedWithContents:(TabContentsWrapper*)contents
1318                        atIndex:(NSInteger)modelIndex {
1319  // Take closing tabs into account.
1320  NSInteger index = [self indexFromModelIndex:modelIndex];
1321
1322  TabController* tab = [tabArray_ objectAtIndex:index];
1323  if (tabStripModel_->count() > 0) {
1324    [self startClosingTabWithAnimation:tab];
1325    [self layoutTabs];
1326  } else {
1327    [self removeTab:tab];
1328  }
1329
1330  // Send a broadcast that the number of tabs have changed.
1331  [[NSNotificationCenter defaultCenter]
1332      postNotificationName:kTabStripNumberOfTabsChanged
1333                    object:self];
1334
1335  [delegate_ onTabDetachedWithContents:contents->tab_contents()];
1336}
1337
1338// A helper routine for creating an NSImageView to hold the favicon or app icon
1339// for |contents|.
1340- (NSImageView*)iconImageViewForContents:(TabContents*)contents {
1341  TabContentsWrapper* wrapper =
1342      TabContentsWrapper::GetCurrentWrapperForContents(contents);
1343  BOOL isApp = wrapper->extension_tab_helper()->is_app();
1344  NSImage* image = nil;
1345  // Favicons come from the renderer, and the renderer draws everything in the
1346  // system color space.
1347  CGColorSpaceRef colorSpace = base::mac::GetSystemColorSpace();
1348  if (isApp) {
1349    SkBitmap* icon = wrapper->extension_tab_helper()->GetExtensionAppIcon();
1350    if (icon)
1351      image = gfx::SkBitmapToNSImageWithColorSpace(*icon, colorSpace);
1352  } else {
1353    image = mac::FaviconForTabContents(contents);
1354  }
1355
1356  // Either we don't have a valid favicon or there was some issue converting it
1357  // from an SkBitmap. Either way, just show the default.
1358  if (!image)
1359    image = defaultFavicon_.get();
1360  NSRect frame = NSMakeRect(0, 0, kIconWidthAndHeight, kIconWidthAndHeight);
1361  NSImageView* view = [[[NSImageView alloc] initWithFrame:frame] autorelease];
1362  [view setImage:image];
1363  return view;
1364}
1365
1366// Updates the current loading state, replacing the icon view with a favicon,
1367// a throbber, the default icon, or nothing at all.
1368- (void)updateFaviconForContents:(TabContents*)contents
1369                         atIndex:(NSInteger)modelIndex {
1370  if (!contents)
1371    return;
1372
1373  static NSImage* throbberWaitingImage =
1374      [ResourceBundle::GetSharedInstance().GetNativeImageNamed(
1375          IDR_THROBBER_WAITING) retain];
1376  static NSImage* throbberLoadingImage =
1377      [ResourceBundle::GetSharedInstance().GetNativeImageNamed(IDR_THROBBER)
1378        retain];
1379  static NSImage* sadFaviconImage =
1380      [ResourceBundle::GetSharedInstance().GetNativeImageNamed(IDR_SAD_FAVICON)
1381        retain];
1382
1383  // Take closing tabs into account.
1384  NSInteger index = [self indexFromModelIndex:modelIndex];
1385  TabController* tabController = [tabArray_ objectAtIndex:index];
1386
1387  bool oldHasIcon = [tabController iconView] != nil;
1388  bool newHasIcon = contents->ShouldDisplayFavicon() ||
1389      tabStripModel_->IsMiniTab(modelIndex);  // Always show icon if mini.
1390
1391  TabLoadingState oldState = [tabController loadingState];
1392  TabLoadingState newState = kTabDone;
1393  NSImage* throbberImage = nil;
1394  if (contents->is_crashed()) {
1395    newState = kTabCrashed;
1396    newHasIcon = true;
1397  } else if (contents->waiting_for_response()) {
1398    newState = kTabWaiting;
1399    throbberImage = throbberWaitingImage;
1400  } else if (contents->is_loading()) {
1401    newState = kTabLoading;
1402    throbberImage = throbberLoadingImage;
1403  }
1404
1405  if (oldState != newState)
1406    [tabController setLoadingState:newState];
1407
1408  // While loading, this function is called repeatedly with the same state.
1409  // To avoid expensive unnecessary view manipulation, only make changes when
1410  // the state is actually changing.  When loading is complete (kTabDone),
1411  // every call to this function is significant.
1412  if (newState == kTabDone || oldState != newState ||
1413      oldHasIcon != newHasIcon) {
1414    NSView* iconView = nil;
1415    if (newHasIcon) {
1416      if (newState == kTabDone) {
1417        iconView = [self iconImageViewForContents:contents];
1418      } else if (newState == kTabCrashed) {
1419        NSImage* oldImage = [[self iconImageViewForContents:contents] image];
1420        NSRect frame =
1421            NSMakeRect(0, 0, kIconWidthAndHeight, kIconWidthAndHeight);
1422        iconView = [ThrobberView toastThrobberViewWithFrame:frame
1423                                                beforeImage:oldImage
1424                                                 afterImage:sadFaviconImage];
1425      } else {
1426        NSRect frame =
1427            NSMakeRect(0, 0, kIconWidthAndHeight, kIconWidthAndHeight);
1428        iconView = [ThrobberView filmstripThrobberViewWithFrame:frame
1429                                                          image:throbberImage];
1430      }
1431    }
1432
1433    [tabController setIconView:iconView];
1434  }
1435}
1436
1437// Called when a notification is received from the model that the given tab
1438// has been updated. |loading| will be YES when we only want to update the
1439// throbber state, not anything else about the (partially) loading tab.
1440- (void)tabChangedWithContents:(TabContentsWrapper*)contents
1441                       atIndex:(NSInteger)modelIndex
1442                    changeType:(TabStripModelObserver::TabChangeType)change {
1443  // Take closing tabs into account.
1444  NSInteger index = [self indexFromModelIndex:modelIndex];
1445
1446  if (modelIndex == tabStripModel_->active_index())
1447    [delegate_ onSelectedTabChange:change];
1448
1449  if (change == TabStripModelObserver::TITLE_NOT_LOADING) {
1450    // TODO(sky): make this work.
1451    // We'll receive another notification of the change asynchronously.
1452    return;
1453  }
1454
1455  TabController* tabController = [tabArray_ objectAtIndex:index];
1456
1457  if (change != TabStripModelObserver::LOADING_ONLY)
1458    [self setTabTitle:tabController withContents:contents->tab_contents()];
1459
1460  [self updateFaviconForContents:contents->tab_contents() atIndex:modelIndex];
1461
1462  TabContentsController* updatedController =
1463      [tabContentsArray_ objectAtIndex:index];
1464  [updatedController tabDidChange:contents->tab_contents()];
1465}
1466
1467// Called when a tab is moved (usually by drag&drop). Keep our parallel arrays
1468// in sync with the tab strip model. It can also be pinned/unpinned
1469// simultaneously, so we need to take care of that.
1470- (void)tabMovedWithContents:(TabContentsWrapper*)contents
1471                   fromIndex:(NSInteger)modelFrom
1472                     toIndex:(NSInteger)modelTo {
1473  // Take closing tabs into account.
1474  NSInteger from = [self indexFromModelIndex:modelFrom];
1475  NSInteger to = [self indexFromModelIndex:modelTo];
1476
1477  scoped_nsobject<TabContentsController> movedTabContentsController(
1478      [[tabContentsArray_ objectAtIndex:from] retain]);
1479  [tabContentsArray_ removeObjectAtIndex:from];
1480  [tabContentsArray_ insertObject:movedTabContentsController.get()
1481                          atIndex:to];
1482  scoped_nsobject<TabController> movedTabController(
1483      [[tabArray_ objectAtIndex:from] retain]);
1484  DCHECK([movedTabController isKindOfClass:[TabController class]]);
1485  [tabArray_ removeObjectAtIndex:from];
1486  [tabArray_ insertObject:movedTabController.get() atIndex:to];
1487
1488  // The tab moved, which means that the mini-tab state may have changed.
1489  if (tabStripModel_->IsMiniTab(modelTo) != [movedTabController mini])
1490    [self tabMiniStateChangedWithContents:contents atIndex:modelTo];
1491
1492  [self layoutTabs];
1493}
1494
1495// Called when a tab is pinned or unpinned without moving.
1496- (void)tabMiniStateChangedWithContents:(TabContentsWrapper*)contents
1497                                atIndex:(NSInteger)modelIndex {
1498  // Take closing tabs into account.
1499  NSInteger index = [self indexFromModelIndex:modelIndex];
1500
1501  TabController* tabController = [tabArray_ objectAtIndex:index];
1502  DCHECK([tabController isKindOfClass:[TabController class]]);
1503
1504  // Don't do anything if the change was already picked up by the move event.
1505  if (tabStripModel_->IsMiniTab(modelIndex) == [tabController mini])
1506    return;
1507
1508  [tabController setMini:tabStripModel_->IsMiniTab(modelIndex)];
1509  [tabController setPinned:tabStripModel_->IsTabPinned(modelIndex)];
1510  [tabController setApp:tabStripModel_->IsAppTab(modelIndex)];
1511  [tabController setUrl:contents->tab_contents()->GetURL()];
1512  [self updateFaviconForContents:contents->tab_contents() atIndex:modelIndex];
1513  // If the tab is being restored and it's pinned, the mini state is set after
1514  // the tab has already been rendered, so re-layout the tabstrip. In all other
1515  // cases, the state is set before the tab is rendered so this isn't needed.
1516  [self layoutTabs];
1517}
1518
1519- (void)setFrameOfSelectedTab:(NSRect)frame {
1520  NSView* view = [self selectedTabView];
1521  NSValue* identifier = [NSValue valueWithPointer:view];
1522  [targetFrames_ setObject:[NSValue valueWithRect:frame]
1523                    forKey:identifier];
1524  [view setFrame:frame];
1525}
1526
1527- (NSView*)selectedTabView {
1528  int selectedIndex = tabStripModel_->active_index();
1529  // Take closing tabs into account. They can't ever be selected.
1530  selectedIndex = [self indexFromModelIndex:selectedIndex];
1531  return [self viewAtIndex:selectedIndex];
1532}
1533
1534// Find the model index based on the x coordinate of the placeholder. If there
1535// is no placeholder, this returns the end of the tab strip. Closing tabs are
1536// not considered in computing the index.
1537- (int)indexOfPlaceholder {
1538  double placeholderX = placeholderFrame_.origin.x;
1539  int index = 0;
1540  int location = 0;
1541  // Use |tabArray_| here instead of the tab strip count in order to get the
1542  // correct index when there are closing tabs to the left of the placeholder.
1543  const int count = [tabArray_ count];
1544  while (index < count) {
1545    // Ignore closing tabs for simplicity. The only drawback of this is that
1546    // if the placeholder is placed right before one or several contiguous
1547    // currently closing tabs, the associated TabController will start at the
1548    // end of the closing tabs.
1549    if ([closingControllers_ containsObject:[tabArray_ objectAtIndex:index]]) {
1550      index++;
1551      continue;
1552    }
1553    NSView* curr = [self viewAtIndex:index];
1554    // The placeholder tab works by changing the frame of the tab being dragged
1555    // to be the bounds of the placeholder, so we need to skip it while we're
1556    // iterating, otherwise we'll end up off by one.  Note This only effects
1557    // dragging to the right, not to the left.
1558    if (curr == placeholderTab_) {
1559      index++;
1560      continue;
1561    }
1562    if (placeholderX <= NSMinX([curr frame]))
1563      break;
1564    index++;
1565    location++;
1566  }
1567  return location;
1568}
1569
1570// Move the given tab at index |from| in this window to the location of the
1571// current placeholder.
1572- (void)moveTabFromIndex:(NSInteger)from {
1573  int toIndex = [self indexOfPlaceholder];
1574  tabStripModel_->MoveTabContentsAt(from, toIndex, true);
1575}
1576
1577// Drop a given TabContents at the location of the current placeholder. If there
1578// is no placeholder, it will go at the end. Used when dragging from another
1579// window when we don't have access to the TabContents as part of our strip.
1580// |frame| is in the coordinate system of the tab strip view and represents
1581// where the user dropped the new tab so it can be animated into its correct
1582// location when the tab is added to the model. If the tab was pinned in its
1583// previous window, setting |pinned| to YES will propagate that state to the
1584// new window. Mini-tabs are either app or pinned tabs; the app state is stored
1585// by the |contents|, but the |pinned| state is the caller's responsibility.
1586- (void)dropTabContents:(TabContentsWrapper*)contents
1587              withFrame:(NSRect)frame
1588            asPinnedTab:(BOOL)pinned {
1589  int modelIndex = [self indexOfPlaceholder];
1590
1591  // Mark that the new tab being created should start at |frame|. It will be
1592  // reset as soon as the tab has been positioned.
1593  droppedTabFrame_ = frame;
1594
1595  // Insert it into this tab strip. We want it in the foreground and to not
1596  // inherit the current tab's group.
1597  tabStripModel_->InsertTabContentsAt(
1598      modelIndex, contents,
1599      TabStripModel::ADD_ACTIVE | (pinned ? TabStripModel::ADD_PINNED : 0));
1600}
1601
1602// Called when the tab strip view changes size. As we only registered for
1603// changes on our view, we know it's only for our view. Layout w/out
1604// animations since they are blocked by the resize nested runloop. We need
1605// the views to adjust immediately. Neither the tabs nor their z-order are
1606// changed, so we don't need to update the subviews.
1607- (void)tabViewFrameChanged:(NSNotification*)info {
1608  [self layoutTabsWithAnimation:NO regenerateSubviews:NO];
1609}
1610
1611// Called when the tracking areas for any given tab are updated. This allows
1612// the individual tabs to update their hover states correctly.
1613// Only generates the event if the cursor is in the tab strip.
1614- (void)tabUpdateTracking:(NSNotification*)notification {
1615  DCHECK([[notification object] isKindOfClass:[TabView class]]);
1616  DCHECK(mouseInside_);
1617  NSWindow* window = [tabStripView_ window];
1618  NSPoint location = [window mouseLocationOutsideOfEventStream];
1619  if (NSPointInRect(location, [tabStripView_ frame])) {
1620    NSEvent* mouseEvent = [NSEvent mouseEventWithType:NSMouseMoved
1621                                             location:location
1622                                        modifierFlags:0
1623                                            timestamp:0
1624                                         windowNumber:[window windowNumber]
1625                                              context:nil
1626                                          eventNumber:0
1627                                           clickCount:0
1628                                             pressure:0];
1629    [self mouseMoved:mouseEvent];
1630  }
1631}
1632
1633- (BOOL)inRapidClosureMode {
1634  return availableResizeWidth_ != kUseFullAvailableWidth;
1635}
1636
1637// Disable tab dragging when there are any pending animations.
1638- (BOOL)tabDraggingAllowed {
1639  return [closingControllers_ count] == 0;
1640}
1641
1642- (void)mouseMoved:(NSEvent*)event {
1643  // Use hit test to figure out what view we are hovering over.
1644  NSView* targetView = [tabStripView_ hitTest:[event locationInWindow]];
1645
1646  // Set the new tab button hover state iff the mouse is over the button.
1647  BOOL shouldShowHoverImage = [targetView isKindOfClass:[NewTabButton class]];
1648  [self setNewTabButtonHoverState:shouldShowHoverImage];
1649
1650  TabView* tabView = (TabView*)targetView;
1651  if (![tabView isKindOfClass:[TabView class]]) {
1652    if ([[tabView superview] isKindOfClass:[TabView class]]) {
1653      tabView = (TabView*)[targetView superview];
1654    } else {
1655      tabView = nil;
1656    }
1657  }
1658
1659  if (hoveredTab_ != tabView) {
1660    [hoveredTab_ mouseExited:nil];  // We don't pass event because moved events
1661    [tabView mouseEntered:nil];  // don't have valid tracking areas
1662    hoveredTab_ = tabView;
1663  } else {
1664    [hoveredTab_ mouseMoved:event];
1665  }
1666}
1667
1668- (void)mouseEntered:(NSEvent*)event {
1669  NSTrackingArea* area = [event trackingArea];
1670  if ([area isEqual:trackingArea_]) {
1671    mouseInside_ = YES;
1672    [self setTabTrackingAreasEnabled:YES];
1673    [self mouseMoved:event];
1674  }
1675}
1676
1677// Called when the tracking area is in effect which means we're tracking to
1678// see if the user leaves the tab strip with their mouse. When they do,
1679// reset layout to use all available width.
1680- (void)mouseExited:(NSEvent*)event {
1681  NSTrackingArea* area = [event trackingArea];
1682  if ([area isEqual:trackingArea_]) {
1683    mouseInside_ = NO;
1684    [self setTabTrackingAreasEnabled:NO];
1685    availableResizeWidth_ = kUseFullAvailableWidth;
1686    [hoveredTab_ mouseExited:event];
1687    hoveredTab_ = nil;
1688    [self layoutTabs];
1689  } else if ([area isEqual:newTabTrackingArea_]) {
1690    // If the mouse is moved quickly enough, it is possible for the mouse to
1691    // leave the tabstrip without sending any mouseMoved: messages at all.
1692    // Since this would result in the new tab button incorrectly staying in the
1693    // hover state, disable the hover image on every mouse exit.
1694    [self setNewTabButtonHoverState:NO];
1695  }
1696}
1697
1698// Enable/Disable the tracking areas for the tabs. They are only enabled
1699// when the mouse is in the tabstrip.
1700- (void)setTabTrackingAreasEnabled:(BOOL)enabled {
1701  NSNotificationCenter* defaultCenter = [NSNotificationCenter defaultCenter];
1702  for (TabController* controller in tabArray_.get()) {
1703    TabView* tabView = [controller tabView];
1704    if (enabled) {
1705      // Set self up to observe tabs so hover states will be correct.
1706      [defaultCenter addObserver:self
1707                        selector:@selector(tabUpdateTracking:)
1708                            name:NSViewDidUpdateTrackingAreasNotification
1709                          object:tabView];
1710    } else {
1711      [defaultCenter removeObserver:self
1712                               name:NSViewDidUpdateTrackingAreasNotification
1713                             object:tabView];
1714    }
1715    [tabView setTrackingEnabled:enabled];
1716  }
1717}
1718
1719// Sets the new tab button's image based on the current hover state.  Does
1720// nothing if the hover state is already correct.
1721- (void)setNewTabButtonHoverState:(BOOL)shouldShowHover {
1722  if (shouldShowHover && !newTabButtonShowingHoverImage_) {
1723    newTabButtonShowingHoverImage_ = YES;
1724    [newTabButton_ setImage:
1725        app::mac::GetCachedImageWithName(kNewTabHoverImage)];
1726  } else if (!shouldShowHover && newTabButtonShowingHoverImage_) {
1727    newTabButtonShowingHoverImage_ = NO;
1728    [newTabButton_ setImage:app::mac::GetCachedImageWithName(kNewTabImage)];
1729  }
1730}
1731
1732// Adds the given subview to (the end of) the list of permanent subviews
1733// (specified from bottom up). These subviews will always be below the
1734// transitory subviews (tabs). |-regenerateSubviewList| must be called to
1735// effectuate the addition.
1736- (void)addSubviewToPermanentList:(NSView*)aView {
1737  if (aView)
1738    [permanentSubviews_ addObject:aView];
1739}
1740
1741// Update the subviews, keeping the permanent ones (or, more correctly, putting
1742// in the ones listed in permanentSubviews_), and putting in the current tabs in
1743// the correct z-order. Any current subviews which is neither in the permanent
1744// list nor a (current) tab will be removed. So if you add such a subview, you
1745// should call |-addSubviewToPermanentList:| (or better yet, call that and then
1746// |-regenerateSubviewList| to actually add it).
1747- (void)regenerateSubviewList {
1748  // Remove self as an observer from all the old tabs before a new set of
1749  // potentially different tabs is put in place.
1750  [self setTabTrackingAreasEnabled:NO];
1751
1752  // Subviews to put in (in bottom-to-top order), beginning with the permanent
1753  // ones.
1754  NSMutableArray* subviews = [NSMutableArray arrayWithArray:permanentSubviews_];
1755
1756  NSView* selectedTabView = nil;
1757  // Go through tabs in reverse order, since |subviews| is bottom-to-top.
1758  for (TabController* tab in [tabArray_ reverseObjectEnumerator]) {
1759    NSView* tabView = [tab view];
1760    if ([tab selected]) {
1761      DCHECK(!selectedTabView);
1762      selectedTabView = tabView;
1763    } else {
1764      [subviews addObject:tabView];
1765    }
1766  }
1767  if (selectedTabView) {
1768    [subviews addObject:selectedTabView];
1769  }
1770  [tabStripView_ setSubviews:subviews];
1771  [self setTabTrackingAreasEnabled:mouseInside_];
1772}
1773
1774// Get the index and disposition for a potential URL(s) drop given a point (in
1775// the |TabStripView|'s coordinates). It considers only the x-coordinate of the
1776// given point. If it's in the "middle" of a tab, it drops on that tab. If it's
1777// to the left, it inserts to the left, and similarly for the right.
1778- (void)droppingURLsAt:(NSPoint)point
1779            givesIndex:(NSInteger*)index
1780           disposition:(WindowOpenDisposition*)disposition {
1781  // Proportion of the tab which is considered the "middle" (and causes things
1782  // to drop on that tab).
1783  const double kMiddleProportion = 0.5;
1784  const double kLRProportion = (1.0 - kMiddleProportion) / 2.0;
1785
1786  DCHECK(index && disposition);
1787  NSInteger i = 0;
1788  for (TabController* tab in tabArray_.get()) {
1789    NSView* view = [tab view];
1790    DCHECK([view isKindOfClass:[TabView class]]);
1791
1792    // Recall that |-[NSView frame]| is in its superview's coordinates, so a
1793    // |TabView|'s frame is in the coordinates of the |TabStripView| (which
1794    // matches the coordinate system of |point|).
1795    NSRect frame = [view frame];
1796
1797    // Modify the frame to make it "unoverlapped".
1798    frame.origin.x += kTabOverlap / 2.0;
1799    frame.size.width -= kTabOverlap;
1800    if (frame.size.width < 1.0)
1801      frame.size.width = 1.0;  // try to avoid complete failure
1802
1803    // Drop in a new tab to the left of tab |i|?
1804    if (point.x < (frame.origin.x + kLRProportion * frame.size.width)) {
1805      *index = i;
1806      *disposition = NEW_FOREGROUND_TAB;
1807      return;
1808    }
1809
1810    // Drop on tab |i|?
1811    if (point.x <= (frame.origin.x +
1812                       (1.0 - kLRProportion) * frame.size.width)) {
1813      *index = i;
1814      *disposition = CURRENT_TAB;
1815      return;
1816    }
1817
1818    // (Dropping in a new tab to the right of tab |i| will be taken care of in
1819    // the next iteration.)
1820    i++;
1821  }
1822
1823  // If we've made it here, we want to append a new tab to the end.
1824  *index = -1;
1825  *disposition = NEW_FOREGROUND_TAB;
1826}
1827
1828- (void)openURL:(GURL*)url inView:(NSView*)view at:(NSPoint)point {
1829  // Get the index and disposition.
1830  NSInteger index;
1831  WindowOpenDisposition disposition;
1832  [self droppingURLsAt:point
1833            givesIndex:&index
1834           disposition:&disposition];
1835
1836  // Either insert a new tab or open in a current tab.
1837  switch (disposition) {
1838    case NEW_FOREGROUND_TAB: {
1839      UserMetrics::RecordAction(UserMetricsAction("Tab_DropURLBetweenTabs"),
1840                                browser_->profile());
1841      browser::NavigateParams params(browser_, *url, PageTransition::TYPED);
1842      params.disposition = disposition;
1843      params.tabstrip_index = index;
1844      params.tabstrip_add_types =
1845          TabStripModel::ADD_ACTIVE | TabStripModel::ADD_FORCE_INDEX;
1846      browser::Navigate(&params);
1847      break;
1848    }
1849    case CURRENT_TAB:
1850      UserMetrics::RecordAction(UserMetricsAction("Tab_DropURLOnTab"),
1851                                browser_->profile());
1852      tabStripModel_->GetTabContentsAt(index)
1853          ->tab_contents()->OpenURL(*url, GURL(), CURRENT_TAB,
1854                                    PageTransition::TYPED);
1855      tabStripModel_->ActivateTabAt(index, true);
1856      break;
1857    default:
1858      NOTIMPLEMENTED();
1859  }
1860}
1861
1862// (URLDropTargetController protocol)
1863- (void)dropURLs:(NSArray*)urls inView:(NSView*)view at:(NSPoint)point {
1864  DCHECK_EQ(view, tabStripView_.get());
1865
1866  if ([urls count] < 1) {
1867    NOTREACHED();
1868    return;
1869  }
1870
1871  //TODO(viettrungluu): dropping multiple URLs.
1872  if ([urls count] > 1)
1873    NOTIMPLEMENTED();
1874
1875  // Get the first URL and fix it up.
1876  GURL url(GURL(URLFixerUpper::FixupURL(
1877      base::SysNSStringToUTF8([urls objectAtIndex:0]), std::string())));
1878
1879  [self openURL:&url inView:view at:point];
1880}
1881
1882// (URLDropTargetController protocol)
1883- (void)dropText:(NSString*)text inView:(NSView*)view at:(NSPoint)point {
1884  DCHECK_EQ(view, tabStripView_.get());
1885
1886  // If the input is plain text, classify the input and make the URL.
1887  AutocompleteMatch match;
1888  browser_->profile()->GetAutocompleteClassifier()->Classify(
1889      base::SysNSStringToUTF16(text), string16(), false, &match, NULL);
1890  GURL url(match.destination_url);
1891
1892  [self openURL:&url inView:view at:point];
1893}
1894
1895// (URLDropTargetController protocol)
1896- (void)indicateDropURLsInView:(NSView*)view at:(NSPoint)point {
1897  DCHECK_EQ(view, tabStripView_.get());
1898
1899  // The minimum y-coordinate at which one should consider place the arrow.
1900  const CGFloat arrowBaseY = 25;
1901
1902  NSInteger index;
1903  WindowOpenDisposition disposition;
1904  [self droppingURLsAt:point
1905            givesIndex:&index
1906           disposition:&disposition];
1907
1908  NSPoint arrowPos = NSMakePoint(0, arrowBaseY);
1909  if (index == -1) {
1910    // Append a tab at the end.
1911    DCHECK(disposition == NEW_FOREGROUND_TAB);
1912    NSInteger lastIndex = [tabArray_ count] - 1;
1913    NSRect overRect = [[[tabArray_ objectAtIndex:lastIndex] view] frame];
1914    arrowPos.x = overRect.origin.x + overRect.size.width - kTabOverlap / 2.0;
1915  } else {
1916    NSRect overRect = [[[tabArray_ objectAtIndex:index] view] frame];
1917    switch (disposition) {
1918      case NEW_FOREGROUND_TAB:
1919        // Insert tab (to the left of the given tab).
1920        arrowPos.x = overRect.origin.x + kTabOverlap / 2.0;
1921        break;
1922      case CURRENT_TAB:
1923        // Overwrite the given tab.
1924        arrowPos.x = overRect.origin.x + overRect.size.width / 2.0;
1925        break;
1926      default:
1927        NOTREACHED();
1928    }
1929  }
1930
1931  [tabStripView_ setDropArrowPosition:arrowPos];
1932  [tabStripView_ setDropArrowShown:YES];
1933  [tabStripView_ setNeedsDisplay:YES];
1934}
1935
1936// (URLDropTargetController protocol)
1937- (void)hideDropURLsIndicatorInView:(NSView*)view {
1938  DCHECK_EQ(view, tabStripView_.get());
1939
1940  if ([tabStripView_ dropArrowShown]) {
1941    [tabStripView_ setDropArrowShown:NO];
1942    [tabStripView_ setNeedsDisplay:YES];
1943  }
1944}
1945
1946- (GTMWindowSheetController*)sheetController {
1947  if (!sheetController_.get())
1948    sheetController_.reset([[GTMWindowSheetController alloc]
1949        initWithWindow:[switchView_ window] delegate:self]);
1950  return sheetController_.get();
1951}
1952
1953- (void)destroySheetController {
1954  // Make sure there are no open sheets.
1955  DCHECK_EQ(0U, [[sheetController_ viewsWithAttachedSheets] count]);
1956  sheetController_.reset();
1957}
1958
1959// TabContentsControllerDelegate protocol.
1960- (void)tabContentsViewFrameWillChange:(TabContentsController*)source
1961                             frameRect:(NSRect)frameRect {
1962  id<TabContentsControllerDelegate> controller =
1963      [[switchView_ window] windowController];
1964  [controller tabContentsViewFrameWillChange:source frameRect:frameRect];
1965}
1966
1967- (TabContentsController*)activeTabContentsController {
1968  int modelIndex = tabStripModel_->active_index();
1969  if (modelIndex < 0)
1970    return nil;
1971  NSInteger index = [self indexFromModelIndex:modelIndex];
1972  if (index < 0 ||
1973      index >= (NSInteger)[tabContentsArray_ count])
1974    return nil;
1975  return [tabContentsArray_ objectAtIndex:index];
1976}
1977
1978- (void)gtm_systemRequestsVisibilityForView:(NSView*)view {
1979  // This implementation is required by GTMWindowSheetController.
1980
1981  // Raise window...
1982  [[switchView_ window] makeKeyAndOrderFront:self];
1983
1984  // ...and raise a tab with a sheet.
1985  NSInteger index = [self modelIndexForContentsView:view];
1986  DCHECK(index >= 0);
1987  if (index >= 0)
1988    tabStripModel_->ActivateTabAt(index, false /* not a user gesture */);
1989}
1990
1991- (void)attachConstrainedWindow:(ConstrainedWindowMac*)window {
1992  // TODO(thakis, avi): Figure out how to make this work when tabs are dragged
1993  // out or if fullscreen mode is toggled.
1994
1995  // View hierarchy of the contents view:
1996  // NSView  -- switchView, same for all tabs
1997  // +- NSView  -- TabContentsController's view
1998  //    +- TabContentsViewCocoa
1999  // Changing it? Do not forget to modify removeConstrainedWindow too.
2000  // We use the TabContentsController's view in |swapInTabAtIndex|, so we have
2001  // to pass it to the sheet controller here.
2002  NSView* tabContentsView = [window->owner()->GetNativeView() superview];
2003  window->delegate()->RunSheet([self sheetController], tabContentsView);
2004
2005  // TODO(avi, thakis): GTMWindowSheetController has no api to move tabsheets
2006  // between windows. Until then, we have to prevent having to move a tabsheet
2007  // between windows, e.g. no tearing off of tabs.
2008  NSInteger modelIndex = [self modelIndexForContentsView:tabContentsView];
2009  NSInteger index = [self indexFromModelIndex:modelIndex];
2010  BrowserWindowController* controller =
2011      (BrowserWindowController*)[[switchView_ window] windowController];
2012  DCHECK(controller != nil);
2013  DCHECK(index >= 0);
2014  if (index >= 0) {
2015    [controller setTab:[self viewAtIndex:index] isDraggable:NO];
2016  }
2017}
2018
2019- (void)removeConstrainedWindow:(ConstrainedWindowMac*)window {
2020  NSView* tabContentsView = [window->owner()->GetNativeView() superview];
2021
2022  // TODO(avi, thakis): GTMWindowSheetController has no api to move tabsheets
2023  // between windows. Until then, we have to prevent having to move a tabsheet
2024  // between windows, e.g. no tearing off of tabs.
2025  NSInteger modelIndex = [self modelIndexForContentsView:tabContentsView];
2026  NSInteger index = [self indexFromModelIndex:modelIndex];
2027  BrowserWindowController* controller =
2028      (BrowserWindowController*)[[switchView_ window] windowController];
2029  DCHECK(index >= 0);
2030  if (index >= 0) {
2031    [controller setTab:[self viewAtIndex:index] isDraggable:YES];
2032  }
2033}
2034
2035- (BOOL)shouldShowProfileMenuButton {
2036  if (!CommandLine::ForCurrentProcess()->HasSwitch(switches::kMultiProfiles))
2037    return NO;
2038  if (browser_->profile()->IsOffTheRecord())
2039    return NO;
2040  return (!browser_->profile()->GetPrefs()->GetString(
2041        prefs::kGoogleServicesUsername).empty());
2042}
2043
2044- (void)updateProfileMenuButton {
2045  if (![self shouldShowProfileMenuButton]) {
2046    [profileMenuButton_ setHidden:YES];
2047    return;
2048  }
2049
2050  std::string profileName = browser_->profile()->GetPrefs()->GetString(
2051      prefs::kGoogleServicesUsername);
2052  [profileMenuButton_ setProfileDisplayName:
2053      [NSString stringWithUTF8String:profileName.c_str()]];
2054  [profileMenuButton_ setHidden:NO];
2055
2056  NSMenu* menu = [profileMenuButton_ menu];
2057  while ([menu numberOfItems] > 0) {
2058    [menu removeItemAtIndex:0];
2059  }
2060
2061  NSString* menuTitle =
2062      l10n_util::GetNSStringWithFixup(IDS_PROFILES_CREATE_NEW_PROFILE_OPTION);
2063  [menu addItemWithTitle:menuTitle
2064                  action:NULL
2065           keyEquivalent:@""];
2066}
2067
2068@end
2069