1/*
2 * Copyright (C) 2010 Apple Inc. All rights reserved.
3 * Portions Copyright (c) 2010 Motorola Mobility, Inc.  All rights reserved.
4 * Copyright (C) 2011 Igalia S.L.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
17 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
19 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
20 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
21 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
22 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
23 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
24 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
25 * THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28#include "config.h"
29#include "WebView.h"
30
31#include "ChunkedUpdateDrawingAreaProxy.h"
32#include "NativeWebKeyboardEvent.h"
33#include "NativeWebMouseEvent.h"
34#include "NotImplemented.h"
35#include "WebContext.h"
36#include "WebContextMenuProxy.h"
37#include "WebEventFactory.h"
38#include "WebViewWidget.h"
39#include "WebPageProxy.h"
40#include <wtf/text/WTFString.h>
41
42typedef HashMap<int, const char*> IntConstCharHashMap;
43
44using namespace WebCore;
45
46namespace WebKit {
47
48void WebView::handleFocusInEvent(GtkWidget* widget)
49{
50    if (!(m_isPageActive)) {
51        m_isPageActive = true;
52        m_page->viewStateDidChange(WebPageProxy::ViewWindowIsActive);
53    }
54
55    m_page->viewStateDidChange(WebPageProxy::ViewIsFocused);
56}
57
58void WebView::handleFocusOutEvent(GtkWidget* widget)
59{
60    m_isPageActive = false;
61    m_page->viewStateDidChange(WebPageProxy::ViewWindowIsActive);
62}
63
64
65static void backspaceCallback(GtkWidget* widget, WebView* client)
66{
67    g_signal_stop_emission_by_name(widget, "backspace");
68    client->addPendingEditorCommand("DeleteBackward");
69}
70
71static void selectAllCallback(GtkWidget* widget, gboolean select, WebView* client)
72{
73    g_signal_stop_emission_by_name(widget, "select-all");
74    client->addPendingEditorCommand(select ? "SelectAll" : "Unselect");
75}
76
77static void cutClipboardCallback(GtkWidget* widget, WebView* client)
78{
79    g_signal_stop_emission_by_name(widget, "cut-clipboard");
80    client->addPendingEditorCommand("Cut");
81}
82
83static void copyClipboardCallback(GtkWidget* widget, WebView* client)
84{
85    g_signal_stop_emission_by_name(widget, "copy-clipboard");
86    client->addPendingEditorCommand("Copy");
87}
88
89static void pasteClipboardCallback(GtkWidget* widget, WebView* client)
90{
91    g_signal_stop_emission_by_name(widget, "paste-clipboard");
92    client->addPendingEditorCommand("Paste");
93}
94
95static void toggleOverwriteCallback(GtkWidget* widget, EditorClient*)
96{
97    // We don't support toggling the overwrite mode, but the default callback expects
98    // the GtkTextView to have a layout, so we handle this signal just to stop it.
99    g_signal_stop_emission_by_name(widget, "toggle-overwrite");
100}
101
102// GTK+ will still send these signals to the web view. So we can safely stop signal
103// emission without breaking accessibility.
104static void popupMenuCallback(GtkWidget* widget, EditorClient*)
105{
106    g_signal_stop_emission_by_name(widget, "popup-menu");
107}
108
109static void showHelpCallback(GtkWidget* widget, EditorClient*)
110{
111    g_signal_stop_emission_by_name(widget, "show-help");
112}
113
114static const char* const gtkDeleteCommands[][2] = {
115    { "DeleteBackward",               "DeleteForward"                        }, // Characters
116    { "DeleteWordBackward",           "DeleteWordForward"                    }, // Word ends
117    { "DeleteWordBackward",           "DeleteWordForward"                    }, // Words
118    { "DeleteToBeginningOfLine",      "DeleteToEndOfLine"                    }, // Lines
119    { "DeleteToBeginningOfLine",      "DeleteToEndOfLine"                    }, // Line ends
120    { "DeleteToBeginningOfParagraph", "DeleteToEndOfParagraph"               }, // Paragraph ends
121    { "DeleteToBeginningOfParagraph", "DeleteToEndOfParagraph"               }, // Paragraphs
122    { 0,                              0                                      } // Whitespace (M-\ in Emacs)
123};
124
125static void deleteFromCursorCallback(GtkWidget* widget, GtkDeleteType deleteType, gint count, WebView* client)
126{
127    g_signal_stop_emission_by_name(widget, "delete-from-cursor");
128    int direction = count > 0 ? 1 : 0;
129
130    // Ensuring that deleteType <= G_N_ELEMENTS here results in a compiler warning
131    // that the condition is always true.
132
133    if (deleteType == GTK_DELETE_WORDS) {
134        if (!direction) {
135            client->addPendingEditorCommand("MoveWordForward");
136            client->addPendingEditorCommand("MoveWordBackward");
137        } else {
138            client->addPendingEditorCommand("MoveWordBackward");
139            client->addPendingEditorCommand("MoveWordForward");
140        }
141    } else if (deleteType == GTK_DELETE_DISPLAY_LINES) {
142        if (!direction)
143            client->addPendingEditorCommand("MoveToBeginningOfLine");
144        else
145            client->addPendingEditorCommand("MoveToEndOfLine");
146    } else if (deleteType == GTK_DELETE_PARAGRAPHS) {
147        if (!direction)
148            client->addPendingEditorCommand("MoveToBeginningOfParagraph");
149        else
150            client->addPendingEditorCommand("MoveToEndOfParagraph");
151    }
152
153    const char* rawCommand = gtkDeleteCommands[deleteType][direction];
154    if (!rawCommand)
155      return;
156
157    for (int i = 0; i < abs(count); i++)
158        client->addPendingEditorCommand(rawCommand);
159}
160
161static const char* const gtkMoveCommands[][4] = {
162    { "MoveBackward",                                   "MoveForward",
163      "MoveBackwardAndModifySelection",                 "MoveForwardAndModifySelection"             }, // Forward/backward grapheme
164    { "MoveLeft",                                       "MoveRight",
165      "MoveBackwardAndModifySelection",                 "MoveForwardAndModifySelection"             }, // Left/right grapheme
166    { "MoveWordBackward",                               "MoveWordForward",
167      "MoveWordBackwardAndModifySelection",             "MoveWordForwardAndModifySelection"         }, // Forward/backward word
168    { "MoveUp",                                         "MoveDown",
169      "MoveUpAndModifySelection",                       "MoveDownAndModifySelection"                }, // Up/down line
170    { "MoveToBeginningOfLine",                          "MoveToEndOfLine",
171      "MoveToBeginningOfLineAndModifySelection",        "MoveToEndOfLineAndModifySelection"         }, // Up/down line ends
172    { "MoveParagraphForward",                           "MoveParagraphBackward",
173      "MoveParagraphForwardAndModifySelection",         "MoveParagraphBackwardAndModifySelection"   }, // Up/down paragraphs
174    { "MoveToBeginningOfParagraph",                     "MoveToEndOfParagraph",
175      "MoveToBeginningOfParagraphAndModifySelection",   "MoveToEndOfParagraphAndModifySelection"    }, // Up/down paragraph ends.
176    { "MovePageUp",                                     "MovePageDown",
177      "MovePageUpAndModifySelection",                   "MovePageDownAndModifySelection"            }, // Up/down page
178    { "MoveToBeginningOfDocument",                      "MoveToEndOfDocument",
179      "MoveToBeginningOfDocumentAndModifySelection",    "MoveToEndOfDocumentAndModifySelection"     }, // Begin/end of buffer
180    { 0,                                                0,
181      0,                                                0                                           } // Horizontal page movement
182};
183
184static void moveCursorCallback(GtkWidget* widget, GtkMovementStep step, gint count, gboolean extendSelection, WebView* client)
185{
186    g_signal_stop_emission_by_name(widget, "move-cursor");
187    int direction = count > 0 ? 1 : 0;
188    if (extendSelection)
189        direction += 2;
190
191    if (static_cast<unsigned>(step) >= G_N_ELEMENTS(gtkMoveCommands))
192        return;
193
194    const char* rawCommand = gtkMoveCommands[step][direction];
195    if (!rawCommand)
196        return;
197
198    for (int i = 0; i < abs(count); i++)
199        client->addPendingEditorCommand(rawCommand);
200}
201
202static const unsigned CtrlKey = 1 << 0;
203static const unsigned AltKey = 1 << 1;
204static const unsigned ShiftKey = 1 << 2;
205
206struct KeyDownEntry {
207    unsigned virtualKey;
208    unsigned modifiers;
209    const char* name;
210};
211
212struct KeyPressEntry {
213    unsigned charCode;
214    unsigned modifiers;
215    const char* name;
216};
217
218static const KeyDownEntry keyDownEntries[] = {
219    { 'B',       CtrlKey,            "ToggleBold"                                  },
220    { 'I',       CtrlKey,            "ToggleItalic"                                },
221    { VK_ESCAPE, 0,                  "Cancel"                                      },
222    { VK_OEM_PERIOD, CtrlKey,        "Cancel"                                      },
223    { VK_TAB,    0,                  "InsertTab"                                   },
224    { VK_TAB,    ShiftKey,           "InsertBacktab"                               },
225    { VK_RETURN, 0,                  "InsertNewline"                               },
226    { VK_RETURN, CtrlKey,            "InsertNewline"                               },
227    { VK_RETURN, AltKey,             "InsertNewline"                               },
228    { VK_RETURN, AltKey | ShiftKey,  "InsertNewline"                               },
229};
230
231static const KeyPressEntry keyPressEntries[] = {
232    { '\t',   0,                  "InsertTab"                                   },
233    { '\t',   ShiftKey,           "InsertBacktab"                               },
234    { '\r',   0,                  "InsertNewline"                               },
235    { '\r',   CtrlKey,            "InsertNewline"                               },
236    { '\r',   AltKey,             "InsertNewline"                               },
237    { '\r',   AltKey | ShiftKey,  "InsertNewline"                               },
238};
239
240WebView::WebView(WebContext* context, WebPageGroup* pageGroup)
241    : m_isPageActive(true)
242    , m_nativeWidget(gtk_text_view_new())
243{
244    m_page = context->createWebPage(this, pageGroup);
245
246    m_viewWidget = static_cast<GtkWidget*>(g_object_new(WEB_VIEW_TYPE_WIDGET, NULL));
247    ASSERT(m_viewWidget);
248
249    m_page->initializeWebPage();
250
251    WebViewWidget* webViewWidget = WEB_VIEW_WIDGET(m_viewWidget);
252    webViewWidgetSetWebViewInstance(webViewWidget, this);
253
254    g_signal_connect(m_nativeWidget.get(), "backspace", G_CALLBACK(backspaceCallback), this);
255    g_signal_connect(m_nativeWidget.get(), "cut-clipboard", G_CALLBACK(cutClipboardCallback), this);
256    g_signal_connect(m_nativeWidget.get(), "copy-clipboard", G_CALLBACK(copyClipboardCallback), this);
257    g_signal_connect(m_nativeWidget.get(), "paste-clipboard", G_CALLBACK(pasteClipboardCallback), this);
258    g_signal_connect(m_nativeWidget.get(), "select-all", G_CALLBACK(selectAllCallback), this);
259    g_signal_connect(m_nativeWidget.get(), "move-cursor", G_CALLBACK(moveCursorCallback), this);
260    g_signal_connect(m_nativeWidget.get(), "delete-from-cursor", G_CALLBACK(deleteFromCursorCallback), this);
261    g_signal_connect(m_nativeWidget.get(), "toggle-overwrite", G_CALLBACK(toggleOverwriteCallback), this);
262    g_signal_connect(m_nativeWidget.get(), "popup-menu", G_CALLBACK(popupMenuCallback), this);
263    g_signal_connect(m_nativeWidget.get(), "show-help", G_CALLBACK(showHelpCallback), this);
264}
265
266WebView::~WebView()
267{
268}
269
270GdkWindow* WebView::getWebViewWindow()
271{
272    return gtk_widget_get_window(m_viewWidget);
273}
274
275void WebView::paint(GtkWidget* widget, GdkRectangle rect, cairo_t* cr)
276{
277    m_page->drawingArea()->paint(IntRect(rect), cr);
278}
279
280void WebView::setSize(GtkWidget*, IntSize windowSize)
281{
282    m_page->drawingArea()->setSize(windowSize, IntSize());
283}
284
285void WebView::handleKeyboardEvent(GdkEventKey* event)
286{
287    m_page->handleKeyboardEvent(NativeWebKeyboardEvent(reinterpret_cast<GdkEvent*>(event)));
288}
289
290void WebView::handleMouseEvent(GdkEvent* event, int currentClickCount)
291{
292    m_page->handleMouseEvent(NativeWebMouseEvent(event, currentClickCount));
293}
294
295void WebView::handleWheelEvent(GdkEventScroll* event)
296{
297    m_page->handleWheelEvent(WebEventFactory::createWebWheelEvent(event));
298}
299
300void WebView::getEditorCommandsForKeyEvent(const NativeWebKeyboardEvent& event, Vector<WTF::String>& commandList)
301{
302    m_pendingEditorCommands.clear();
303
304#ifdef GTK_API_VERSION_2
305    gtk_bindings_activate_event(GTK_OBJECT(m_nativeWidget.get()), const_cast<GdkEventKey*>(&event.nativeEvent()->key));
306#else
307    gtk_bindings_activate_event(G_OBJECT(m_nativeWidget.get()), const_cast<GdkEventKey*>(&event.nativeEvent()->key));
308#endif
309
310    if (m_pendingEditorCommands.isEmpty()) {
311        commandList.append(m_pendingEditorCommands);
312        return;
313    }
314
315    DEFINE_STATIC_LOCAL(IntConstCharHashMap, keyDownCommandsMap, ());
316    DEFINE_STATIC_LOCAL(IntConstCharHashMap, keyPressCommandsMap, ());
317
318    if (keyDownCommandsMap.isEmpty()) {
319        for (unsigned i = 0; i < G_N_ELEMENTS(keyDownEntries); i++)
320            keyDownCommandsMap.set(keyDownEntries[i].modifiers << 16 | keyDownEntries[i].virtualKey, keyDownEntries[i].name);
321
322        for (unsigned i = 0; i < G_N_ELEMENTS(keyPressEntries); i++)
323            keyPressCommandsMap.set(keyPressEntries[i].modifiers << 16 | keyPressEntries[i].charCode, keyPressEntries[i].name);
324    }
325
326    unsigned modifiers = 0;
327    if (event.shiftKey())
328        modifiers |= ShiftKey;
329    if (event.altKey())
330        modifiers |= AltKey;
331    if (event.controlKey())
332        modifiers |= CtrlKey;
333
334    // For keypress events, we want charCode(), but keyCode() does that.
335    int mapKey = modifiers << 16 | event.nativeVirtualKeyCode();
336    if (mapKey) {
337        HashMap<int, const char*>* commandMap = event.type() == WebEvent::KeyDown ?
338            &keyDownCommandsMap : &keyPressCommandsMap;
339        if (const char* commandString = commandMap->get(mapKey))
340            m_pendingEditorCommands.append(commandString);
341    }
342
343    commandList.append(m_pendingEditorCommands);
344}
345
346bool WebView::isActive()
347{
348    return m_isPageActive;
349}
350
351void WebView::close()
352{
353    m_page->close();
354}
355
356// PageClient's pure virtual functions
357PassOwnPtr<DrawingAreaProxy> WebView::createDrawingAreaProxy()
358{
359    return ChunkedUpdateDrawingAreaProxy::create(this, m_page.get());
360}
361
362void WebView::setViewNeedsDisplay(const WebCore::IntRect&)
363{
364    notImplemented();
365}
366
367void WebView::displayView()
368{
369    notImplemented();
370}
371
372void WebView::scrollView(const WebCore::IntRect& scrollRect, const WebCore::IntSize& scrollOffset)
373{
374    notImplemented();
375}
376
377WebCore::IntSize WebView::viewSize()
378{
379    GtkAllocation allocation;
380    gtk_widget_get_allocation(m_viewWidget, &allocation);
381    return IntSize(allocation.width, allocation.height);
382}
383
384bool WebView::isViewWindowActive()
385{
386    notImplemented();
387    return true;
388}
389
390bool WebView::isViewFocused()
391{
392    notImplemented();
393    return true;
394}
395
396bool WebView::isViewVisible()
397{
398    notImplemented();
399    return true;
400}
401
402bool WebView::isViewInWindow()
403{
404    notImplemented();
405    return true;
406}
407
408void WebView::WebView::processDidCrash()
409{
410    notImplemented();
411}
412
413void WebView::didRelaunchProcess()
414{
415    notImplemented();
416}
417
418void WebView::takeFocus(bool)
419{
420    notImplemented();
421}
422
423void WebView::toolTipChanged(const String&, const String&)
424{
425    notImplemented();
426}
427
428void WebView::setCursor(const Cursor& cursor)
429{
430    // [GTK] Widget::setCursor() gets called frequently
431    // http://bugs.webkit.org/show_bug.cgi?id=16388
432    // Setting the cursor may be an expensive operation in some backends,
433    // so don't re-set the cursor if it's already set to the target value.
434    GdkWindow* window = gtk_widget_get_window(m_viewWidget);
435    GdkCursor* currentCursor = gdk_window_get_cursor(window);
436    GdkCursor* newCursor = cursor.platformCursor().get();
437    if (currentCursor != newCursor)
438        gdk_window_set_cursor(window, newCursor);
439}
440
441void WebView::setViewportArguments(const WebCore::ViewportArguments&)
442{
443    notImplemented();
444}
445
446void WebView::registerEditCommand(PassRefPtr<WebEditCommandProxy>, WebPageProxy::UndoOrRedo)
447{
448    notImplemented();
449}
450
451void WebView::clearAllEditCommands()
452{
453    notImplemented();
454}
455
456bool WebView::canUndoRedo(WebPageProxy::UndoOrRedo)
457{
458    notImplemented();
459    return false;
460}
461
462void WebView::executeUndoRedo(WebPageProxy::UndoOrRedo)
463{
464    notImplemented();
465}
466
467FloatRect WebView::convertToDeviceSpace(const FloatRect& viewRect)
468{
469    notImplemented();
470    return viewRect;
471}
472
473FloatRect WebView::convertToUserSpace(const FloatRect& viewRect)
474{
475    notImplemented();
476    return viewRect;
477}
478
479IntRect WebView::windowToScreen(const IntRect& rect)
480{
481    notImplemented();
482    return IntRect();
483}
484
485void WebView::doneWithKeyEvent(const NativeWebKeyboardEvent&, bool wasEventHandled)
486{
487    notImplemented();
488}
489
490void WebView::didNotHandleKeyEvent(const NativeWebKeyboardEvent& event)
491{
492    notImplemented();
493}
494
495PassRefPtr<WebPopupMenuProxy> WebView::createPopupMenuProxy(WebPageProxy*)
496{
497    notImplemented();
498    return 0;
499}
500
501PassRefPtr<WebContextMenuProxy> WebView::createContextMenuProxy(WebPageProxy*)
502{
503    notImplemented();
504    return 0;
505}
506
507void WebView::setFindIndicator(PassRefPtr<FindIndicator>, bool fadeOut)
508{
509    notImplemented();
510}
511
512#if USE(ACCELERATED_COMPOSITING)
513void WebView::pageDidEnterAcceleratedCompositing()
514{
515    notImplemented();
516}
517
518void WebView::pageDidLeaveAcceleratedCompositing()
519{
520    notImplemented();
521}
522#endif // USE(ACCELERATED_COMPOSITING)
523
524void WebView::didCommitLoadForMainFrame(bool useCustomRepresentation)
525{
526}
527
528void WebView::didFinishLoadingDataForCustomRepresentation(const String& suggestedFilename, const CoreIPC::DataReference&)
529{
530}
531
532double WebView::customRepresentationZoomFactor()
533{
534    notImplemented();
535    return 0;
536}
537
538void WebView::setCustomRepresentationZoomFactor(double)
539{
540    notImplemented();
541}
542
543void WebView::pageClosed()
544{
545    notImplemented();
546}
547
548void WebView::didChangeScrollbarsForMainFrame() const
549{
550}
551
552void WebView::flashBackingStoreUpdates(const Vector<IntRect>&)
553{
554    notImplemented();
555}
556
557void WebView::findStringInCustomRepresentation(const String&, FindOptions, unsigned)
558{
559    notImplemented();
560}
561
562void WebView::countStringMatchesInCustomRepresentation(const String&, FindOptions, unsigned)
563{
564    notImplemented();
565}
566
567} // namespace WebKit
568