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];
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 [self setWantsLayer:YES];
48 // Draw bottom border bitmap. Each tab is responsible for mimicking this bottom
49 // border, unless it's the selected tab.
50 - (void)drawBorder:(NSRect)dirtyRect {
51 ThemeService* themeProvider =
52 static_cast<ThemeService*>([[self window] themeProvider]);
56 // First draw the toolbar bitmap, so that theme colors can shine through.
57 CGFloat backgroundHeight = 2 * [self cr_lineWidth];
58 if (NSMinY(dirtyRect) < backgroundHeight) {
59 gfx::ScopedNSGraphicsContextSaveGState scopedGState;
60 NSGraphicsContext *context = [NSGraphicsContext currentContext];
61 NSPoint position = [[self window] themeImagePositionForAlignment:
62 THEME_IMAGE_ALIGN_WITH_TAB_STRIP];
63 [context cr_setPatternPhase:position forView:self];
65 // Themes don't have an inactive image so only look for one if there's no
67 bool active = [[self window] isKeyWindow] || [[self window] isMainWindow] ||
68 !themeProvider->UsingDefaultTheme();
69 int resource_id = active ? IDR_THEME_TOOLBAR : IDR_THEME_TOOLBAR_INACTIVE;
70 [themeProvider->GetNSImageColorNamed(resource_id) set];
72 NSMakeRect(NSMinX(dirtyRect), 0, NSWidth(dirtyRect), backgroundHeight));
75 // Draw the border bitmap, which is partially transparent.
76 NSImage* image = themeProvider->GetNSImageNamed(IDR_TOOLBAR_SHADE_TOP);
77 if (NSMinY(dirtyRect) >= [image size].height)
80 NSRect borderRect = dirtyRect;
81 borderRect.size.height = [image size].height;
82 borderRect.origin.y = 0;
84 BOOL focused = [[self window] isKeyWindow] || [[self window] isMainWindow];
85 NSDrawThreePartImage(borderRect, nil, image, nil, /*vertical=*/ NO,
86 NSCompositeSourceOver,
87 focused ? 1.0 : tabs::kImageNoFocusAlpha,
91 - (void)drawRect:(NSRect)rect {
92 NSRect boundsRect = [self bounds];
94 [self drawBorder:boundsRect];
96 // Draw drop-indicator arrow (if appropriate).
97 // TODO(viettrungluu): this is all a stop-gap measure.
98 if ([self dropArrowShown]) {
99 // Programmer art: an arrow parametrized by many knobs. Note that the arrow
100 // points downwards (so understand "width" and "height" accordingly).
102 // How many (pixels) to inset on the top/bottom.
103 const CGFloat kArrowTopInset = 1.5;
104 const CGFloat kArrowBottomInset = 1;
106 // What proportion of the vertical space is dedicated to the arrow tip,
107 // i.e., (arrow tip height)/(amount of vertical space).
108 const CGFloat kArrowTipProportion = 0.55;
110 // This is a slope, i.e., (arrow tip height)/(0.5 * arrow tip width).
111 const CGFloat kArrowTipSlope = 1.2;
113 // What proportion of the arrow tip width is the stem, i.e., (stem
114 // width)/(arrow tip width).
115 const CGFloat kArrowStemProportion = 0.33;
117 NSPoint arrowTipPos = [self dropArrowPosition];
118 arrowTipPos.x = std::floor(arrowTipPos.x); // Draw on the pixel.
119 arrowTipPos.y += kArrowBottomInset; // Inset on the bottom.
121 // Height we have to work with (insetting on the top).
122 CGFloat availableHeight =
123 NSMaxY(boundsRect) - arrowTipPos.y - kArrowTopInset;
124 DCHECK(availableHeight >= 5);
126 // Based on the knobs above, calculate actual dimensions which we'll need
128 CGFloat arrowTipHeight = kArrowTipProportion * availableHeight;
129 CGFloat arrowTipWidth = 2 * arrowTipHeight / kArrowTipSlope;
130 CGFloat arrowStemHeight = availableHeight - arrowTipHeight;
131 CGFloat arrowStemWidth = kArrowStemProportion * arrowTipWidth;
132 CGFloat arrowStemInset = (arrowTipWidth - arrowStemWidth) / 2;
134 // The line width is arbitrary, but our path really should be mitered.
135 NSBezierPath* arrow = [NSBezierPath bezierPath];
136 [arrow setLineJoinStyle:NSMiterLineJoinStyle];
137 [arrow setLineWidth:1];
139 // Define the arrow's shape! We start from the tip and go clockwise.
140 [arrow moveToPoint:arrowTipPos];
141 [arrow relativeLineToPoint:NSMakePoint(-arrowTipWidth / 2, arrowTipHeight)];
142 [arrow relativeLineToPoint:NSMakePoint(arrowStemInset, 0)];
143 [arrow relativeLineToPoint:NSMakePoint(0, arrowStemHeight)];
144 [arrow relativeLineToPoint:NSMakePoint(arrowStemWidth, 0)];
145 [arrow relativeLineToPoint:NSMakePoint(0, -arrowStemHeight)];
146 [arrow relativeLineToPoint:NSMakePoint(arrowStemInset, 0)];
149 // Draw and fill the arrow.
150 [[NSColor colorWithCalibratedWhite:0 alpha:0.67] set];
152 [[NSColor colorWithCalibratedWhite:1 alpha:0.67] setFill];
157 // YES if a double-click in the background of the tab strip minimizes the
159 - (BOOL)doubleClickMinimizesWindow {
163 // We accept first mouse so clicks onto close/zoom/miniaturize buttons and
164 // title bar double-clicks are properly detected even when the window is in the
166 - (BOOL)acceptsFirstMouse:(NSEvent*)event {
170 // Trap double-clicks and make them miniaturize the browser window.
171 - (void)mouseUp:(NSEvent*)event {
172 // Bail early if double-clicks are disabled.
173 if (![self doubleClickMinimizesWindow]) {
174 [super mouseUp:event];
178 NSInteger clickCount = [event clickCount];
179 NSTimeInterval timestamp = [event timestamp];
181 // Double-clicks on Zoom/Close/Mininiaturize buttons shouldn't cause
182 // miniaturization. For those, we miss the first click but get the second
183 // (with clickCount == 2!). We thus check that we got a first click shortly
184 // before (measured up-to-up) a double-click. Cocoa doesn't have a documented
185 // way of getting the proper interval (= (double-click-threshold) +
186 // (drag-threshold); the former is Carbon GetDblTime()/60.0 or
187 // com.apple.mouse.doubleClickThreshold [undocumented]). So we hard-code
188 // "short" as 0.8 seconds. (Measuring up-to-up isn't enough to properly
189 // detect double-clicks, but we're actually using Cocoa for that.)
190 if (clickCount == 2 && (timestamp - lastMouseUp_) < 0.8) {
191 if (base::mac::ShouldWindowsMiniaturizeOnDoubleClick())
192 [[self window] performMiniaturize:self];
194 [super mouseUp:event];
197 // If clickCount is 0, the drag threshold was passed.
198 lastMouseUp_ = (clickCount == 1) ? timestamp : -1000.0;
201 // (URLDropTarget protocol)
202 - (id<URLDropTargetController>)urlDropController {
206 // (URLDropTarget protocol)
207 - (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender {
208 return [dropHandler_ draggingEntered:sender];
211 // (URLDropTarget protocol)
212 - (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)sender {
213 return [dropHandler_ draggingUpdated:sender];
216 // (URLDropTarget protocol)
217 - (void)draggingExited:(id<NSDraggingInfo>)sender {
218 return [dropHandler_ draggingExited:sender];
221 // (URLDropTarget protocol)
222 - (BOOL)performDragOperation:(id<NSDraggingInfo>)sender {
223 return [dropHandler_ performDragOperation:sender];
226 - (BOOL)accessibilityIsIgnored {
230 - (id)accessibilityAttributeValue:(NSString*)attribute {
231 if ([attribute isEqual:NSAccessibilityRoleAttribute])
232 return NSAccessibilityTabGroupRole;
233 if ([attribute isEqual:NSAccessibilityTabsAttribute]) {
234 NSMutableArray* tabs = [[[NSMutableArray alloc] init] autorelease];
236 [self accessibilityAttributeValue:NSAccessibilityChildrenAttribute];
237 for (id child in children) {
238 if ([[child accessibilityAttributeValue:NSAccessibilityRoleAttribute]
239 isEqual:NSAccessibilityRadioButtonRole]) {
240 [tabs addObject:child];
245 if ([attribute isEqual:NSAccessibilityContentsAttribute])
246 return [self accessibilityAttributeValue:NSAccessibilityChildrenAttribute];
247 if ([attribute isEqual:NSAccessibilityValueAttribute])
248 return [controller_ activeTabView];
250 return [super accessibilityAttributeValue:attribute];
253 - (NSArray*)accessibilityAttributeNames {
254 NSMutableArray* attributes =
255 [[super accessibilityAttributeNames] mutableCopy];
256 [attributes addObject:NSAccessibilityTabsAttribute];
257 [attributes addObject:NSAccessibilityContentsAttribute];
258 [attributes addObject:NSAccessibilityValueAttribute];
260 return [attributes autorelease];
264 return VIEW_ID_TAB_STRIP;
267 - (NewTabButton*)getNewTabButton {
268 return newTabButton_;
271 - (void)setNewTabButton:(NewTabButton*)button {
272 newTabButton_.reset([button retain]);
275 - (void)setController:(TabStripController*)controller {
276 controller_ = controller;