• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2014 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 "ui/views/cocoa/bridged_content_view.h"
6
7#include "base/logging.h"
8#import "base/mac/scoped_nsobject.h"
9#include "base/strings/sys_string_conversions.h"
10#include "ui/base/ime/text_input_client.h"
11#include "ui/gfx/canvas_paint_mac.h"
12#include "ui/gfx/geometry/rect.h"
13#include "ui/strings/grit/ui_strings.h"
14#include "ui/views/view.h"
15#include "ui/views/widget/widget.h"
16
17@interface BridgedContentView ()
18
19// Translates the location of |theEvent| to toolkit-views coordinates and passes
20// the event to NativeWidgetMac for handling.
21- (void)handleMouseEvent:(NSEvent*)theEvent;
22
23// Execute a command on the currently focused TextInputClient.
24// |commandId| should be a resource ID from ui_strings.grd.
25- (void)doCommandByID:(int)commandId;
26
27@end
28
29@implementation BridgedContentView
30
31@synthesize hostedView = hostedView_;
32@synthesize textInputClient = textInputClient_;
33
34- (id)initWithView:(views::View*)viewToHost {
35  DCHECK(viewToHost);
36  gfx::Rect bounds = viewToHost->bounds();
37  // To keep things simple, assume the origin is (0, 0) until there exists a use
38  // case for something other than that.
39  DCHECK(bounds.origin().IsOrigin());
40  NSRect initialFrame = NSMakeRect(0, 0, bounds.width(), bounds.height());
41  if ((self = [super initWithFrame:initialFrame])) {
42    hostedView_ = viewToHost;
43
44    trackingArea_.reset(
45        [[CrTrackingArea alloc] initWithRect:NSZeroRect
46                                     options:NSTrackingMouseMoved |
47                                             NSTrackingActiveAlways |
48                                             NSTrackingInVisibleRect
49                                       owner:self
50                                    userInfo:nil]);
51    [self addTrackingArea:trackingArea_.get()];
52  }
53  return self;
54}
55
56- (void)clearView {
57  hostedView_ = NULL;
58  [trackingArea_.get() clearOwner];
59  [self removeTrackingArea:trackingArea_.get()];
60}
61
62// BridgedContentView private implementation.
63
64- (void)handleMouseEvent:(NSEvent*)theEvent {
65  if (!hostedView_)
66    return;
67
68  ui::MouseEvent event(theEvent);
69  hostedView_->GetWidget()->OnMouseEvent(&event);
70}
71
72- (void)doCommandByID:(int)commandId {
73  if (textInputClient_ && textInputClient_->IsEditingCommandEnabled(commandId))
74    textInputClient_->ExecuteEditingCommand(commandId);
75}
76
77// NSView implementation.
78
79- (BOOL)acceptsFirstResponder {
80  return YES;
81}
82
83- (void)setFrameSize:(NSSize)newSize {
84  [super setFrameSize:newSize];
85  if (!hostedView_)
86    return;
87
88  hostedView_->SetSize(gfx::Size(newSize.width, newSize.height));
89}
90
91- (void)drawRect:(NSRect)dirtyRect {
92  if (!hostedView_)
93    return;
94
95  gfx::CanvasSkiaPaint canvas(dirtyRect, false /* opaque */);
96  hostedView_->Paint(&canvas, views::CullSet());
97}
98
99// NSResponder implementation.
100
101- (void)keyDown:(NSEvent*)theEvent {
102  if (textInputClient_)
103    [self interpretKeyEvents:@[ theEvent ]];
104  else
105    [super keyDown:theEvent];
106}
107
108- (void)mouseDown:(NSEvent*)theEvent {
109  [self handleMouseEvent:theEvent];
110}
111
112- (void)rightMouseDown:(NSEvent*)theEvent {
113  [self handleMouseEvent:theEvent];
114}
115
116- (void)otherMouseDown:(NSEvent*)theEvent {
117  [self handleMouseEvent:theEvent];
118}
119
120- (void)mouseUp:(NSEvent*)theEvent {
121  [self handleMouseEvent:theEvent];
122}
123
124- (void)rightMouseUp:(NSEvent*)theEvent {
125  [self handleMouseEvent:theEvent];
126}
127
128- (void)otherMouseUp:(NSEvent*)theEvent {
129  [self handleMouseEvent:theEvent];
130}
131
132- (void)mouseDragged:(NSEvent*)theEvent {
133  [self handleMouseEvent:theEvent];
134}
135
136- (void)rightMouseDragged:(NSEvent*)theEvent {
137  [self handleMouseEvent:theEvent];
138}
139
140- (void)otherMouseDragged:(NSEvent*)theEvent {
141  [self handleMouseEvent:theEvent];
142}
143
144- (void)mouseMoved:(NSEvent*)theEvent {
145  // Note: mouseEntered: and mouseExited: are not handled separately.
146  // |hostedView_| is responsible for converting the move events into entered
147  // and exited events for the view heirarchy.
148  [self handleMouseEvent:theEvent];
149}
150
151- (void)scrollWheel:(NSEvent*)theEvent {
152  if (!hostedView_)
153    return;
154
155  ui::MouseWheelEvent event(theEvent);
156  hostedView_->GetWidget()->OnMouseEvent(&event);
157}
158
159- (void)deleteBackward:(id)sender {
160  [self doCommandByID:IDS_DELETE_BACKWARD];
161}
162
163- (void)deleteForward:(id)sender {
164  [self doCommandByID:IDS_DELETE_FORWARD];
165}
166
167- (void)moveLeft:(id)sender {
168  [self doCommandByID:IDS_MOVE_LEFT];
169}
170
171- (void)moveRight:(id)sender {
172  [self doCommandByID:IDS_MOVE_RIGHT];
173}
174
175- (void)insertText:(id)text {
176  if (textInputClient_)
177    textInputClient_->InsertText(base::SysNSStringToUTF16(text));
178}
179
180// Support for Services in context menus.
181// Currently we only support reading and writing plain strings.
182- (id)validRequestorForSendType:(NSString*)sendType
183                     returnType:(NSString*)returnType {
184  BOOL canWrite = [sendType isEqualToString:NSStringPboardType] &&
185                  [self selectedRange].length > 0;
186  BOOL canRead = [returnType isEqualToString:NSStringPboardType];
187  // Valid if (sendType, returnType) is either (string, nil), (nil, string),
188  // or (string, string).
189  BOOL valid = textInputClient_ && ((canWrite && (canRead || !returnType)) ||
190                                    (canRead && (canWrite || !sendType)));
191  return valid ? self : [super validRequestorForSendType:sendType
192                                              returnType:returnType];
193}
194
195// NSServicesRequests informal protocol.
196
197- (BOOL)writeSelectionToPasteboard:(NSPasteboard*)pboard types:(NSArray*)types {
198  DCHECK([types containsObject:NSStringPboardType]);
199  if (!textInputClient_)
200    return NO;
201
202  gfx::Range selectionRange;
203  if (!textInputClient_->GetSelectionRange(&selectionRange))
204    return NO;
205
206  base::string16 text;
207  textInputClient_->GetTextFromRange(selectionRange, &text);
208  return [pboard writeObjects:@[ base::SysUTF16ToNSString(text) ]];
209}
210
211- (BOOL)readSelectionFromPasteboard:(NSPasteboard*)pboard {
212  NSArray* objects =
213      [pboard readObjectsForClasses:@[ [NSString class] ] options:0];
214  DCHECK([objects count] == 1);
215  [self insertText:[objects lastObject]];
216  return YES;
217}
218
219// NSTextInputClient protocol implementation.
220
221- (NSAttributedString*)
222    attributedSubstringForProposedRange:(NSRange)range
223                            actualRange:(NSRangePointer)actualRange {
224  base::string16 substring;
225  if (textInputClient_) {
226    gfx::Range textRange;
227    textInputClient_->GetTextRange(&textRange);
228    gfx::Range subrange = textRange.Intersect(gfx::Range(range));
229    textInputClient_->GetTextFromRange(subrange, &substring);
230    if (actualRange)
231      *actualRange = subrange.ToNSRange();
232  }
233  return [[[NSAttributedString alloc]
234      initWithString:base::SysUTF16ToNSString(substring)] autorelease];
235}
236
237- (NSUInteger)characterIndexForPoint:(NSPoint)aPoint {
238  NOTIMPLEMENTED();
239  return 0;
240}
241
242- (void)doCommandBySelector:(SEL)selector {
243  if ([self respondsToSelector:selector])
244    [self performSelector:selector withObject:nil];
245  else
246    [[self nextResponder] doCommandBySelector:selector];
247}
248
249- (NSRect)firstRectForCharacterRange:(NSRange)range
250                         actualRange:(NSRangePointer)actualRange {
251  NOTIMPLEMENTED();
252  return NSZeroRect;
253}
254
255- (BOOL)hasMarkedText {
256  return textInputClient_ && textInputClient_->HasCompositionText();
257}
258
259- (void)insertText:(id)text replacementRange:(NSRange)replacementRange {
260  if (!textInputClient_)
261    return;
262
263  if ([text isKindOfClass:[NSAttributedString class]])
264    text = [text string];
265  textInputClient_->DeleteRange(gfx::Range(replacementRange));
266  textInputClient_->InsertText(base::SysNSStringToUTF16(text));
267}
268
269- (NSRange)markedRange {
270  if (!textInputClient_)
271    return NSMakeRange(NSNotFound, 0);
272
273  gfx::Range range;
274  textInputClient_->GetCompositionTextRange(&range);
275  return range.ToNSRange();
276}
277
278- (NSRange)selectedRange {
279  if (!textInputClient_)
280    return NSMakeRange(NSNotFound, 0);
281
282  gfx::Range range;
283  textInputClient_->GetSelectionRange(&range);
284  return range.ToNSRange();
285}
286
287- (void)setMarkedText:(id)text
288        selectedRange:(NSRange)selectedRange
289     replacementRange:(NSRange)replacementRange {
290  if (!textInputClient_)
291    return;
292
293  if ([text isKindOfClass:[NSAttributedString class]])
294    text = [text string];
295  ui::CompositionText composition;
296  composition.text = base::SysNSStringToUTF16(text);
297  composition.selection = gfx::Range(selectedRange);
298  textInputClient_->SetCompositionText(composition);
299}
300
301- (void)unmarkText {
302  if (textInputClient_)
303    textInputClient_->ConfirmCompositionText();
304}
305
306- (NSArray*)validAttributesForMarkedText {
307  return @[];
308}
309
310// NSAccessibility informal protocol implementation.
311
312- (id)accessibilityAttributeValue:(NSString*)attribute {
313  if ([attribute isEqualToString:NSAccessibilityChildrenAttribute]) {
314    return @[ hostedView_->GetNativeViewAccessible() ];
315  }
316
317  return [super accessibilityAttributeValue:attribute];
318}
319
320- (id)accessibilityHitTest:(NSPoint)point {
321  return [hostedView_->GetNativeViewAccessible() accessibilityHitTest:point];
322}
323
324@end
325