Home | History | Annotate | Download | only in cocoa
      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