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 30 using content::RenderViewHost; 31 32 @interface ChromeRenderWidgetHostViewMacDelegate () <HistorySwiperDelegate> 33 - (void)spellCheckEnabled:(BOOL)enabled checked:(BOOL)checked; 34 @end 35 36 namespace ChromeRenderWidgetHostViewMacDelegateInternal { 37 38 // Filters the message sent by the renderer to know if spellchecking is enabled 39 // or not for the currently focused element. 40 class 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