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 "WebViewWidget.h"
30
31#include "GOwnPtrGtk.h"
32#include "GtkVersioning.h"
33#include "NotImplemented.h"
34#include "RefPtrCairo.h"
35
36using namespace WebKit;
37using namespace WebCore;
38
39static gpointer webViewWidgetParentClass = 0;
40
41struct _WebViewWidgetPrivate {
42    WebView* webViewInstance;
43    GtkIMContext* imContext;
44    gint currentClickCount;
45    IntPoint previousClickPoint;
46    guint previousClickButton;
47    guint32 previousClickTime;
48};
49
50static void webViewWidgetRealize(GtkWidget* widget)
51{
52    gtk_widget_set_realized(widget, TRUE);
53
54    GtkAllocation allocation;
55    gtk_widget_get_allocation(widget, &allocation);
56
57    GdkWindowAttr attributes;
58    attributes.window_type = GDK_WINDOW_CHILD;
59    attributes.x = allocation.x;
60    attributes.y = allocation.y;
61    attributes.width = allocation.width;
62    attributes.height = allocation.height;
63    attributes.wclass = GDK_INPUT_OUTPUT;
64    attributes.visual = gtk_widget_get_visual(widget);
65#ifdef GTK_API_VERSION_2
66    attributes.colormap = gtk_widget_get_colormap(widget);
67#endif
68    attributes.event_mask = GDK_VISIBILITY_NOTIFY_MASK
69        | GDK_EXPOSURE_MASK
70        | GDK_BUTTON_PRESS_MASK
71        | GDK_BUTTON_RELEASE_MASK
72        | GDK_POINTER_MOTION_MASK
73        | GDK_KEY_PRESS_MASK
74        | GDK_KEY_RELEASE_MASK
75        | GDK_BUTTON_MOTION_MASK
76        | GDK_BUTTON1_MOTION_MASK
77        | GDK_BUTTON2_MOTION_MASK
78        | GDK_BUTTON3_MOTION_MASK;
79
80    gint attributesMask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL;
81#ifdef GTK_API_VERSION_2
82    attributesMask |= GDK_WA_COLORMAP;
83#endif
84    GdkWindow* window = gdk_window_new(gtk_widget_get_parent_window(widget), &attributes, attributesMask);
85    gtk_widget_set_window(widget, window);
86    gdk_window_set_user_data(window, widget);
87
88#ifdef GTK_API_VERSION_2
89#if GTK_CHECK_VERSION(2, 20, 0)
90    gtk_widget_style_attach(widget);
91#else
92    widget->style = gtk_style_attach(gtk_widget_get_style(widget), window);
93#endif
94    gtk_style_set_background(gtk_widget_get_style(widget), window, GTK_STATE_NORMAL);
95#else
96    gtk_style_context_set_background(gtk_widget_get_style_context(widget), window);
97#endif
98
99    WebViewWidget* webView = WEB_VIEW_WIDGET(widget);
100    WebViewWidgetPrivate* priv = webView->priv;
101    gtk_im_context_set_client_window(priv->imContext, window);
102}
103
104static void webViewWidgetContainerAdd(GtkContainer* container, GtkWidget* widget)
105{
106    gtk_widget_set_parent(widget, GTK_WIDGET(container));
107}
108
109static void webViewWidgetDispose(GObject* gobject)
110{
111    WebViewWidget* webViewWidget = WEB_VIEW_WIDGET(gobject);
112    WebViewWidgetPrivate* priv = webViewWidget->priv;
113
114    if (priv->imContext) {
115        g_object_unref(priv->imContext);
116        priv->imContext = 0;
117    }
118
119    G_OBJECT_CLASS(webViewWidgetParentClass)->dispose(gobject);
120}
121
122static void webViewWidgetInit(WebViewWidget* webViewWidget)
123{
124    WebViewWidgetPrivate* priv = G_TYPE_INSTANCE_GET_PRIVATE(webViewWidget, WEB_VIEW_TYPE_WIDGET, WebViewWidgetPrivate);
125    webViewWidget->priv = priv;
126
127    gtk_widget_set_can_focus(GTK_WIDGET(webViewWidget), TRUE);
128    priv->imContext = gtk_im_multicontext_new();
129
130    priv->currentClickCount = 0;
131    priv->previousClickButton = 0;
132    priv->previousClickTime = 0;
133}
134
135#ifdef GTK_API_VERSION_2
136static gboolean webViewExpose(GtkWidget* widget, GdkEventExpose* event)
137{
138    WebView* webView = webViewWidgetGetWebViewInstance(WEB_VIEW_WIDGET(widget));
139    GdkRectangle clipRect;
140    gdk_region_get_clipbox(event->region, &clipRect);
141
142    GdkWindow* window = gtk_widget_get_window(widget);
143    RefPtr<cairo_t> cr = adoptRef(gdk_cairo_create(window));
144
145    webView->paint(widget, clipRect, cr.get());
146
147    return FALSE;
148}
149#else
150static gboolean webViewDraw(GtkWidget* widget, cairo_t* cr)
151{
152    WebView* webView = webViewWidgetGetWebViewInstance(WEB_VIEW_WIDGET(widget));
153    GdkRectangle clipRect;
154
155    if (!gdk_cairo_get_clip_rectangle(cr, &clipRect))
156        return FALSE;
157
158    webView->paint(widget, clipRect, cr);
159
160    return FALSE;
161}
162#endif
163
164static void webViewSizeAllocate(GtkWidget* widget, GtkAllocation* allocation)
165{
166    WebView* webView = webViewWidgetGetWebViewInstance(WEB_VIEW_WIDGET(widget));
167    GTK_WIDGET_CLASS(webViewWidgetParentClass)->size_allocate(widget, allocation);
168    webView->setSize(widget, IntSize(allocation->width, allocation->height));
169}
170
171static gboolean webViewFocusInEvent(GtkWidget* widget, GdkEventFocus* event)
172{
173    WebViewWidget* webViewWidget = WEB_VIEW_WIDGET(widget);
174    WebView* webView = webViewWidgetGetWebViewInstance(webViewWidget);
175
176    GtkWidget* toplevel = gtk_widget_get_toplevel(widget);
177    if (gtk_widget_is_toplevel(toplevel) && gtk_window_has_toplevel_focus(GTK_WINDOW(toplevel))) {
178        gtk_im_context_focus_in(webViewWidgetGetIMContext(webViewWidget));
179        webView->handleFocusInEvent(widget);
180    }
181
182    return GTK_WIDGET_CLASS(webViewWidgetParentClass)->focus_in_event(widget, event);
183}
184
185static gboolean webViewFocusOutEvent(GtkWidget* widget, GdkEventFocus* event)
186{
187    WebViewWidget* webViewWidget = WEB_VIEW_WIDGET(widget);
188    WebView* webView = webViewWidgetGetWebViewInstance(webViewWidget);
189
190    webView->handleFocusOutEvent(widget);
191    GtkIMContext* imContext = webViewWidgetGetIMContext(webViewWidget);
192    if (imContext)
193        gtk_im_context_focus_out(imContext);
194
195    return GTK_WIDGET_CLASS(webViewWidgetParentClass)->focus_out_event(widget, event);
196}
197
198static gboolean webViewKeyPressEvent(GtkWidget* widget, GdkEventKey* event)
199{
200    WebView* webView = webViewWidgetGetWebViewInstance(WEB_VIEW_WIDGET(widget));
201    webView->handleKeyboardEvent(event);
202
203    return GTK_WIDGET_CLASS(webViewWidgetParentClass)->key_press_event(widget, event);
204}
205
206static gboolean webViewKeyReleaseEvent(GtkWidget* widget, GdkEventKey* event)
207{
208    WebViewWidget* webViewWidget = WEB_VIEW_WIDGET(widget);
209    WebView* webView = webViewWidgetGetWebViewInstance(webViewWidget);
210
211    if (gtk_im_context_filter_keypress(webViewWidgetGetIMContext(webViewWidget), event))
212        return TRUE;
213
214    webView->handleKeyboardEvent(event);
215
216    return GTK_WIDGET_CLASS(webViewWidgetParentClass)->key_release_event(widget, event);
217}
218
219// Copied from webkitwebview.cpp
220static guint32 getEventTime(GdkEvent* event)
221{
222    guint32 time = gdk_event_get_time(event);
223    if (time)
224        return time;
225
226    // Real events always have a non-zero time, but events synthesized
227    // by the DRT do not and we must calculate a time manually. This time
228    // is not calculated in the DRT, because GTK+ does not work well with
229    // anything other than GDK_CURRENT_TIME on synthesized events.
230    GTimeVal timeValue;
231    g_get_current_time(&timeValue);
232    return (timeValue.tv_sec * 1000) + (timeValue.tv_usec / 1000);
233}
234
235static gboolean webViewButtonPressEvent(GtkWidget* widget, GdkEventButton* buttonEvent)
236{
237    if (buttonEvent->button == 3) {
238        // FIXME: [GTK] Add context menu support for Webkit2.
239        // https://bugs.webkit.org/show_bug.cgi?id=54827
240        notImplemented();
241        return FALSE;
242    }
243
244    gtk_widget_grab_focus(widget);
245
246    // For double and triple clicks GDK sends both a normal button press event
247    // and a specific type (like GDK_2BUTTON_PRESS). If we detect a special press
248    // coming up, ignore this event as it certainly generated the double or triple
249    // click. The consequence of not eating this event is two DOM button press events
250    // are generated.
251    GOwnPtr<GdkEvent> nextEvent(gdk_event_peek());
252    if (nextEvent && (nextEvent->any.type == GDK_2BUTTON_PRESS || nextEvent->any.type == GDK_3BUTTON_PRESS))
253        return TRUE;
254
255    gint doubleClickDistance = 250;
256    gint doubleClickTime = 5;
257    GtkSettings* settings = gtk_settings_get_for_screen(gtk_widget_get_screen(widget));
258    g_object_get(settings,
259                 "gtk-double-click-distance", &doubleClickDistance,
260                 "gtk-double-click-time", &doubleClickTime, NULL);
261
262    // GTK+ only counts up to triple clicks, but WebCore wants to know about
263    // quadruple clicks, quintuple clicks, ad infinitum. Here, we replicate the
264    // GDK logic for counting clicks.
265    GdkEvent* event(reinterpret_cast<GdkEvent*>(buttonEvent));
266    guint32 eventTime = getEventTime(event);
267    WebViewWidget* webViewWidget = WEB_VIEW_WIDGET(widget);
268    WebViewWidgetPrivate* priv = webViewWidget->priv;
269    if ((event->type == GDK_2BUTTON_PRESS || event->type == GDK_3BUTTON_PRESS)
270        || ((abs(buttonEvent->x - priv->previousClickPoint.x()) < doubleClickDistance)
271            && (abs(buttonEvent->y - priv->previousClickPoint.y()) < doubleClickDistance)
272            && (eventTime - priv->previousClickTime < static_cast<guint>(doubleClickTime))
273            && (buttonEvent->button == priv->previousClickButton)))
274        priv->currentClickCount++;
275    else
276        priv->currentClickCount = 1;
277
278    WebView* webView = webViewWidgetGetWebViewInstance(webViewWidget);
279    webView->handleMouseEvent(event, priv->currentClickCount);
280
281    gdouble x, y;
282    gdk_event_get_coords(event, &x, &y);
283    priv->previousClickPoint = IntPoint(x, y);
284    priv->previousClickButton = buttonEvent->button;
285    priv->previousClickTime = eventTime;
286
287    return FALSE;
288}
289
290static gboolean webViewButtonReleaseEvent(GtkWidget* widget, GdkEventButton* event)
291{
292    WebView* webView = webViewWidgetGetWebViewInstance(WEB_VIEW_WIDGET(widget));
293    gtk_widget_grab_focus(widget);
294    webView->handleMouseEvent(reinterpret_cast<GdkEvent*>(event), 0 /* currentClickCount */);
295
296    return FALSE;
297}
298
299static gboolean webViewScrollEvent(GtkWidget* widget, GdkEventScroll* event)
300{
301    WebView* webView = webViewWidgetGetWebViewInstance(WEB_VIEW_WIDGET(widget));
302    webView->handleWheelEvent(event);
303
304    return FALSE;
305}
306
307static gboolean webViewMotionNotifyEvent(GtkWidget* widget, GdkEventMotion* event)
308{
309    WebView* webView = webViewWidgetGetWebViewInstance(WEB_VIEW_WIDGET(widget));
310    webView->handleMouseEvent(reinterpret_cast<GdkEvent*>(event), 0 /* currentClickCount */);
311
312    return FALSE;
313}
314
315static void webViewWidgetClassInit(WebViewWidgetClass* webViewWidgetClass)
316{
317    webViewWidgetParentClass = g_type_class_peek_parent(webViewWidgetClass);
318
319    GtkWidgetClass* widgetClass = GTK_WIDGET_CLASS(webViewWidgetClass);
320    widgetClass->realize = webViewWidgetRealize;
321#ifdef GTK_API_VERSION_2
322    widgetClass->expose_event = webViewExpose;
323#else
324    widgetClass->draw = webViewDraw;
325#endif
326    widgetClass->size_allocate = webViewSizeAllocate;
327    widgetClass->focus_in_event = webViewFocusInEvent;
328    widgetClass->focus_out_event = webViewFocusOutEvent;
329    widgetClass->key_press_event = webViewKeyPressEvent;
330    widgetClass->key_release_event = webViewKeyReleaseEvent;
331    widgetClass->button_press_event = webViewButtonPressEvent;
332    widgetClass->button_release_event = webViewButtonReleaseEvent;
333    widgetClass->scroll_event = webViewScrollEvent;
334    widgetClass->motion_notify_event = webViewMotionNotifyEvent;
335
336    GObjectClass* gobjectClass = G_OBJECT_CLASS(webViewWidgetClass);
337    gobjectClass->dispose = webViewWidgetDispose;
338
339    GtkContainerClass* containerClass = GTK_CONTAINER_CLASS(webViewWidgetClass);
340    containerClass->add = webViewWidgetContainerAdd;
341
342    g_type_class_add_private(webViewWidgetClass, sizeof(WebViewWidgetPrivate));
343}
344
345GType webViewWidgetGetType()
346{
347    static volatile gsize gDefineTypeIdVolatile = 0;
348
349    if (!g_once_init_enter(&gDefineTypeIdVolatile))
350        return gDefineTypeIdVolatile;
351
352    GType gDefineTypeId = g_type_register_static_simple(GTK_TYPE_CONTAINER,
353                                                        g_intern_static_string("WebViewWidget"),
354                                                        sizeof(WebViewWidgetClass),
355                                                        reinterpret_cast<GClassInitFunc>(webViewWidgetClassInit),
356                                                        sizeof(WebViewWidget),
357                                                        reinterpret_cast<GInstanceInitFunc>(webViewWidgetInit),
358                                                        static_cast<GTypeFlags>(0));
359    g_once_init_leave(&gDefineTypeIdVolatile, gDefineTypeId);
360
361    return gDefineTypeIdVolatile;
362}
363
364WebView* webViewWidgetGetWebViewInstance(WebViewWidget* webViewWidget)
365{
366    return webViewWidget->priv->webViewInstance;
367}
368
369void webViewWidgetSetWebViewInstance(WebViewWidget* webViewWidget, WebView* webViewInstance)
370{
371    webViewWidget->priv->webViewInstance = webViewInstance;
372}
373
374GtkIMContext* webViewWidgetGetIMContext(WebViewWidget* webViewWidget)
375{
376    return webViewWidget->priv->imContext;
377}
378