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