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