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#include "ui/base/cocoa/base_view.h"
6
7#include "base/mac/mac_util.h"
8
9NSString* kViewDidBecomeFirstResponder =
10    @"Chromium.kViewDidBecomeFirstResponder";
11NSString* kSelectionDirection = @"Chromium.kSelectionDirection";
12
13@implementation BaseView
14
15- (instancetype)initWithFrame:(NSRect)frame {
16  if ((self = [super initWithFrame:frame])) {
17    [self enableTracking];
18  }
19  return self;
20}
21
22- (instancetype)initWithCoder:(NSCoder*)decoder {
23  if ((self = [super initWithCoder:decoder])) {
24    [self enableTracking];
25  }
26  return self;
27}
28
29- (void)dealloc {
30  [self disableTracking];
31  [super dealloc];
32}
33
34- (void)enableTracking {
35  if (_trackingArea.get())
36    return;
37
38  NSTrackingAreaOptions trackingOptions = NSTrackingMouseEnteredAndExited |
39                                          NSTrackingMouseMoved |
40                                          NSTrackingActiveAlways |
41                                          NSTrackingInVisibleRect;
42  _trackingArea.reset([[CrTrackingArea alloc] initWithRect:NSZeroRect
43                                                   options:trackingOptions
44                                                     owner:self
45                                                  userInfo:nil]);
46  [self addTrackingArea:_trackingArea.get()];
47}
48
49- (void)disableTracking {
50  if (_trackingArea.get()) {
51    [self removeTrackingArea:_trackingArea.get()];
52    _trackingArea.reset();
53  }
54}
55
56- (void)updateTrackingAreas {
57  [super updateTrackingAreas];
58
59  // NSTrackingInVisibleRect doesn't work correctly with Lion's window
60  // resizing (See https://crbug.com/176725 and
61  // http://openradar.appspot.com/radar?id=2773401). It also doesn't work
62  // correctly when the window enters fullscreen
63  // (See https://crbug.com/170058).
64  //
65  // Work around it by reinstalling the tracking area after the window resizes
66  // or enters fullscreen. This AppKit bug is fixed on High Sierra, so we only
67  // apply this workaround on 10.12 or earlier.
68  if (base::mac::IsAtMostOS10_12()) {
69    [self disableTracking];
70    [self enableTracking];
71  }
72}
73
74- (void)handleLeftMouseUp:(NSEvent*)theEvent {
75  DCHECK_EQ([theEvent type], NSLeftMouseUp);
76  _dragging = NO;
77  if (!_pendingExitEvent)
78    return;
79
80  NSEvent* exitEvent =
81      [NSEvent enterExitEventWithType:NSMouseExited
82                             location:[theEvent locationInWindow]
83                        modifierFlags:[theEvent modifierFlags]
84                            timestamp:[theEvent timestamp]
85                         windowNumber:[theEvent windowNumber]
86                              context:[theEvent context]
87                          eventNumber:[_pendingExitEvent eventNumber]
88                       trackingNumber:[_pendingExitEvent trackingNumber]
89                             userData:[_pendingExitEvent userData]];
90  [self mouseEvent:exitEvent];
91  _pendingExitEvent.reset();
92}
93
94- (void)mouseEvent:(NSEvent*)theEvent {
95  // This method left intentionally blank.
96}
97
98- (EventHandled)keyEvent:(NSEvent*)theEvent {
99  // The default implementation of this method does not handle any key events.
100  // Derived classes should return kEventHandled if they handled an event,
101  // otherwise it will be forwarded on to |super|.
102  return kEventNotHandled;
103}
104
105- (void)forceTouchEvent:(NSEvent*)theEvent {
106  // This method left intentionally blank.
107}
108
109- (void)tabletEvent:(NSEvent*)theEvent {
110  // This method left intentionally blank.
111}
112
113- (void)mouseDown:(NSEvent*)theEvent {
114  _dragging = YES;
115  [self mouseEvent:theEvent];
116}
117
118- (void)rightMouseDown:(NSEvent*)theEvent {
119  [self mouseEvent:theEvent];
120}
121
122- (void)otherMouseDown:(NSEvent*)theEvent {
123  [self mouseEvent:theEvent];
124}
125
126- (void)mouseUp:(NSEvent*)theEvent {
127  [self mouseEvent:theEvent];
128  [self handleLeftMouseUp:theEvent];
129}
130
131- (void)rightMouseUp:(NSEvent*)theEvent {
132  [self mouseEvent:theEvent];
133}
134
135- (void)otherMouseUp:(NSEvent*)theEvent {
136  [self mouseEvent:theEvent];
137}
138
139- (void)mouseMoved:(NSEvent*)theEvent {
140  [self mouseEvent:theEvent];
141}
142
143- (void)mouseDragged:(NSEvent*)theEvent {
144  [self mouseEvent:theEvent];
145}
146
147- (void)rightMouseDragged:(NSEvent*)theEvent {
148  [self mouseEvent:theEvent];
149}
150
151- (void)otherMouseDragged:(NSEvent*)theEvent {
152  [self mouseEvent:theEvent];
153}
154
155- (void)mouseEntered:(NSEvent*)theEvent {
156  if (_pendingExitEvent) {
157    _pendingExitEvent.reset();
158    return;
159  }
160
161  [self mouseEvent:theEvent];
162}
163
164- (void)mouseExited:(NSEvent*)theEvent {
165  // The tracking area will send an exit event even during a drag, which isn't
166  // how the event flow for drags should work. This stores the exit event, and
167  // sends it when the drag completes instead.
168  if (_dragging) {
169    _pendingExitEvent.reset([theEvent retain]);
170    return;
171  }
172
173  [self mouseEvent:theEvent];
174}
175
176- (void)keyDown:(NSEvent*)theEvent {
177  if ([self keyEvent:theEvent] != kEventHandled)
178    [super keyDown:theEvent];
179}
180
181- (void)keyUp:(NSEvent*)theEvent {
182  if ([self keyEvent:theEvent] != kEventHandled)
183    [super keyUp:theEvent];
184}
185
186- (void)pressureChangeWithEvent:(NSEvent*)theEvent {
187  NSInteger newStage = [theEvent stage];
188  if (_pressureEventStage == newStage)
189    return;
190
191  // Call the force touch event when the stage reaches 2, which is the value
192  // for force touch.
193  if (newStage == 2) {
194    [self forceTouchEvent:theEvent];
195  }
196  _pressureEventStage = newStage;
197}
198
199- (void)flagsChanged:(NSEvent*)theEvent {
200  if ([self keyEvent:theEvent] != kEventHandled)
201    [super flagsChanged:theEvent];
202}
203
204- (gfx::Rect)flipNSRectToRect:(NSRect)rect {
205  gfx::Rect new_rect(NSRectToCGRect(rect));
206  new_rect.set_y(NSHeight([self bounds]) - new_rect.bottom());
207  return new_rect;
208}
209
210- (NSRect)flipRectToNSRect:(gfx::Rect)rect {
211  NSRect new_rect(NSRectFromCGRect(rect.ToCGRect()));
212  new_rect.origin.y = NSHeight([self bounds]) - NSMaxY(new_rect);
213  return new_rect;
214}
215
216- (void)tabletProximity:(NSEvent*)theEvent {
217  [self tabletEvent:theEvent];
218}
219
220@end
221