base/threading: remove ScopedTracker placed for experiments
[chromium-blink-merge.git] / ui / views / cocoa / bridged_content_view.mm
blob0a4b25796790c6c0aa92350d1710839250849c42
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.
5 #import "ui/views/cocoa/bridged_content_view.h"
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/input_method.h"
11 #include "ui/base/ime/text_input_client.h"
12 #include "ui/compositor/canvas_painter.h"
13 #import "ui/events/cocoa/cocoa_event_utils.h"
14 #include "ui/events/keycodes/dom/dom_code.h"
15 #import "ui/events/keycodes/keyboard_code_conversion_mac.h"
16 #include "ui/gfx/canvas_paint_mac.h"
17 #include "ui/gfx/geometry/rect.h"
18 #include "ui/strings/grit/ui_strings.h"
19 #include "ui/views/controls/menu/menu_controller.h"
20 #include "ui/views/view.h"
21 #include "ui/views/widget/widget.h"
23 using views::MenuController;
25 namespace {
27 // Convert a |point| in |source_window|'s AppKit coordinate system (origin at
28 // the bottom left of the window) to |target_window|'s content rect, with the
29 // origin at the top left of the content area.
30 // If |source_window| is nil, |point| will be treated as screen coordinates.
31 gfx::Point MovePointToWindow(const NSPoint& point,
32                              NSWindow* source_window,
33                              NSWindow* target_window) {
34   NSPoint point_in_screen = source_window
35       ? [source_window convertBaseToScreen:point]
36       : point;
38   NSPoint point_in_window = [target_window convertScreenToBase:point_in_screen];
39   NSRect content_rect =
40       [target_window contentRectForFrameRect:[target_window frame]];
41   return gfx::Point(point_in_window.x,
42                     NSHeight(content_rect) - point_in_window.y);
45 // Checks if there's an active MenuController during key event dispatch. If
46 // there is one, it gets preference, and it will likely swallow the event.
47 bool DispatchEventToMenu(views::Widget* widget, ui::KeyboardCode key_code) {
48   MenuController* menuController = MenuController::GetActiveInstance();
49   if (menuController && menuController->owner() == widget) {
50     if (menuController->OnWillDispatchKeyEvent(0, key_code) ==
51         ui::POST_DISPATCH_NONE)
52       return true;
53   }
54   return false;
57 }  // namespace
59 @interface BridgedContentView ()
61 // Translates keycodes and modifiers on |theEvent| to ui::KeyEvents and passes
62 // the event to the InputMethod for dispatch.
63 - (void)handleKeyEvent:(NSEvent*)theEvent;
65 // Handles an NSResponder Action Message by mapping it to a corresponding text
66 // editing command from ui_strings.grd and, when not being sent to a
67 // TextInputClient, the keyCode that toolkit-views expects internally.
68 // For example, moveToLeftEndOfLine: would pass ui::VKEY_HOME in non-RTL locales
69 // even though the Home key on Mac defaults to moveToBeginningOfDocument:.
70 // This approach also allows action messages a user
71 // may have remapped in ~/Library/KeyBindings/DefaultKeyBinding.dict to be
72 // catered for.
73 // Note: default key bindings in Mac can be read from StandardKeyBinding.dict
74 // which lives in /System/Library/Frameworks/AppKit.framework/Resources. Do
75 // `plutil -convert xml1 -o StandardKeyBinding.xml StandardKeyBinding.dict` to
76 // get something readable.
77 - (void)handleAction:(int)commandId
78              keyCode:(ui::KeyboardCode)keyCode
79              domCode:(ui::DomCode)domCode
80           eventFlags:(int)eventFlags;
82 // Menu action handlers.
83 - (void)undo:(id)sender;
84 - (void)redo:(id)sender;
85 - (void)cut:(id)sender;
86 - (void)copy:(id)sender;
87 - (void)paste:(id)sender;
88 - (void)selectAll:(id)sender;
90 @end
92 @implementation BridgedContentView
94 @synthesize hostedView = hostedView_;
95 @synthesize textInputClient = textInputClient_;
96 @synthesize mouseDownCanMoveWindow = mouseDownCanMoveWindow_;
98 - (id)initWithView:(views::View*)viewToHost {
99   DCHECK(viewToHost);
100   gfx::Rect bounds = viewToHost->bounds();
101   // To keep things simple, assume the origin is (0, 0) until there exists a use
102   // case for something other than that.
103   DCHECK(bounds.origin().IsOrigin());
104   NSRect initialFrame = NSMakeRect(0, 0, bounds.width(), bounds.height());
105   if ((self = [super initWithFrame:initialFrame])) {
106     hostedView_ = viewToHost;
108     // Apple's documentation says that NSTrackingActiveAlways is incompatible
109     // with NSTrackingCursorUpdate, so use NSTrackingActiveInActiveApp.
110     cursorTrackingArea_.reset([[CrTrackingArea alloc]
111         initWithRect:NSZeroRect
112              options:NSTrackingMouseMoved | NSTrackingCursorUpdate |
113                      NSTrackingActiveInActiveApp | NSTrackingInVisibleRect
114                owner:self
115             userInfo:nil]);
116     [self addTrackingArea:cursorTrackingArea_.get()];
117   }
118   return self;
121 - (void)clearView {
122   hostedView_ = NULL;
123   [cursorTrackingArea_.get() clearOwner];
124   [self removeTrackingArea:cursorTrackingArea_.get()];
127 - (void)processCapturedMouseEvent:(NSEvent*)theEvent {
128   if (!hostedView_)
129     return;
131   NSWindow* source = [theEvent window];
132   NSWindow* target = [self window];
133   DCHECK(target);
135   // If it's the view's window, process normally.
136   if ([target isEqual:source]) {
137     [self mouseEvent:theEvent];
138     return;
139   }
141   ui::MouseEvent event(theEvent);
142   event.set_location(
143       MovePointToWindow([theEvent locationInWindow], source, target));
144   hostedView_->GetWidget()->OnMouseEvent(&event);
147 - (void)updateTooltipIfRequiredAt:(const gfx::Point&)locationInContent {
148   DCHECK(hostedView_);
149   base::string16 newTooltipText;
151   views::View* view = hostedView_->GetTooltipHandlerForPoint(locationInContent);
152   if (view) {
153     gfx::Point viewPoint = locationInContent;
154     views::View::ConvertPointToTarget(hostedView_, view, &viewPoint);
155     if (!view->GetTooltipText(viewPoint, &newTooltipText))
156       DCHECK(newTooltipText.empty());
157   }
158   if (newTooltipText != lastTooltipText_) {
159     std::swap(newTooltipText, lastTooltipText_);
160     [self setToolTipAtMousePoint:base::SysUTF16ToNSString(lastTooltipText_)];
161   }
164 // BridgedContentView private implementation.
166 - (void)handleKeyEvent:(NSEvent*)theEvent {
167   if (!hostedView_)
168     return;
170   DCHECK(theEvent);
171   ui::KeyEvent event(theEvent);
172   if (DispatchEventToMenu(hostedView_->GetWidget(), event.key_code()))
173     return;
175   hostedView_->GetWidget()->GetInputMethod()->DispatchKeyEvent(&event);
178 - (void)handleAction:(int)commandId
179              keyCode:(ui::KeyboardCode)keyCode
180              domCode:(ui::DomCode)domCode
181           eventFlags:(int)eventFlags {
182   if (!hostedView_)
183     return;
185   if (DispatchEventToMenu(hostedView_->GetWidget(), keyCode))
186     return;
188   // If there's an active TextInputClient, schedule the editing command to be
189   // performed.
190   if (commandId && textInputClient_ &&
191       textInputClient_->IsEditCommandEnabled(commandId))
192     textInputClient_->SetEditCommandForNextKeyEvent(commandId);
194   // Generate a synthetic event with the keycode toolkit-views expects.
195   ui::KeyEvent event(ui::ET_KEY_PRESSED, keyCode, domCode, eventFlags);
196   hostedView_->GetWidget()->GetInputMethod()->DispatchKeyEvent(&event);
199 - (void)undo:(id)sender {
200   // This DCHECK is more strict than a similar check in handleAction:. It can be
201   // done here because the actors sending these actions should be calling
202   // validateUserInterfaceItem: before enabling UI that allows these messages to
203   // be sent. Checking it here would be too late to provide correct UI feedback
204   // (e.g. there will be no "beep").
205   DCHECK(textInputClient_->IsEditCommandEnabled(IDS_APP_UNDO));
206   [self handleAction:IDS_APP_UNDO
207              keyCode:ui::VKEY_Z
208              domCode:ui::DomCode::KEY_Z
209           eventFlags:ui::EF_CONTROL_DOWN];
212 - (void)redo:(id)sender {
213   DCHECK(textInputClient_->IsEditCommandEnabled(IDS_APP_REDO));
214   [self handleAction:IDS_APP_REDO
215              keyCode:ui::VKEY_Z
216              domCode:ui::DomCode::KEY_Z
217           eventFlags:ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN];
220 - (void)cut:(id)sender {
221   DCHECK(textInputClient_->IsEditCommandEnabled(IDS_APP_CUT));
222   [self handleAction:IDS_APP_CUT
223              keyCode:ui::VKEY_X
224              domCode:ui::DomCode::KEY_X
225           eventFlags:ui::EF_CONTROL_DOWN];
228 - (void)copy:(id)sender {
229   DCHECK(textInputClient_->IsEditCommandEnabled(IDS_APP_COPY));
230   [self handleAction:IDS_APP_COPY
231              keyCode:ui::VKEY_C
232              domCode:ui::DomCode::KEY_C
233           eventFlags:ui::EF_CONTROL_DOWN];
236 - (void)paste:(id)sender {
237   DCHECK(textInputClient_->IsEditCommandEnabled(IDS_APP_PASTE));
238   [self handleAction:IDS_APP_PASTE
239              keyCode:ui::VKEY_V
240              domCode:ui::DomCode::KEY_V
241           eventFlags:ui::EF_CONTROL_DOWN];
244 - (void)selectAll:(id)sender {
245   DCHECK(textInputClient_->IsEditCommandEnabled(IDS_APP_SELECT_ALL));
246   [self handleAction:IDS_APP_SELECT_ALL
247              keyCode:ui::VKEY_A
248              domCode:ui::DomCode::KEY_A
249           eventFlags:ui::EF_CONTROL_DOWN];
252 // BaseView implementation.
254 // Don't use tracking areas from BaseView. BridgedContentView's tracks
255 // NSTrackingCursorUpdate and Apple's documentation suggests it's incompatible.
256 - (void)enableTracking {
259 // Translates the location of |theEvent| to toolkit-views coordinates and passes
260 // the event to NativeWidgetMac for handling.
261 - (void)mouseEvent:(NSEvent*)theEvent {
262   if (!hostedView_)
263     return;
265   ui::MouseEvent event(theEvent);
267   // Aura updates tooltips with the help of aura::Window::AddPreTargetHandler().
268   // Mac hooks in here.
269   [self updateTooltipIfRequiredAt:event.location()];
271   hostedView_->GetWidget()->OnMouseEvent(&event);
274 // NSView implementation.
276 - (BOOL)acceptsFirstResponder {
277   return YES;
280 - (void)viewDidMoveToWindow {
281   // When this view is added to a window, AppKit calls setFrameSize before it is
282   // added to the window, so the behavior in setFrameSize is not triggered.
283   NSWindow* window = [self window];
284   if (window)
285     [self setFrameSize:NSZeroSize];
288 - (void)setFrameSize:(NSSize)newSize {
289   // The size passed in here does not always use
290   // -[NSWindow contentRectForFrameRect]. The following ensures that the
291   // contentView for a frameless window can extend over the titlebar of the new
292   // window containing it, since AppKit requires a titlebar to give frameless
293   // windows correct shadows and rounded corners.
294   NSWindow* window = [self window];
295   if (window)
296     newSize = [window contentRectForFrameRect:[window frame]].size;
298   [super setFrameSize:newSize];
299   if (!hostedView_)
300     return;
302   hostedView_->SetSize(gfx::Size(newSize.width, newSize.height));
305 - (void)drawRect:(NSRect)dirtyRect {
306   // Note that BridgedNativeWidget uses -[NSWindow setAutodisplay:NO] to
307   // suppress calls to this when the window is known to be hidden.
308   if (!hostedView_)
309     return;
311   // If there's a layer, painting occurs in BridgedNativeWidget::OnPaintLayer().
312   if (hostedView_->GetWidget()->GetLayer())
313     return;
315   gfx::CanvasSkiaPaint canvas(dirtyRect, false /* opaque */);
316   hostedView_->GetWidget()->OnNativeWidgetPaint(
317       ui::CanvasPainter(&canvas, 1.f).context());
320 - (NSTextInputContext*)inputContext {
321   if (!hostedView_)
322     return [super inputContext];
324   // If a menu is active, and -[NSView interpretKeyEvents:] asks for the
325   // input context, return nil. This ensures the action message is sent to
326   // the view, rather than any NSTextInputClient a subview has installed.
327   MenuController* menuController = MenuController::GetActiveInstance();
328   if (menuController && menuController->owner() == hostedView_->GetWidget())
329     return nil;
331   return [super inputContext];
334 // NSResponder implementation.
336 - (void)keyDown:(NSEvent*)theEvent {
337   // Convert the event into an action message, according to OSX key mappings.
338   inKeyDown_ = YES;
339   [self interpretKeyEvents:@[ theEvent ]];
340   inKeyDown_ = NO;
343 - (void)scrollWheel:(NSEvent*)theEvent {
344   if (!hostedView_)
345     return;
347   ui::MouseWheelEvent event(theEvent);
348   hostedView_->GetWidget()->OnMouseEvent(&event);
351 ////////////////////////////////////////////////////////////////////////////////
352 // NSResponder Action Messages. Keep sorted according NSResponder.h (from the
353 // 10.9 SDK). The list should eventually be complete. Anything not defined will
354 // beep when interpretKeyEvents: would otherwise call it.
355 // TODO(tapted): Make this list complete, except for insert* methods which are
356 // dispatched as regular key events in doCommandBySelector:.
358 // The insertText action message forwards to the TextInputClient unless a menu
359 // is active. Note that NSResponder's interpretKeyEvents: implementation doesn't
360 // direct insertText: through doCommandBySelector:, so this is still needed to
361 // handle the case when inputContext: is nil. When inputContext: returns non-nil
362 // text goes directly to insertText:replacementRange:.
363 - (void)insertText:(id)text {
364   [self insertText:text replacementRange:NSMakeRange(NSNotFound, 0)];
367 // Selection movement and scrolling.
369 - (void)moveRight:(id)sender {
370   [self handleAction:IDS_MOVE_RIGHT
371              keyCode:ui::VKEY_RIGHT
372              domCode:ui::DomCode::ARROW_RIGHT
373           eventFlags:0];
376 - (void)moveLeft:(id)sender {
377   [self handleAction:IDS_MOVE_LEFT
378              keyCode:ui::VKEY_LEFT
379              domCode:ui::DomCode::ARROW_LEFT
380           eventFlags:0];
383 - (void)moveUp:(id)sender {
384   [self handleAction:0
385              keyCode:ui::VKEY_UP
386              domCode:ui::DomCode::ARROW_UP
387           eventFlags:0];
390 - (void)moveDown:(id)sender {
391   [self handleAction:0
392              keyCode:ui::VKEY_DOWN
393              domCode:ui::DomCode::ARROW_DOWN
394           eventFlags:0];
397 - (void)moveWordRight:(id)sender {
398   [self handleAction:IDS_MOVE_WORD_RIGHT
399              keyCode:ui::VKEY_RIGHT
400              domCode:ui::DomCode::ARROW_RIGHT
401           eventFlags:ui::EF_CONTROL_DOWN];
404 - (void)moveWordLeft:(id)sender {
405   [self handleAction:IDS_MOVE_WORD_LEFT
406              keyCode:ui::VKEY_LEFT
407              domCode:ui::DomCode::ARROW_LEFT
408           eventFlags:ui::EF_CONTROL_DOWN];
411 - (void)moveLeftAndModifySelection:(id)sender {
412   [self handleAction:IDS_MOVE_LEFT_AND_MODIFY_SELECTION
413              keyCode:ui::VKEY_LEFT
414              domCode:ui::DomCode::ARROW_LEFT
415           eventFlags:ui::EF_SHIFT_DOWN];
418 - (void)moveRightAndModifySelection:(id)sender {
419   [self handleAction:IDS_MOVE_RIGHT_AND_MODIFY_SELECTION
420              keyCode:ui::VKEY_RIGHT
421              domCode:ui::DomCode::ARROW_RIGHT
422           eventFlags:ui::EF_SHIFT_DOWN];
425 - (void)moveWordRightAndModifySelection:(id)sender {
426   [self handleAction:IDS_MOVE_WORD_RIGHT_AND_MODIFY_SELECTION
427              keyCode:ui::VKEY_RIGHT
428              domCode:ui::DomCode::ARROW_RIGHT
429           eventFlags:ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN];
432 - (void)moveWordLeftAndModifySelection:(id)sender {
433   [self handleAction:IDS_MOVE_WORD_LEFT_AND_MODIFY_SELECTION
434              keyCode:ui::VKEY_LEFT
435              domCode:ui::DomCode::ARROW_LEFT
436           eventFlags:ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN];
439 - (void)moveToLeftEndOfLine:(id)sender {
440   [self handleAction:IDS_MOVE_TO_BEGINNING_OF_LINE
441              keyCode:ui::VKEY_HOME
442              domCode:ui::DomCode::HOME
443           eventFlags:0];
446 - (void)moveToRightEndOfLine:(id)sender {
447   [self handleAction:IDS_MOVE_TO_END_OF_LINE
448              keyCode:ui::VKEY_END
449              domCode:ui::DomCode::END
450           eventFlags:0];
453 - (void)moveToLeftEndOfLineAndModifySelection:(id)sender {
454   [self handleAction:IDS_MOVE_TO_BEGINNING_OF_LINE_AND_MODIFY_SELECTION
455              keyCode:ui::VKEY_HOME
456              domCode:ui::DomCode::HOME
457           eventFlags:ui::EF_SHIFT_DOWN];
460 - (void)moveToRightEndOfLineAndModifySelection:(id)sender {
461   [self handleAction:IDS_MOVE_TO_END_OF_LINE_AND_MODIFY_SELECTION
462              keyCode:ui::VKEY_END
463              domCode:ui::DomCode::END
464           eventFlags:ui::EF_SHIFT_DOWN];
467 // Deletions.
469 - (void)deleteForward:(id)sender {
470   [self handleAction:IDS_DELETE_FORWARD
471              keyCode:ui::VKEY_DELETE
472              domCode:ui::DomCode::DEL
473           eventFlags:0];
476 - (void)deleteBackward:(id)sender {
477   [self handleAction:IDS_DELETE_BACKWARD
478              keyCode:ui::VKEY_BACK
479              domCode:ui::DomCode::BACKSPACE
480           eventFlags:0];
483 - (void)deleteWordForward:(id)sender {
484   [self handleAction:IDS_DELETE_WORD_FORWARD
485              keyCode:ui::VKEY_DELETE
486              domCode:ui::DomCode::DEL
487           eventFlags:ui::EF_CONTROL_DOWN];
490 - (void)deleteWordBackward:(id)sender {
491   [self handleAction:IDS_DELETE_WORD_BACKWARD
492              keyCode:ui::VKEY_BACK
493              domCode:ui::DomCode::BACKSPACE
494           eventFlags:ui::EF_CONTROL_DOWN];
497 // Cancellation.
499 - (void)cancelOperation:(id)sender {
500   [self handleAction:0
501              keyCode:ui::VKEY_ESCAPE
502              domCode:ui::DomCode::ESCAPE
503           eventFlags:0];
506 // Support for Services in context menus.
507 // Currently we only support reading and writing plain strings.
508 - (id)validRequestorForSendType:(NSString*)sendType
509                      returnType:(NSString*)returnType {
510   BOOL canWrite = [sendType isEqualToString:NSStringPboardType] &&
511                   [self selectedRange].length > 0;
512   BOOL canRead = [returnType isEqualToString:NSStringPboardType];
513   // Valid if (sendType, returnType) is either (string, nil), (nil, string),
514   // or (string, string).
515   BOOL valid = textInputClient_ && ((canWrite && (canRead || !returnType)) ||
516                                     (canRead && (canWrite || !sendType)));
517   return valid ? self : [super validRequestorForSendType:sendType
518                                               returnType:returnType];
521 // NSServicesRequests informal protocol.
523 - (BOOL)writeSelectionToPasteboard:(NSPasteboard*)pboard types:(NSArray*)types {
524   DCHECK([types containsObject:NSStringPboardType]);
525   if (!textInputClient_)
526     return NO;
528   gfx::Range selectionRange;
529   if (!textInputClient_->GetSelectionRange(&selectionRange))
530     return NO;
532   base::string16 text;
533   textInputClient_->GetTextFromRange(selectionRange, &text);
534   return [pboard writeObjects:@[ base::SysUTF16ToNSString(text) ]];
537 - (BOOL)readSelectionFromPasteboard:(NSPasteboard*)pboard {
538   NSArray* objects =
539       [pboard readObjectsForClasses:@[ [NSString class] ] options:0];
540   DCHECK([objects count] == 1);
541   [self insertText:[objects lastObject]];
542   return YES;
545 // NSTextInputClient protocol implementation.
547 - (NSAttributedString*)
548     attributedSubstringForProposedRange:(NSRange)range
549                             actualRange:(NSRangePointer)actualRange {
550   base::string16 substring;
551   if (textInputClient_) {
552     gfx::Range textRange;
553     textInputClient_->GetTextRange(&textRange);
554     gfx::Range subrange = textRange.Intersect(gfx::Range(range));
555     textInputClient_->GetTextFromRange(subrange, &substring);
556     if (actualRange)
557       *actualRange = subrange.ToNSRange();
558   }
559   return [[[NSAttributedString alloc]
560       initWithString:base::SysUTF16ToNSString(substring)] autorelease];
563 - (NSUInteger)characterIndexForPoint:(NSPoint)aPoint {
564   NOTIMPLEMENTED();
565   return 0;
568 - (void)doCommandBySelector:(SEL)selector {
569   // Like the renderer, handle insert action messages as a regular key dispatch.
570   // This ensures, e.g., insertTab correctly changes focus between fields.
571   if (inKeyDown_ && [NSStringFromSelector(selector) hasPrefix:@"insert"]) {
572     [self handleKeyEvent:[NSApp currentEvent]];
573     return;
574   }
576   if ([self respondsToSelector:selector])
577     [self performSelector:selector withObject:nil];
578   else
579     [[self nextResponder] doCommandBySelector:selector];
582 - (NSRect)firstRectForCharacterRange:(NSRange)range
583                          actualRange:(NSRangePointer)actualRange {
584   NOTIMPLEMENTED();
585   return NSZeroRect;
588 - (BOOL)hasMarkedText {
589   return textInputClient_ && textInputClient_->HasCompositionText();
592 - (void)insertText:(id)text replacementRange:(NSRange)replacementRange {
593   if (!hostedView_)
594     return;
596   if ([text isKindOfClass:[NSAttributedString class]])
597     text = [text string];
599   MenuController* menuController = MenuController::GetActiveInstance();
600   if (menuController && menuController->owner() == hostedView_->GetWidget()) {
601     // Handle menu mnemonics (e.g. "sav" jumps to "Save"). Handles both single-
602     // characters and input from IME. For IME, swallow the entire string unless
603     // the very first character gives ui::POST_DISPATCH_PERFORM_DEFAULT.
604     bool swallowedAny = false;
605     for (NSUInteger i = 0; i < [text length]; ++i) {
606       if (!menuController ||
607           menuController->OnWillDispatchKeyEvent([text characterAtIndex:i],
608                                                  ui::VKEY_UNKNOWN) ==
609               ui::POST_DISPATCH_PERFORM_DEFAULT) {
610         if (swallowedAny)
611           return;  // Swallow remainder.
612         break;
613       }
614       swallowedAny = true;
615       // Ensure the menu remains active.
616       menuController = MenuController::GetActiveInstance();
617     }
618   }
620   if (!textInputClient_)
621     return;
623   textInputClient_->DeleteRange(gfx::Range(replacementRange));
625   // If a single character is inserted by keyDown's call to interpretKeyEvents:
626   // then use InsertChar() to allow editing events to be merged. The second
627   // argument is the key modifier, which interpretKeyEvents: will have already
628   // processed, so don't send it to InsertChar() as well. E.g. Alt+S puts 'ß' in
629   // |text| but sending 'Alt' to InsertChar would filter it out since it thinks
630   // it's a command. Actual commands (e.g. Cmd+S) won't go through insertText:.
631   if (inKeyDown_ && [text length] == 1)
632     textInputClient_->InsertChar([text characterAtIndex:0], 0);
633   else
634     textInputClient_->InsertText(base::SysNSStringToUTF16(text));
637 - (NSRange)markedRange {
638   if (!textInputClient_)
639     return NSMakeRange(NSNotFound, 0);
641   gfx::Range range;
642   textInputClient_->GetCompositionTextRange(&range);
643   return range.ToNSRange();
646 - (NSRange)selectedRange {
647   if (!textInputClient_)
648     return NSMakeRange(NSNotFound, 0);
650   gfx::Range range;
651   textInputClient_->GetSelectionRange(&range);
652   return range.ToNSRange();
655 - (void)setMarkedText:(id)text
656         selectedRange:(NSRange)selectedRange
657      replacementRange:(NSRange)replacementRange {
658   if (!textInputClient_)
659     return;
661   if ([text isKindOfClass:[NSAttributedString class]])
662     text = [text string];
663   ui::CompositionText composition;
664   composition.text = base::SysNSStringToUTF16(text);
665   composition.selection = gfx::Range(selectedRange);
666   textInputClient_->SetCompositionText(composition);
669 - (void)unmarkText {
670   if (textInputClient_)
671     textInputClient_->ConfirmCompositionText();
674 - (NSArray*)validAttributesForMarkedText {
675   return @[];
678 // NSUserInterfaceValidations protocol implementation.
680 - (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item {
681   if (!textInputClient_)
682     return NO;
684   SEL action = [item action];
686   if (action == @selector(undo:))
687     return textInputClient_->IsEditCommandEnabled(IDS_APP_UNDO);
688   if (action == @selector(redo:))
689     return textInputClient_->IsEditCommandEnabled(IDS_APP_REDO);
690   if (action == @selector(cut:))
691     return textInputClient_->IsEditCommandEnabled(IDS_APP_CUT);
692   if (action == @selector(copy:))
693     return textInputClient_->IsEditCommandEnabled(IDS_APP_COPY);
694   if (action == @selector(paste:))
695     return textInputClient_->IsEditCommandEnabled(IDS_APP_PASTE);
696   if (action == @selector(selectAll:))
697     return textInputClient_->IsEditCommandEnabled(IDS_APP_SELECT_ALL);
699   return NO;
702 // NSAccessibility informal protocol implementation.
704 - (id)accessibilityAttributeValue:(NSString*)attribute {
705   if ([attribute isEqualToString:NSAccessibilityChildrenAttribute]) {
706     return @[ hostedView_->GetNativeViewAccessible() ];
707   }
709   return [super accessibilityAttributeValue:attribute];
712 - (id)accessibilityHitTest:(NSPoint)point {
713   return [hostedView_->GetNativeViewAccessible() accessibilityHitTest:point];
716 @end