- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / browser / renderer_host / chrome_render_widget_host_view_mac_delegate.mm
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 #import "base/mac/sdk_forward_declarations.h"
10 #include "base/prefs/pref_service.h"
11 #include "base/strings/sys_string_conversions.h"
12 #include "chrome/browser/devtools/devtools_window.h"
13 #include "chrome/browser/profiles/profile.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/history_overlay_controller.h"
20 #import "chrome/browser/ui/cocoa/view_id_util.h"
21 #include "chrome/common/pref_names.h"
22 #include "chrome/common/spellcheck_messages.h"
23 #include "chrome/common/url_constants.h"
24 #include "content/public/browser/render_process_host.h"
25 #include "content/public/browser/render_view_host.h"
26 #include "content/public/browser/render_widget_host.h"
27 #include "content/public/browser/render_widget_host_view.h"
28 #include "content/public/browser/web_contents.h"
29 #include "content/public/browser/web_contents_observer.h"
30
31 using content::RenderViewHost;
32
33 @interface ChromeRenderWidgetHostViewMacDelegate ()
34 - (BOOL)maybeHandleHistorySwiping:(NSEvent*)theEvent;
35 - (void)spellCheckEnabled:(BOOL)enabled checked:(BOOL)checked;
36 @end
37
38 namespace ChromeRenderWidgetHostViewMacDelegateInternal {
39
40 // Filters the message sent by the renderer to know if spellchecking is enabled
41 // or not for the currently focused element.
42 class SpellCheckObserver : public content::WebContentsObserver {
43  public:
44   SpellCheckObserver(
45       RenderViewHost* host,
46       ChromeRenderWidgetHostViewMacDelegate* view_delegate)
47       : content::WebContentsObserver(
48             content::WebContents::FromRenderViewHost(host)),
49         view_delegate_(view_delegate) {
50   }
51
52   virtual ~SpellCheckObserver() {
53   }
54
55  private:
56   virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE {
57     bool handled = true;
58     IPC_BEGIN_MESSAGE_MAP(SpellCheckObserver, message)
59       IPC_MESSAGE_HANDLER(SpellCheckHostMsg_ToggleSpellCheck,
60                           OnToggleSpellCheck)
61       IPC_MESSAGE_UNHANDLED(handled = false)
62     IPC_END_MESSAGE_MAP()
63     return handled;
64   }
65
66   void OnToggleSpellCheck(bool enabled, bool checked) {
67     [view_delegate_ spellCheckEnabled:enabled checked:checked];
68   }
69
70   ChromeRenderWidgetHostViewMacDelegate* view_delegate_;
71 };
72
73 }  // namespace ChromeRenderWidgetHostViewMacDelegateInternal
74
75 @implementation ChromeRenderWidgetHostViewMacDelegate
76
77 - (id)initWithRenderWidgetHost:(content::RenderWidgetHost*)renderWidgetHost {
78   self = [super init];
79   if (self) {
80     renderWidgetHost_ = renderWidgetHost;
81     NSView* nativeView = renderWidgetHost_->GetView()->GetNativeView();
82     view_id_util::SetID(nativeView, VIEW_ID_TAB_CONTAINER);
83
84     if (renderWidgetHost_->IsRenderView()) {
85       spellingObserver_.reset(
86           new ChromeRenderWidgetHostViewMacDelegateInternal::SpellCheckObserver(
87               RenderViewHost::From(renderWidgetHost_), self));
88     }
89   }
90   return self;
91 }
92
93 - (void)viewGone:(NSView*)view {
94   view_id_util::UnsetID(view);
95   [self autorelease];
96 }
97
98 - (BOOL)handleEvent:(NSEvent*)event {
99   if ([event type] == NSScrollWheel)
100     return [self maybeHandleHistorySwiping:event];
101
102   return NO;
103 }
104
105 - (void)gotUnhandledWheelEvent {
106   gotUnhandledWheelEvent_ = YES;
107 }
108
109 - (void)scrollOffsetPinnedToLeft:(BOOL)left toRight:(BOOL)right {
110   isPinnedLeft_ = left;
111   isPinnedRight_ = right;
112 }
113
114 - (void)setHasHorizontalScrollbar:(BOOL)hasHorizontalScrollbar {
115   hasHorizontalScrollbar_ = hasHorizontalScrollbar;
116 }
117
118 // Checks if |theEvent| should trigger history swiping, and if so, does
119 // history swiping. Returns YES if the event was consumed or NO if it should
120 // be passed on to the renderer.
121 - (BOOL)maybeHandleHistorySwiping:(NSEvent*)theEvent {
122   BOOL canUseLionApis = [theEvent respondsToSelector:@selector(phase)];
123   if (!canUseLionApis)
124     return NO;
125
126   // Scroll events always go to the web first, and can only trigger history
127   // swiping if they come back unhandled.
128   if ([theEvent phase] == NSEventPhaseBegan) {
129     totalScrollDelta_ = NSZeroSize;
130     gotUnhandledWheelEvent_ = NO;
131   }
132
133   if (!renderWidgetHost_ || !renderWidgetHost_->IsRenderView())
134     return NO;
135   if (DevToolsWindow::IsDevToolsWindow(
136           RenderViewHost::From(renderWidgetHost_))) {
137     return NO;
138   }
139
140   if (gotUnhandledWheelEvent_ &&
141       [NSEvent isSwipeTrackingFromScrollEventsEnabled] &&
142       [theEvent phase] == NSEventPhaseChanged) {
143     Browser* browser = chrome::FindBrowserWithWindow([theEvent window]);
144     totalScrollDelta_.width += [theEvent scrollingDeltaX];
145     totalScrollDelta_.height += [theEvent scrollingDeltaY];
146
147     bool isHorizontalGesture =
148       std::abs(totalScrollDelta_.width) > std::abs(totalScrollDelta_.height);
149
150     bool isRightScroll = [theEvent scrollingDeltaX] < 0;
151     bool goForward = isRightScroll;
152     bool canGoBack = false, canGoForward = false;
153     if (browser) {
154       canGoBack = chrome::CanGoBack(browser);
155       canGoForward = chrome::CanGoForward(browser);
156     }
157
158     // If "forward" is inactive and the user rubber-bands to the right,
159     // "isPinnedLeft" will be false.  When the user then rubber-bands to the
160     // left in the same gesture, that should trigger history immediately if
161     // there's no scrollbar, hence the check for hasHorizontalScrollbar_.
162     bool shouldGoBack = isPinnedLeft_ || !hasHorizontalScrollbar_;
163     bool shouldGoForward = isPinnedRight_ || !hasHorizontalScrollbar_;
164     if (isHorizontalGesture &&
165         // For normal pages, canGoBack/canGoForward are checked in the renderer
166         // (ChromeClientImpl::shouldRubberBand()), when it decides if it should
167         // rubberband or send back an event unhandled. The check here is
168         // required for pages with an onmousewheel handler that doesn't call
169         // preventDefault().
170         ((shouldGoBack && canGoBack && !isRightScroll) ||
171          (shouldGoForward && canGoForward && isRightScroll))) {
172
173       // Released by the tracking handler once the gesture is complete.
174       HistoryOverlayController* historyOverlay =
175           [[HistoryOverlayController alloc]
176               initForMode:goForward ? kHistoryOverlayModeForward :
177                                       kHistoryOverlayModeBack];
178
179       // The way this API works: gestureAmount is between -1 and 1 (float).  If
180       // the user does the gesture for more than about 25% (i.e. < -0.25 or >
181       // 0.25) and then lets go, it is accepted, we get a NSEventPhaseEnded,
182       // and after that the block is called with amounts animating towards 1
183       // (or -1, depending on the direction).  If the user lets go below that
184       // threshold, we get NSEventPhaseCancelled, and the amount animates
185       // toward 0.  When gestureAmount has reaches its final value, i.e. the
186       // track animation is done, the handler is called with |isComplete| set
187       // to |YES|.
188       // When starting a backwards navigation gesture (swipe from left to right,
189       // gestureAmount will go from 0 to 1), if the user swipes from left to
190       // right and then quickly back to the left, this call can send
191       // NSEventPhaseEnded and then animate to gestureAmount of -1. For a
192       // picture viewer, that makes sense, but for back/forward navigation users
193       // find it confusing. There are two ways to prevent this:
194       // 1. Set Options to NSEventSwipeTrackingLockDirection. This way,
195       //    gestureAmount will always stay > 0.
196       // 2. Pass min:0 max:1 (instead of min:-1 max:1). This way, gestureAmount
197       //    will become less than 0, but on the quick swipe back to the left,
198       //    NSEventPhaseCancelled is sent instead.
199       // The current UI looks nicer with (1) so that swiping the opposite
200       // direction after the initial swipe doesn't cause the shield to move
201       // in the wrong direction.
202       [theEvent trackSwipeEventWithOptions:NSEventSwipeTrackingLockDirection
203                   dampenAmountThresholdMin:-1
204                                        max:1
205                               usingHandler:^(CGFloat gestureAmount,
206                                              NSEventPhase phase,
207                                              BOOL isComplete,
208                                              BOOL *stop) {
209           if (phase == NSEventPhaseBegan) {
210             [historyOverlay showPanelForView:
211                 renderWidgetHost_->GetView()->GetNativeView()];
212             return;
213           }
214
215           BOOL ended = phase == NSEventPhaseEnded;
216
217           // Dismiss the panel before navigation for immediate visual feedback.
218           [historyOverlay setProgress:gestureAmount];
219           if (ended)
220             [historyOverlay dismiss];
221
222           // |gestureAmount| obeys -[NSEvent isDirectionInvertedFromDevice]
223           // automatically.
224           Browser* browser = chrome::FindBrowserWithWindow(
225               historyOverlay.view.window);
226           if (ended && browser) {
227             if (goForward)
228               chrome::GoForward(browser, CURRENT_TAB);
229             else
230               chrome::GoBack(browser, CURRENT_TAB);
231           }
232
233           if (isComplete)
234             [historyOverlay release];
235         }];
236       return YES;
237     }
238   }
239   return NO;
240 }
241
242 - (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item
243                       isValidItem:(BOOL*)valid {
244   SEL action = [item action];
245
246   // For now, this action is always enabled for render view;
247   // this is sub-optimal.
248   // TODO(suzhe): Plumb the "can*" methods up from WebCore.
249   if (action == @selector(checkSpelling:)) {
250     *valid = renderWidgetHost_->IsRenderView();
251     return YES;
252   }
253
254   // TODO(groby): Clarify who sends this and if toggleContinuousSpellChecking:
255   // is still necessary.
256   if (action == @selector(toggleContinuousSpellChecking:)) {
257     if ([(id)item respondsToSelector:@selector(setState:)]) {
258       content::RenderProcessHost* host = renderWidgetHost_->GetProcess();
259       Profile* profile = Profile::FromBrowserContext(host->GetBrowserContext());
260       DCHECK(profile);
261       spellcheckChecked_ =
262           profile->GetPrefs()->GetBoolean(prefs::kEnableContinuousSpellcheck);
263       NSCellStateValue checkedState =
264           spellcheckChecked_ ? NSOnState : NSOffState;
265       [(id)item setState:checkedState];
266     }
267     *valid = spellcheckEnabled_;
268     return YES;
269   }
270
271   return NO;
272 }
273
274 // Spellchecking methods
275 // The next five methods are implemented here since this class is the first
276 // responder for anything in the browser.
277
278 // This message is sent whenever the user specifies that a word should be
279 // changed from the spellChecker.
280 - (void)changeSpelling:(id)sender {
281   // Grab the currently selected word from the spell panel, as this is the word
282   // that we want to replace the selected word in the text with.
283   NSString* newWord = [[sender selectedCell] stringValue];
284   if (newWord != nil) {
285     renderWidgetHost_->Replace(base::SysNSStringToUTF16(newWord));
286   }
287 }
288
289 // This message is sent by NSSpellChecker whenever the next word should be
290 // advanced to, either after a correction or clicking the "Find Next" button.
291 // This isn't documented anywhere useful, like in NSSpellProtocol.h with the
292 // other spelling panel methods. This is probably because Apple assumes that the
293 // the spelling panel will be used with an NSText, which will automatically
294 // catch this and advance to the next word for you. Thanks Apple.
295 // This is also called from the Edit -> Spelling -> Check Spelling menu item.
296 - (void)checkSpelling:(id)sender {
297   renderWidgetHost_->Send(new SpellCheckMsg_AdvanceToNextMisspelling(
298       renderWidgetHost_->GetRoutingID()));
299 }
300
301 // This message is sent by the spelling panel whenever a word is ignored.
302 - (void)ignoreSpelling:(id)sender {
303   // Ideally, we would ask the current RenderView for its tag, but that would
304   // mean making a blocking IPC call from the browser. Instead,
305   // spellcheck_mac::CheckSpelling remembers the last tag and
306   // spellcheck_mac::IgnoreWord assumes that is the correct tag.
307   NSString* wordToIgnore = [sender stringValue];
308   if (wordToIgnore != nil)
309     spellcheck_mac::IgnoreWord(base::SysNSStringToUTF16(wordToIgnore));
310 }
311
312 - (void)showGuessPanel:(id)sender {
313   renderWidgetHost_->Send(new SpellCheckMsg_ToggleSpellPanel(
314       renderWidgetHost_->GetRoutingID(),
315       spellcheck_mac::SpellingPanelVisible()));
316 }
317
318 - (void)toggleContinuousSpellChecking:(id)sender {
319   content::RenderProcessHost* host = renderWidgetHost_->GetProcess();
320   Profile* profile = Profile::FromBrowserContext(host->GetBrowserContext());
321   DCHECK(profile);
322   PrefService* pref = profile->GetPrefs();
323   pref->SetBoolean(prefs::kEnableContinuousSpellcheck,
324                    !pref->GetBoolean(prefs::kEnableContinuousSpellcheck));
325 }
326
327 - (void)spellCheckEnabled:(BOOL)enabled checked:(BOOL)checked {
328   spellcheckEnabled_ = enabled;
329   spellcheckChecked_ = checked;
330 }
331
332 // END Spellchecking methods
333
334 @end