chrome_render_widget_host_view_mac_delegate.mm revision 0529e5d033099cbfc42635f6f6183833b09dff6e
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// Notification that a wheel event was unhandled.
110- (void)gotUnhandledWheelEvent {
111  [historySwiper_ gotUnhandledWheelEvent];
112}
113
114// Notification of scroll offset pinning.
115- (void)scrollOffsetPinnedToLeft:(BOOL)left toRight:(BOOL)right {
116  [historySwiper_ scrollOffsetPinnedToLeft:left toRight:right];
117}
118
119// Notification of whether the view has a horizontal scrollbar.
120- (void)setHasHorizontalScrollbar:(BOOL)has_horizontal_scrollbar {
121  [historySwiper_ setHasHorizontalScrollbar:has_horizontal_scrollbar];
122}
123
124// NSWindow events.
125
126- (void)beginGestureWithEvent:(NSEvent*)event {
127  [historySwiper_ beginGestureWithEvent:event];
128}
129
130- (void)endGestureWithEvent:(NSEvent*)event {
131  [historySwiper_ endGestureWithEvent:event];
132}
133
134// This is a low level API which provides touches associated with an event.
135// It is used in conjunction with gestures to determine finger placement
136// on the trackpad.
137- (void)touchesMovedWithEvent:(NSEvent*)event {
138  [historySwiper_ touchesMovedWithEvent:event];
139}
140
141- (void)touchesBeganWithEvent:(NSEvent*)event {
142  [historySwiper_ touchesBeganWithEvent:event];
143}
144
145- (void)touchesCancelledWithEvent:(NSEvent*)event {
146  [historySwiper_ touchesCancelledWithEvent:event];
147}
148
149- (void)touchesEndedWithEvent:(NSEvent*)event {
150  [historySwiper_ touchesEndedWithEvent:event];
151}
152
153- (BOOL)canRubberbandLeft:(NSView*)view {
154  return [historySwiper_ canRubberbandLeft:view];
155}
156
157- (BOOL)canRubberbandRight:(NSView*)view {
158  return [historySwiper_ canRubberbandRight:view];
159}
160
161// HistorySwiperDelegate methods
162
163- (BOOL)shouldAllowHistorySwiping {
164  if (!renderWidgetHost_ || !renderWidgetHost_->IsRenderView())
165    return NO;
166  if (DevToolsWindow::IsDevToolsWindow(
167      RenderViewHost::From(renderWidgetHost_))) {
168    return NO;
169  }
170
171  return YES;
172}
173
174- (NSView*)viewThatWantsHistoryOverlay {
175  return renderWidgetHost_->GetView()->GetNativeView();
176}
177
178- (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item
179                      isValidItem:(BOOL*)valid {
180  SEL action = [item action];
181
182  // For now, this action is always enabled for render view;
183  // this is sub-optimal.
184  // TODO(suzhe): Plumb the "can*" methods up from WebCore.
185  if (action == @selector(checkSpelling:)) {
186    *valid = renderWidgetHost_->IsRenderView();
187    return YES;
188  }
189
190  // TODO(groby): Clarify who sends this and if toggleContinuousSpellChecking:
191  // is still necessary.
192  if (action == @selector(toggleContinuousSpellChecking:)) {
193    if ([(id)item respondsToSelector:@selector(setState:)]) {
194      content::RenderProcessHost* host = renderWidgetHost_->GetProcess();
195      Profile* profile = Profile::FromBrowserContext(host->GetBrowserContext());
196      DCHECK(profile);
197      spellcheckChecked_ =
198          profile->GetPrefs()->GetBoolean(prefs::kEnableContinuousSpellcheck);
199      NSCellStateValue checkedState =
200          spellcheckChecked_ ? NSOnState : NSOffState;
201      [(id)item setState:checkedState];
202    }
203    *valid = spellcheckEnabled_;
204    return YES;
205  }
206
207  return NO;
208}
209
210// Spellchecking methods
211// The next five methods are implemented here since this class is the first
212// responder for anything in the browser.
213
214// This message is sent whenever the user specifies that a word should be
215// changed from the spellChecker.
216- (void)changeSpelling:(id)sender {
217  // Grab the currently selected word from the spell panel, as this is the word
218  // that we want to replace the selected word in the text with.
219  NSString* newWord = [[sender selectedCell] stringValue];
220  if (newWord != nil) {
221    content::WebContents* webContents =
222        content::WebContents::FromRenderViewHost(
223            RenderViewHost::From(renderWidgetHost_));
224    webContents->Replace(base::SysNSStringToUTF16(newWord));
225  }
226}
227
228// This message is sent by NSSpellChecker whenever the next word should be
229// advanced to, either after a correction or clicking the "Find Next" button.
230// This isn't documented anywhere useful, like in NSSpellProtocol.h with the
231// other spelling panel methods. This is probably because Apple assumes that the
232// the spelling panel will be used with an NSText, which will automatically
233// catch this and advance to the next word for you. Thanks Apple.
234// This is also called from the Edit -> Spelling -> Check Spelling menu item.
235- (void)checkSpelling:(id)sender {
236  renderWidgetHost_->Send(new SpellCheckMsg_AdvanceToNextMisspelling(
237      renderWidgetHost_->GetRoutingID()));
238}
239
240// This message is sent by the spelling panel whenever a word is ignored.
241- (void)ignoreSpelling:(id)sender {
242  // Ideally, we would ask the current RenderView for its tag, but that would
243  // mean making a blocking IPC call from the browser. Instead,
244  // spellcheck_mac::CheckSpelling remembers the last tag and
245  // spellcheck_mac::IgnoreWord assumes that is the correct tag.
246  NSString* wordToIgnore = [sender stringValue];
247  if (wordToIgnore != nil)
248    spellcheck_mac::IgnoreWord(base::SysNSStringToUTF16(wordToIgnore));
249}
250
251- (void)showGuessPanel:(id)sender {
252  renderWidgetHost_->Send(new SpellCheckMsg_ToggleSpellPanel(
253      renderWidgetHost_->GetRoutingID(),
254      spellcheck_mac::SpellingPanelVisible()));
255}
256
257- (void)toggleContinuousSpellChecking:(id)sender {
258  content::RenderProcessHost* host = renderWidgetHost_->GetProcess();
259  Profile* profile = Profile::FromBrowserContext(host->GetBrowserContext());
260  DCHECK(profile);
261  PrefService* pref = profile->GetPrefs();
262  pref->SetBoolean(prefs::kEnableContinuousSpellcheck,
263                   !pref->GetBoolean(prefs::kEnableContinuousSpellcheck));
264}
265
266- (void)spellCheckEnabled:(BOOL)enabled checked:(BOOL)checked {
267  spellcheckEnabled_ = enabled;
268  spellcheckChecked_ = checked;
269}
270
271// END Spellchecking methods
272
273@end
274