/*
* Copyright (C) 2010 Apple Inc. All rights reserved.
* Portions Copyright (c) 2010 Motorola Mobility, Inc. All rights reserved.
* Copyright (C) 2011 Igalia S.L.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "WebViewWidget.h"
#include "GOwnPtrGtk.h"
#include "GtkVersioning.h"
#include "NotImplemented.h"
#include "RefPtrCairo.h"
using namespace WebKit;
using namespace WebCore;
static gpointer webViewWidgetParentClass = 0;
struct _WebViewWidgetPrivate {
WebView* webViewInstance;
GtkIMContext* imContext;
gint currentClickCount;
IntPoint previousClickPoint;
guint previousClickButton;
guint32 previousClickTime;
};
static void webViewWidgetRealize(GtkWidget* widget)
{
gtk_widget_set_realized(widget, TRUE);
GtkAllocation allocation;
gtk_widget_get_allocation(widget, &allocation);
GdkWindowAttr attributes;
attributes.window_type = GDK_WINDOW_CHILD;
attributes.x = allocation.x;
attributes.y = allocation.y;
attributes.width = allocation.width;
attributes.height = allocation.height;
attributes.wclass = GDK_INPUT_OUTPUT;
attributes.visual = gtk_widget_get_visual(widget);
#ifdef GTK_API_VERSION_2
attributes.colormap = gtk_widget_get_colormap(widget);
#endif
attributes.event_mask = GDK_VISIBILITY_NOTIFY_MASK
| GDK_EXPOSURE_MASK
| GDK_BUTTON_PRESS_MASK
| GDK_BUTTON_RELEASE_MASK
| GDK_POINTER_MOTION_MASK
| GDK_KEY_PRESS_MASK
| GDK_KEY_RELEASE_MASK
| GDK_BUTTON_MOTION_MASK
| GDK_BUTTON1_MOTION_MASK
| GDK_BUTTON2_MOTION_MASK
| GDK_BUTTON3_MOTION_MASK;
gint attributesMask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL;
#ifdef GTK_API_VERSION_2
attributesMask |= GDK_WA_COLORMAP;
#endif
GdkWindow* window = gdk_window_new(gtk_widget_get_parent_window(widget), &attributes, attributesMask);
gtk_widget_set_window(widget, window);
gdk_window_set_user_data(window, widget);
#ifdef GTK_API_VERSION_2
#if GTK_CHECK_VERSION(2, 20, 0)
gtk_widget_style_attach(widget);
#else
widget->style = gtk_style_attach(gtk_widget_get_style(widget), window);
#endif
gtk_style_set_background(gtk_widget_get_style(widget), window, GTK_STATE_NORMAL);
#else
gtk_style_context_set_background(gtk_widget_get_style_context(widget), window);
#endif
WebViewWidget* webView = WEB_VIEW_WIDGET(widget);
WebViewWidgetPrivate* priv = webView->priv;
gtk_im_context_set_client_window(priv->imContext, window);
}
static void webViewWidgetContainerAdd(GtkContainer* container, GtkWidget* widget)
{
gtk_widget_set_parent(widget, GTK_WIDGET(container));
}
static void webViewWidgetDispose(GObject* gobject)
{
WebViewWidget* webViewWidget = WEB_VIEW_WIDGET(gobject);
WebViewWidgetPrivate* priv = webViewWidget->priv;
if (priv->imContext) {
g_object_unref(priv->imContext);
priv->imContext = 0;
}
G_OBJECT_CLASS(webViewWidgetParentClass)->dispose(gobject);
}
static void webViewWidgetInit(WebViewWidget* webViewWidget)
{
WebViewWidgetPrivate* priv = G_TYPE_INSTANCE_GET_PRIVATE(webViewWidget, WEB_VIEW_TYPE_WIDGET, WebViewWidgetPrivate);
webViewWidget->priv = priv;
gtk_widget_set_can_focus(GTK_WIDGET(webViewWidget), TRUE);
priv->imContext = gtk_im_multicontext_new();
priv->currentClickCount = 0;
priv->previousClickButton = 0;
priv->previousClickTime = 0;
}
#ifdef GTK_API_VERSION_2
static gboolean webViewExpose(GtkWidget* widget, GdkEventExpose* event)
{
WebView* webView = webViewWidgetGetWebViewInstance(WEB_VIEW_WIDGET(widget));
GdkRectangle clipRect;
gdk_region_get_clipbox(event->region, &clipRect);
GdkWindow* window = gtk_widget_get_window(widget);
RefPtr<cairo_t> cr = adoptRef(gdk_cairo_create(window));
webView->paint(widget, clipRect, cr.get());
return FALSE;
}
#else
static gboolean webViewDraw(GtkWidget* widget, cairo_t* cr)
{
WebView* webView = webViewWidgetGetWebViewInstance(WEB_VIEW_WIDGET(widget));
GdkRectangle clipRect;
if (!gdk_cairo_get_clip_rectangle(cr, &clipRect))
return FALSE;
webView->paint(widget, clipRect, cr);
return FALSE;
}
#endif
static void webViewSizeAllocate(GtkWidget* widget, GtkAllocation* allocation)
{
WebView* webView = webViewWidgetGetWebViewInstance(WEB_VIEW_WIDGET(widget));
GTK_WIDGET_CLASS(webViewWidgetParentClass)->size_allocate(widget, allocation);
webView->setSize(widget, IntSize(allocation->width, allocation->height));
}
static gboolean webViewFocusInEvent(GtkWidget* widget, GdkEventFocus* event)
{
WebViewWidget* webViewWidget = WEB_VIEW_WIDGET(widget);
WebView* webView = webViewWidgetGetWebViewInstance(webViewWidget);
GtkWidget* toplevel = gtk_widget_get_toplevel(widget);
if (gtk_widget_is_toplevel(toplevel) && gtk_window_has_toplevel_focus(GTK_WINDOW(toplevel))) {
gtk_im_context_focus_in(webViewWidgetGetIMContext(webViewWidget));
webView->handleFocusInEvent(widget);
}
return GTK_WIDGET_CLASS(webViewWidgetParentClass)->focus_in_event(widget, event);
}
static gboolean webViewFocusOutEvent(GtkWidget* widget, GdkEventFocus* event)
{
WebViewWidget* webViewWidget = WEB_VIEW_WIDGET(widget);
WebView* webView = webViewWidgetGetWebViewInstance(webViewWidget);
webView->handleFocusOutEvent(widget);
GtkIMContext* imContext = webViewWidgetGetIMContext(webViewWidget);
if (imContext)
gtk_im_context_focus_out(imContext);
return GTK_WIDGET_CLASS(webViewWidgetParentClass)->focus_out_event(widget, event);
}
static gboolean webViewKeyPressEvent(GtkWidget* widget, GdkEventKey* event)
{
WebView* webView = webViewWidgetGetWebViewInstance(WEB_VIEW_WIDGET(widget));
webView->handleKeyboardEvent(event);
return GTK_WIDGET_CLASS(webViewWidgetParentClass)->key_press_event(widget, event);
}
static gboolean webViewKeyReleaseEvent(GtkWidget* widget, GdkEventKey* event)
{
WebViewWidget* webViewWidget = WEB_VIEW_WIDGET(widget);
WebView* webView = webViewWidgetGetWebViewInstance(webViewWidget);
if (gtk_im_context_filter_keypress(webViewWidgetGetIMContext(webViewWidget), event))
return TRUE;
webView->handleKeyboardEvent(event);
return GTK_WIDGET_CLASS(webViewWidgetParentClass)->key_release_event(widget, event);
}
// Copied from webkitwebview.cpp
static guint32 getEventTime(GdkEvent* event)
{
guint32 time = gdk_event_get_time(event);
if (time)
return time;
// Real events always have a non-zero time, but events synthesized
// by the DRT do not and we must calculate a time manually. This time
// is not calculated in the DRT, because GTK+ does not work well with
// anything other than GDK_CURRENT_TIME on synthesized events.
GTimeVal timeValue;
g_get_current_time(&timeValue);
return (timeValue.tv_sec * 1000) + (timeValue.tv_usec / 1000);
}
static gboolean webViewButtonPressEvent(GtkWidget* widget, GdkEventButton* buttonEvent)
{
if (buttonEvent->button == 3) {
// FIXME: [GTK] Add context menu support for Webkit2.
// https://bugs.webkit.org/show_bug.cgi?id=54827
notImplemented();
return FALSE;
}
gtk_widget_grab_focus(widget);
// For double and triple clicks GDK sends both a normal button press event
// and a specific type (like GDK_2BUTTON_PRESS). If we detect a special press
// coming up, ignore this event as it certainly generated the double or triple
// click. The consequence of not eating this event is two DOM button press events
// are generated.
GOwnPtr<GdkEvent> nextEvent(gdk_event_peek());
if (nextEvent && (nextEvent->any.type == GDK_2BUTTON_PRESS || nextEvent->any.type == GDK_3BUTTON_PRESS))
return TRUE;
gint doubleClickDistance = 250;
gint doubleClickTime = 5;
GtkSettings* settings = gtk_settings_get_for_screen(gtk_widget_get_screen(widget));
g_object_get(settings,
"gtk-double-click-distance", &doubleClickDistance,
"gtk-double-click-time", &doubleClickTime, NULL);
// GTK+ only counts up to triple clicks, but WebCore wants to know about
// quadruple clicks, quintuple clicks, ad infinitum. Here, we replicate the
// GDK logic for counting clicks.
GdkEvent* event(reinterpret_cast<GdkEvent*>(buttonEvent));
guint32 eventTime = getEventTime(event);
WebViewWidget* webViewWidget = WEB_VIEW_WIDGET(widget);
WebViewWidgetPrivate* priv = webViewWidget->priv;
if ((event->type == GDK_2BUTTON_PRESS || event->type == GDK_3BUTTON_PRESS)
|| ((abs(buttonEvent->x - priv->previousClickPoint.x()) < doubleClickDistance)
&& (abs(buttonEvent->y - priv->previousClickPoint.y()) < doubleClickDistance)
&& (eventTime - priv->previousClickTime < static_cast<guint>(doubleClickTime))
&& (buttonEvent->button == priv->previousClickButton)))
priv->currentClickCount++;
else
priv->currentClickCount = 1;
WebView* webView = webViewWidgetGetWebViewInstance(webViewWidget);
webView->handleMouseEvent(event, priv->currentClickCount);
gdouble x, y;
gdk_event_get_coords(event, &x, &y);
priv->previousClickPoint = IntPoint(x, y);
priv->previousClickButton = buttonEvent->button;
priv->previousClickTime = eventTime;
return FALSE;
}
static gboolean webViewButtonReleaseEvent(GtkWidget* widget, GdkEventButton* event)
{
WebView* webView = webViewWidgetGetWebViewInstance(WEB_VIEW_WIDGET(widget));
gtk_widget_grab_focus(widget);
webView->handleMouseEvent(reinterpret_cast<GdkEvent*>(event), 0 /* currentClickCount */);
return FALSE;
}
static gboolean webViewScrollEvent(GtkWidget* widget, GdkEventScroll* event)
{
WebView* webView = webViewWidgetGetWebViewInstance(WEB_VIEW_WIDGET(widget));
webView->handleWheelEvent(event);
return FALSE;
}
static gboolean webViewMotionNotifyEvent(GtkWidget* widget, GdkEventMotion* event)
{
WebView* webView = webViewWidgetGetWebViewInstance(WEB_VIEW_WIDGET(widget));
webView->handleMouseEvent(reinterpret_cast<GdkEvent*>(event), 0 /* currentClickCount */);
return FALSE;
}
static void webViewWidgetClassInit(WebViewWidgetClass* webViewWidgetClass)
{
webViewWidgetParentClass = g_type_class_peek_parent(webViewWidgetClass);
GtkWidgetClass* widgetClass = GTK_WIDGET_CLASS(webViewWidgetClass);
widgetClass->realize = webViewWidgetRealize;
#ifdef GTK_API_VERSION_2
widgetClass->expose_event = webViewExpose;
#else
widgetClass->draw = webViewDraw;
#endif
widgetClass->size_allocate = webViewSizeAllocate;
widgetClass->focus_in_event = webViewFocusInEvent;
widgetClass->focus_out_event = webViewFocusOutEvent;
widgetClass->key_press_event = webViewKeyPressEvent;
widgetClass->key_release_event = webViewKeyReleaseEvent;
widgetClass->button_press_event = webViewButtonPressEvent;
widgetClass->button_release_event = webViewButtonReleaseEvent;
widgetClass->scroll_event = webViewScrollEvent;
widgetClass->motion_notify_event = webViewMotionNotifyEvent;
GObjectClass* gobjectClass = G_OBJECT_CLASS(webViewWidgetClass);
gobjectClass->dispose = webViewWidgetDispose;
GtkContainerClass* containerClass = GTK_CONTAINER_CLASS(webViewWidgetClass);
containerClass->add = webViewWidgetContainerAdd;
g_type_class_add_private(webViewWidgetClass, sizeof(WebViewWidgetPrivate));
}
GType webViewWidgetGetType()
{
static volatile gsize gDefineTypeIdVolatile = 0;
if (!g_once_init_enter(&gDefineTypeIdVolatile))
return gDefineTypeIdVolatile;
GType gDefineTypeId = g_type_register_static_simple(GTK_TYPE_CONTAINER,
g_intern_static_string("WebViewWidget"),
sizeof(WebViewWidgetClass),
reinterpret_cast<GClassInitFunc>(webViewWidgetClassInit),
sizeof(WebViewWidget),
reinterpret_cast<GInstanceInitFunc>(webViewWidgetInit),
static_cast<GTypeFlags>(0));
g_once_init_leave(&gDefineTypeIdVolatile, gDefineTypeId);
return gDefineTypeIdVolatile;
}
WebView* webViewWidgetGetWebViewInstance(WebViewWidget* webViewWidget)
{
return webViewWidget->priv->webViewInstance;
}
void webViewWidgetSetWebViewInstance(WebViewWidget* webViewWidget, WebView* webViewInstance)
{
webViewWidget->priv->webViewInstance = webViewInstance;
}
GtkIMContext* webViewWidgetGetIMContext(WebViewWidget* webViewWidget)
{
return webViewWidget->priv->imContext;
}