NaCl: Update revision in DEPS, r12770 -> r12773
[chromium-blink-merge.git] / chrome / browser / ui / cocoa / tabs / tab_strip_view.mm
blob28eeb7b97ba222bf73939b1308fbc8034f49b7ea
1 // Copyright (c) 2012 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.
5 #import "chrome/browser/ui/cocoa/tabs/tab_strip_view.h"
7 #include <cmath>  // floor
9 #include "base/logging.h"
10 #include "base/mac/mac_util.h"
11 #include "chrome/browser/themes/theme_service.h"
12 #import "chrome/browser/ui/cocoa/browser_window_controller.h"
13 #import "chrome/browser/ui/cocoa/new_tab_button.h"
14 #import "chrome/browser/ui/cocoa/nsview_additions.h"
15 #import "chrome/browser/ui/cocoa/tabs/tab_strip_controller.h"
16 #import "chrome/browser/ui/cocoa/tabs/tab_view.h"
17 #import "chrome/browser/ui/cocoa/view_id_util.h"
18 #include "grit/generated_resources.h"
19 #include "grit/theme_resources.h"
20 #import "ui/base/cocoa/nsgraphics_context_additions.h"
21 #include "ui/base/l10n/l10n_util_mac.h"
22 #include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h"
24 @implementation TabStripView
26 @synthesize dropArrowShown = dropArrowShown_;
27 @synthesize dropArrowPosition = dropArrowPosition_;
29 - (id)initWithFrame:(NSRect)frame {
30   self = [super initWithFrame:frame];
31   if (self) {
32     newTabButton_.reset([[NewTabButton alloc] initWithFrame:
33         NSMakeRect(295, 0, 40, 27)]);
34     [newTabButton_ setToolTip:l10n_util::GetNSString(IDS_TOOLTIP_NEW_TAB)];
36     // Set lastMouseUp_ = -1000.0 so that timestamp-lastMouseUp_ is big unless
37     // lastMouseUp_ has been reset.
38     lastMouseUp_ = -1000.0;
40     // Register to be an URL drop target.
41     dropHandler_.reset([[URLDropTargetHandler alloc] initWithView:self]);
43     // Give this view its own layer, and squash child layers into this layer
44     // so that the text in the infobar have sub-pixel anti-aliasing.
45     [self cr_setWantsLayer:YES withSquashing:YES];
46   }
47   return self;
50 // Draw bottom border bitmap. Each tab is responsible for mimicking this bottom
51 // border, unless it's the selected tab.
52 - (void)drawBorder:(NSRect)dirtyRect {
53   ThemeService* themeProvider =
54       static_cast<ThemeService*>([[self window] themeProvider]);
55   if (!themeProvider)
56     return;
58   // First draw the toolbar bitmap, so that theme colors can shine through.
59   CGFloat backgroundHeight = 2 * [self cr_lineWidth];
60   if (NSMinY(dirtyRect) < backgroundHeight) {
61     gfx::ScopedNSGraphicsContextSaveGState scopedGState;
62     NSGraphicsContext *context = [NSGraphicsContext currentContext];
63     NSPoint position = [[self window] themeImagePositionForAlignment:
64         THEME_IMAGE_ALIGN_WITH_TAB_STRIP];
65     [context cr_setPatternPhase:position forView:self];
67     // Themes don't have an inactive image so only look for one if there's no
68     // theme.
69     bool active = [[self window] isKeyWindow] || [[self window] isMainWindow] ||
70                   !themeProvider->UsingDefaultTheme();
71     int resource_id = active ? IDR_THEME_TOOLBAR : IDR_THEME_TOOLBAR_INACTIVE;
72     [themeProvider->GetNSImageColorNamed(resource_id) set];
73     NSRectFill(
74         NSMakeRect(NSMinX(dirtyRect), 0, NSWidth(dirtyRect), backgroundHeight));
75   }
77   // Draw the border bitmap, which is partially transparent.
78   NSImage* image = themeProvider->GetNSImageNamed(IDR_TOOLBAR_SHADE_TOP);
79   if (NSMinY(dirtyRect) >= [image size].height)
80     return;
82   NSRect borderRect = dirtyRect;
83   borderRect.size.height = [image size].height;
84   borderRect.origin.y = 0;
86   BOOL focused = [[self window] isKeyWindow] || [[self window] isMainWindow];
87   NSDrawThreePartImage(borderRect, nil, image, nil, /*vertical=*/ NO,
88                        NSCompositeSourceOver,
89                        focused ?  1.0 : tabs::kImageNoFocusAlpha,
90                        /*flipped=*/ NO);
93 - (void)drawRect:(NSRect)rect {
94   NSRect boundsRect = [self bounds];
96   [self drawBorder:boundsRect];
98   // Draw drop-indicator arrow (if appropriate).
99   // TODO(viettrungluu): this is all a stop-gap measure.
100   if ([self dropArrowShown]) {
101     // Programmer art: an arrow parametrized by many knobs. Note that the arrow
102     // points downwards (so understand "width" and "height" accordingly).
104     // How many (pixels) to inset on the top/bottom.
105     const CGFloat kArrowTopInset = 1.5;
106     const CGFloat kArrowBottomInset = 1;
108     // What proportion of the vertical space is dedicated to the arrow tip,
109     // i.e., (arrow tip height)/(amount of vertical space).
110     const CGFloat kArrowTipProportion = 0.55;
112     // This is a slope, i.e., (arrow tip height)/(0.5 * arrow tip width).
113     const CGFloat kArrowTipSlope = 1.2;
115     // What proportion of the arrow tip width is the stem, i.e., (stem
116     // width)/(arrow tip width).
117     const CGFloat kArrowStemProportion = 0.33;
119     NSPoint arrowTipPos = [self dropArrowPosition];
120     arrowTipPos.x = std::floor(arrowTipPos.x);  // Draw on the pixel.
121     arrowTipPos.y += kArrowBottomInset;  // Inset on the bottom.
123     // Height we have to work with (insetting on the top).
124     CGFloat availableHeight =
125         NSMaxY(boundsRect) - arrowTipPos.y - kArrowTopInset;
126     DCHECK(availableHeight >= 5);
128     // Based on the knobs above, calculate actual dimensions which we'll need
129     // for drawing.
130     CGFloat arrowTipHeight = kArrowTipProportion * availableHeight;
131     CGFloat arrowTipWidth = 2 * arrowTipHeight / kArrowTipSlope;
132     CGFloat arrowStemHeight = availableHeight - arrowTipHeight;
133     CGFloat arrowStemWidth = kArrowStemProportion * arrowTipWidth;
134     CGFloat arrowStemInset = (arrowTipWidth - arrowStemWidth) / 2;
136     // The line width is arbitrary, but our path really should be mitered.
137     NSBezierPath* arrow = [NSBezierPath bezierPath];
138     [arrow setLineJoinStyle:NSMiterLineJoinStyle];
139     [arrow setLineWidth:1];
141     // Define the arrow's shape! We start from the tip and go clockwise.
142     [arrow moveToPoint:arrowTipPos];
143     [arrow relativeLineToPoint:NSMakePoint(-arrowTipWidth / 2, arrowTipHeight)];
144     [arrow relativeLineToPoint:NSMakePoint(arrowStemInset, 0)];
145     [arrow relativeLineToPoint:NSMakePoint(0, arrowStemHeight)];
146     [arrow relativeLineToPoint:NSMakePoint(arrowStemWidth, 0)];
147     [arrow relativeLineToPoint:NSMakePoint(0, -arrowStemHeight)];
148     [arrow relativeLineToPoint:NSMakePoint(arrowStemInset, 0)];
149     [arrow closePath];
151     // Draw and fill the arrow.
152     [[NSColor colorWithCalibratedWhite:0 alpha:0.67] set];
153     [arrow stroke];
154     [[NSColor colorWithCalibratedWhite:1 alpha:0.67] setFill];
155     [arrow fill];
156   }
159 // YES if a double-click in the background of the tab strip minimizes the
160 // window.
161 - (BOOL)doubleClickMinimizesWindow {
162   return YES;
165 // We accept first mouse so clicks onto close/zoom/miniaturize buttons and
166 // title bar double-clicks are properly detected even when the window is in the
167 // background.
168 - (BOOL)acceptsFirstMouse:(NSEvent*)event {
169   return YES;
172 // Trap double-clicks and make them miniaturize the browser window.
173 - (void)mouseUp:(NSEvent*)event {
174   // Bail early if double-clicks are disabled.
175   if (![self doubleClickMinimizesWindow]) {
176     [super mouseUp:event];
177     return;
178   }
180   NSInteger clickCount = [event clickCount];
181   NSTimeInterval timestamp = [event timestamp];
183   // Double-clicks on Zoom/Close/Mininiaturize buttons shouldn't cause
184   // miniaturization. For those, we miss the first click but get the second
185   // (with clickCount == 2!). We thus check that we got a first click shortly
186   // before (measured up-to-up) a double-click. Cocoa doesn't have a documented
187   // way of getting the proper interval (= (double-click-threshold) +
188   // (drag-threshold); the former is Carbon GetDblTime()/60.0 or
189   // com.apple.mouse.doubleClickThreshold [undocumented]). So we hard-code
190   // "short" as 0.8 seconds. (Measuring up-to-up isn't enough to properly
191   // detect double-clicks, but we're actually using Cocoa for that.)
192   if (clickCount == 2 && (timestamp - lastMouseUp_) < 0.8) {
193     if (base::mac::ShouldWindowsMiniaturizeOnDoubleClick())
194       [[self window] performMiniaturize:self];
195   } else {
196     [super mouseUp:event];
197   }
199   // If clickCount is 0, the drag threshold was passed.
200   lastMouseUp_ = (clickCount == 1) ? timestamp : -1000.0;
203 // (URLDropTarget protocol)
204 - (id<URLDropTargetController>)urlDropController {
205   return controller_;
208 // (URLDropTarget protocol)
209 - (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender {
210   return [dropHandler_ draggingEntered:sender];
213 // (URLDropTarget protocol)
214 - (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)sender {
215   return [dropHandler_ draggingUpdated:sender];
218 // (URLDropTarget protocol)
219 - (void)draggingExited:(id<NSDraggingInfo>)sender {
220   return [dropHandler_ draggingExited:sender];
223 // (URLDropTarget protocol)
224 - (BOOL)performDragOperation:(id<NSDraggingInfo>)sender {
225   return [dropHandler_ performDragOperation:sender];
228 - (BOOL)accessibilityIsIgnored {
229   return NO;
232 - (id)accessibilityAttributeValue:(NSString*)attribute {
233   if ([attribute isEqual:NSAccessibilityRoleAttribute])
234     return NSAccessibilityTabGroupRole;
235   if ([attribute isEqual:NSAccessibilityTabsAttribute]) {
236     NSMutableArray* tabs = [[[NSMutableArray alloc] init] autorelease];
237     NSArray* children =
238         [self accessibilityAttributeValue:NSAccessibilityChildrenAttribute];
239     for (id child in children) {
240       if ([[child accessibilityAttributeValue:NSAccessibilityRoleAttribute]
241           isEqual:NSAccessibilityRadioButtonRole]) {
242         [tabs addObject:child];
243       }
244     }
245     return tabs;
246   }
247   if ([attribute isEqual:NSAccessibilityContentsAttribute])
248     return [self accessibilityAttributeValue:NSAccessibilityChildrenAttribute];
249   if ([attribute isEqual:NSAccessibilityValueAttribute])
250     return [controller_ activeTabView];
252   return [super accessibilityAttributeValue:attribute];
255 - (NSArray*)accessibilityAttributeNames {
256   NSMutableArray* attributes =
257       [[super accessibilityAttributeNames] mutableCopy];
258   [attributes addObject:NSAccessibilityTabsAttribute];
259   [attributes addObject:NSAccessibilityContentsAttribute];
260   [attributes addObject:NSAccessibilityValueAttribute];
262   return [attributes autorelease];
265 - (ViewID)viewID {
266   return VIEW_ID_TAB_STRIP;
269 - (NewTabButton*)getNewTabButton {
270   return newTabButton_;
273 - (void)setNewTabButton:(NewTabButton*)button {
274   newTabButton_.reset([button retain]);
277 - (void)setController:(TabStripController*)controller {
278   controller_ = controller;
281 @end