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