chrome_render_widget_host_view_mac_delegate.mm revision 116680a4aac90f2aa7413d9095a592090648e557
1// Copyright (c) 2012 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 "chrome/browser/renderer_host/chrome_render_widget_host_view_mac_delegate.h"
6
7#include <cmath>
8
9#include "base/prefs/pref_service.h"
10#include "base/strings/sys_string_conversions.h"
11#include "chrome/browser/devtools/devtools_window.h"
12#include "chrome/browser/profiles/profile.h"
13#import "chrome/browser/renderer_host/chrome_render_widget_host_view_mac_history_swiper.h"
14#include "chrome/browser/spellchecker/spellcheck_platform_mac.h"
15#include "chrome/browser/ui/browser.h"
16#include "chrome/browser/ui/browser_commands.h"
17#include "chrome/browser/ui/browser_finder.h"
18#import "chrome/browser/ui/cocoa/browser_window_controller.h"
19#import "chrome/browser/ui/cocoa/view_id_util.h"
20#include "chrome/common/pref_names.h"
21#include "chrome/common/spellcheck_messages.h"
22#include "chrome/common/url_constants.h"
23#include "content/public/browser/render_process_host.h"
24#include "content/public/browser/render_view_host.h"
25#include "content/public/browser/render_widget_host.h"
26#include "content/public/browser/render_widget_host_view.h"
27#include "content/public/browser/web_contents.h"
28#include "content/public/browser/web_contents_observer.h"
29
30using content::RenderViewHost;
31
32@interface ChromeRenderWidgetHostViewMacDelegate () <HistorySwiperDelegate>
33- (void)spellCheckEnabled:(BOOL)enabled checked:(BOOL)checked;
34@end
35
36namespace ChromeRenderWidgetHostViewMacDelegateInternal {
37
38// Filters the message sent by the renderer to know if spellchecking is enabled
39// or not for the currently focused element.
40class SpellCheckObserver : public content::WebContentsObserver {
41 public:
42  SpellCheckObserver(
43      RenderViewHost* host,
44      ChromeRenderWidgetHostViewMacDelegate* view_delegate)
45      : content::WebContentsObserver(
46            content::WebContents::FromRenderViewHost(host)),
47        view_delegate_(view_delegate) {
48  }
49
50  virtual ~SpellCheckObserver() {
51  }
52
53 private:
54  virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE {
55    bool handled = true;
56    IPC_BEGIN_MESSAGE_MAP(SpellCheckObserver, message)
57      IPC_MESSAGE_HANDLER(SpellCheckHostMsg_ToggleSpellCheck,
58                          OnToggleSpellCheck)
59      IPC_MESSAGE_UNHANDLED(handled = false)
60    IPC_END_MESSAGE_MAP()
61    return handled;
62  }
63
64  void OnToggleSpellCheck(bool enabled, bool checked) {
65    [view_delegate_ spellCheckEnabled:enabled checked:checked];
66  }
67
68  ChromeRenderWidgetHostViewMacDelegate* view_delegate_;
69};
70
71}  // namespace ChromeRenderWidgetHostViewMacDelegateInternal
72
73@implementation ChromeRenderWidgetHostViewMacDelegate
74
75- (id)initWithRenderWidgetHost:(content::RenderWidgetHost*)renderWidgetHost {
76  self = [super init];
77  if (self) {
78    renderWidgetHost_ = renderWidgetHost;
79    NSView* nativeView = renderWidgetHost_->GetView()->GetNativeView();
80    view_id_util::SetID(nativeView, VIEW_ID_TAB_CONTAINER);
81
82    if (renderWidgetHost_->IsRenderView()) {
83      spellingObserver_.reset(
84          new ChromeRenderWidgetHostViewMacDelegateInternal::SpellCheckObserver(
85              RenderViewHost::From(renderWidgetHost_), self));
86    }
87
88    historySwiper_.reset([[HistorySwiper alloc] initWithDelegate:self]);
89  }
90  return self;
91}
92
93- (void)dealloc {
94  [historySwiper_ setDelegate:nil];
95  [super dealloc];
96}
97
98- (void)viewGone:(NSView*)view {
99  view_id_util::UnsetID(view);
100}
101
102// Handle an event. All incoming key and mouse events flow through this
103// delegate method if implemented. Return YES if the event is fully handled, or
104// NO if normal processing should take place.
105- (BOOL)handleEvent:(NSEvent*)event {
106  return [historySwiper_ handleEvent:event];
107}
108
109// NSWindow events.
110
111- (void)beginGestureWithEvent:(NSEvent*)event {
112  [historySwiper_ beginGestureWithEvent:event];
113}
114
115- (void)endGestureWithEvent:(NSEvent*)event {
116  [historySwiper_ endGestureWithEvent:event];
117}
118
119// This is a low level API which provides touches associated with an event.
120// It is used in conjunction with gestures to determine finger placement
121// on the trackpad.
122- (void)touchesMovedWithEvent:(NSEvent*)event {
123  [historySwiper_ touchesMovedWithEvent:event];
124}
125
126- (void)touchesBeganWithEvent:(NSEvent*)event {
127  [historySwiper_ touchesBeganWithEvent:event];
128}
129
130- (void)touchesCancelledWithEvent:(NSEvent*)event {
131  [historySwiper_ touchesCancelledWithEvent:event];
132}
133
134- (void)touchesEndedWithEvent:(NSEvent*)event {
135  [historySwiper_ touchesEndedWithEvent:event];
136}
137
138- (BOOL)canRubberbandLeft:(NSView*)view {
139  return [historySwiper_ canRubberbandLeft:view];
140}
141
142- (BOOL)canRubberbandRight:(NSView*)view {
143  return [historySwiper_ canRubberbandRight:view];
144}
145
146// HistorySwiperDelegate methods
147
148- (BOOL)shouldAllowHistorySwiping {
149  if (!renderWidgetHost_ || !renderWidgetHost_->IsRenderView())
150    return NO;
151  content::WebContents* webContents = content::WebContents::FromRenderViewHost(
152      RenderViewHost::From(renderWidgetHost_));
153  if (webContents && DevToolsWindow::IsDevToolsWindow(webContents)) {
154    return NO;
155  }
156
157  return YES;
158}
159
160- (NSView*)viewThatWantsHistoryOverlay {
161  return renderWidgetHost_->GetView()->GetNativeView();
162}
163
164- (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item
165                      isValidItem:(BOOL*)valid {
166  SEL action = [item action];
167
168  // For now, this action is always enabled for render view;
169  // this is sub-optimal.
170  // TODO(suzhe): Plumb the "can*" methods up from WebCore.
171  if (action == @selector(checkSpelling:)) {
172    *valid = renderWidgetHost_->IsRenderView();
173    return YES;
174  }
175
176  // TODO(groby): Clarify who sends this and if toggleContinuousSpellChecking:
177  // is still necessary.
178  if (action == @selector(toggleContinuousSpellChecking:)) {
179    if ([(id)item respondsToSelector:@selector(setState:)]) {
180      content::RenderProcessHost* host = renderWidgetHost_->GetProcess();
181      Profile* profile = Profile::FromBrowserContext(host->GetBrowserContext());
182      DCHECK(profile);
183      spellcheckChecked_ =
184          profile->GetPrefs()->GetBoolean(prefs::kEnableContinuousSpellcheck);
185      NSCellStateValue checkedState =
186          spellcheckChecked_ ? NSOnState : NSOffState;
187      [(id)item setState:checkedState];
188    }
189    *valid = spellcheckEnabled_;
190    return YES;
191  }
192
193  return NO;
194}
195
196- (void)rendererHandledWheelEvent:(const blink::WebMouseWheelEvent&)event
197                         consumed:(BOOL)consumed {
198  [historySwiper_ rendererHandledWheelEvent:event consumed:consumed];
199}
200
201// Spellchecking methods
202// The next five methods are implemented here since this class is the first
203// responder for anything in the browser.
204
205// This message is sent whenever the user specifies that a word should be
206// changed from the spellChecker.
207- (void)changeSpelling:(id)sender {
208  // Grab the currently selected word from the spell panel, as this is the word
209  // that we want to replace the selected word in the text with.
210  NSString* newWord = [[sender selectedCell] stringValue];
211  if (newWord != nil) {
212    content::WebContents* webContents =
213        content::WebContents::FromRenderViewHost(
214            RenderViewHost::From(renderWidgetHost_));
215    webContents->Replace(base::SysNSStringToUTF16(newWord));
216  }
217}
218
219// This message is sent by NSSpellChecker whenever the next word should be
220// advanced to, either after a correction or clicking the "Find Next" button.
221// This isn't documented anywhere useful, like in NSSpellProtocol.h with the
222// other spelling panel methods. This is probably because Apple assumes that the
223// the spelling panel will be used with an NSText, which will automatically
224// catch this and advance to the next word for you. Thanks Apple.
225// This is also called from the Edit -> Spelling -> Check Spelling menu item.
226- (void)checkSpelling:(id)sender {
227  renderWidgetHost_->Send(new SpellCheckMsg_AdvanceToNextMisspelling(
228      renderWidgetHost_->GetRoutingID()));
229}
230
231// This message is sent by the spelling panel whenever a word is ignored.
232- (void)ignoreSpelling:(id)sender {
233  // Ideally, we would ask the current RenderView for its tag, but that would
234  // mean making a blocking IPC call from the browser. Instead,
235  // spellcheck_mac::CheckSpelling remembers the last tag and
236  // spellcheck_mac::IgnoreWord assumes that is the correct tag.
237  NSString* wordToIgnore = [sender stringValue];
238  if (wordToIgnore != nil)
239    spellcheck_mac::IgnoreWord(base::SysNSStringToUTF16(wordToIgnore));
240}
241
242- (void)showGuessPanel:(id)sender {
243  renderWidgetHost_->Send(new SpellCheckMsg_ToggleSpellPanel(
244      renderWidgetHost_->GetRoutingID(),
245      spellcheck_mac::SpellingPanelVisible()));
246}
247
248- (void)toggleContinuousSpellChecking:(id)sender {
249  content::RenderProcessHost* host = renderWidgetHost_->GetProcess();
250  Profile* profile = Profile::FromBrowserContext(host->GetBrowserContext());
251  DCHECK(profile);
252  PrefService* pref = profile->GetPrefs();
253  pref->SetBoolean(prefs::kEnableContinuousSpellcheck,
254                   !pref->GetBoolean(prefs::kEnableContinuousSpellcheck));
255}
256
257- (void)spellCheckEnabled:(BOOL)enabled checked:(BOOL)checked {
258  spellcheckEnabled_ = enabled;
259  spellcheckChecked_ = checked;
260}
261
262// END Spellchecking methods
263
264@end
265