Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / cocoa / tabs / tab_strip_view.mm
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.
4
5 #import "chrome/browser/ui/cocoa/tabs/tab_strip_view.h"
6
7 #include <cmath>  // floor
8
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/tabs/tab_strip_controller.h"
15 #import "chrome/browser/ui/cocoa/tabs/tab_view.h"
16 #import "chrome/browser/ui/cocoa/view_id_util.h"
17 #include "chrome/grit/generated_resources.h"
18 #include "grit/theme_resources.h"
19 #import "ui/base/cocoa/nsgraphics_context_additions.h"
20 #import "ui/base/cocoa/nsview_additions.h"
21 #include "ui/base/l10n/l10n_util_mac.h"
22 #include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h"
23
24 @implementation TabStripView
25
26 @synthesize dropArrowShown = dropArrowShown_;
27 @synthesize dropArrowPosition = dropArrowPosition_;
28
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)];
35
36     // Set lastMouseUp_ = -1000.0 so that timestamp-lastMouseUp_ is big unless
37     // lastMouseUp_ has been reset.
38     lastMouseUp_ = -1000.0;
39
40     // Register to be an URL drop target.
41     dropHandler_.reset([[URLDropTargetHandler alloc] initWithView:self]);
42
43     [self setWantsLayer:YES];
44   }
45   return self;
46 }
47
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]);
53   if (!themeProvider)
54     return;
55
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];
64
65     // Themes don't have an inactive image so only look for one if there's no
66     // theme.
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];
71     NSRectFill(
72         NSMakeRect(NSMinX(dirtyRect), 0, NSWidth(dirtyRect), backgroundHeight));
73   }
74
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)
78     return;
79
80   NSRect borderRect = dirtyRect;
81   borderRect.size.height = [image size].height;
82   borderRect.origin.y = 0;
83
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,
88                        /*flipped=*/ NO);
89 }
90
91 - (void)drawRect:(NSRect)rect {
92   NSRect boundsRect = [self bounds];
93
94   [self drawBorder:boundsRect];
95
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).
101
102     // How many (pixels) to inset on the top/bottom.
103     const CGFloat kArrowTopInset = 1.5;
104     const CGFloat kArrowBottomInset = 1;
105
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;
109
110     // This is a slope, i.e., (arrow tip height)/(0.5 * arrow tip width).
111     const CGFloat kArrowTipSlope = 1.2;
112
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;
116
117     NSPoint arrowTipPos = [self dropArrowPosition];
118     arrowTipPos.x = std::floor(arrowTipPos.x);  // Draw on the pixel.
119     arrowTipPos.y += kArrowBottomInset;  // Inset on the bottom.
120
121     // Height we have to work with (insetting on the top).
122     CGFloat availableHeight =
123         NSMaxY(boundsRect) - arrowTipPos.y - kArrowTopInset;
124     DCHECK(availableHeight >= 5);
125
126     // Based on the knobs above, calculate actual dimensions which we'll need
127     // for drawing.
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;
133
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];
138
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)];
147     [arrow closePath];
148
149     // Draw and fill the arrow.
150     [[NSColor colorWithCalibratedWhite:0 alpha:0.67] set];
151     [arrow stroke];
152     [[NSColor colorWithCalibratedWhite:1 alpha:0.67] setFill];
153     [arrow fill];
154   }
155 }
156
157 // YES if a double-click in the background of the tab strip minimizes the
158 // window.
159 - (BOOL)doubleClickMinimizesWindow {
160   return YES;
161 }
162
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
165 // background.
166 - (BOOL)acceptsFirstMouse:(NSEvent*)event {
167   return YES;
168 }
169
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];
175     return;
176   }
177
178   NSInteger clickCount = [event clickCount];
179   NSTimeInterval timestamp = [event timestamp];
180
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];
193   } else {
194     [super mouseUp:event];
195   }
196
197   // If clickCount is 0, the drag threshold was passed.
198   lastMouseUp_ = (clickCount == 1) ? timestamp : -1000.0;
199 }
200
201 // (URLDropTarget protocol)
202 - (id<URLDropTargetController>)urlDropController {
203   return controller_;
204 }
205
206 // (URLDropTarget protocol)
207 - (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender {
208   return [dropHandler_ draggingEntered:sender];
209 }
210
211 // (URLDropTarget protocol)
212 - (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)sender {
213   return [dropHandler_ draggingUpdated:sender];
214 }
215
216 // (URLDropTarget protocol)
217 - (void)draggingExited:(id<NSDraggingInfo>)sender {
218   return [dropHandler_ draggingExited:sender];
219 }
220
221 // (URLDropTarget protocol)
222 - (BOOL)performDragOperation:(id<NSDraggingInfo>)sender {
223   return [dropHandler_ performDragOperation:sender];
224 }
225
226 - (BOOL)accessibilityIsIgnored {
227   return NO;
228 }
229
230 // Returns AX children (tabs and new tab button), sorted from left to right.
231 - (NSArray*)accessibilityChildren {
232   NSArray* children =
233       [super accessibilityAttributeValue:NSAccessibilityChildrenAttribute];
234   return [children sortedArrayUsingComparator:
235       ^NSComparisonResult(id first, id second) {
236           NSPoint firstPosition =
237               [[first accessibilityAttributeValue:
238                           NSAccessibilityPositionAttribute] pointValue];
239           NSPoint secondPosition =
240               [[second accessibilityAttributeValue:
241                            NSAccessibilityPositionAttribute] pointValue];
242           if (firstPosition.x < secondPosition.x)
243             return NSOrderedAscending;
244           else if (firstPosition.x > secondPosition.x)
245             return NSOrderedDescending;
246           else
247             return NSOrderedSame;
248       }];
249 }
250
251 - (id)accessibilityAttributeValue:(NSString*)attribute {
252   if ([attribute isEqual:NSAccessibilityRoleAttribute]) {
253     return NSAccessibilityTabGroupRole;
254   } else if ([attribute isEqual:NSAccessibilityChildrenAttribute]) {
255     return [self accessibilityChildren];
256   } else if ([attribute isEqual:NSAccessibilityTabsAttribute]) {
257     NSArray* children = [self accessibilityChildren];
258     NSIndexSet* indexes = [children indexesOfObjectsPassingTest:
259         ^BOOL(id child, NSUInteger idx, BOOL* stop) {
260             NSString* role = [child
261                 accessibilityAttributeValue:NSAccessibilityRoleAttribute];
262             return [role isEqualToString:NSAccessibilityRadioButtonRole];
263         }];
264     return [children objectsAtIndexes:indexes];
265   } else if ([attribute isEqual:NSAccessibilityContentsAttribute]) {
266     return [self accessibilityChildren];
267   } else if ([attribute isEqual:NSAccessibilityValueAttribute]) {
268     return [controller_ activeTabView];
269   }
270
271   return [super accessibilityAttributeValue:attribute];
272 }
273
274 - (NSArray*)accessibilityAttributeNames {
275   NSMutableArray* attributes =
276       [[super accessibilityAttributeNames] mutableCopy];
277   [attributes addObject:NSAccessibilityTabsAttribute];
278   [attributes addObject:NSAccessibilityContentsAttribute];
279   [attributes addObject:NSAccessibilityValueAttribute];
280
281   return [attributes autorelease];
282 }
283
284 - (ViewID)viewID {
285   return VIEW_ID_TAB_STRIP;
286 }
287
288 - (NewTabButton*)getNewTabButton {
289   return newTabButton_;
290 }
291
292 - (void)setNewTabButton:(NewTabButton*)button {
293   newTabButton_.reset([button retain]);
294 }
295
296 - (void)setController:(TabStripController*)controller {
297   controller_ = controller;
298 }
299
300 @end