cef/tests/cefclient/cefclient_osr_widget_gtk.cpp

494 lines
15 KiB
C++

// Copyright (c) 2013 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.
#include "cefclient/cefclient_osr_widget_gtk.h"
// This value is defined in build/common.gypi and must be undefined here
// in order for gtkglext to compile.
#undef GTK_DISABLE_SINGLE_INCLUDES
#include <gdk/gdk.h>
#include <gdk/gdkkeysyms.h>
#include <glib-object.h>
#include <gtk/gtk.h>
#include <gtk/gtkgl.h>
#include <GL/gl.h>
#include "include/cef_runnable.h"
#include "cefclient/util.h"
namespace {
gint glarea_size_allocation(GtkWidget* widget,
GtkAllocation* allocation,
OSRWindow* window) {
CefRefPtr<CefBrowserHost> host = window->GetBrowserHost();
host->WasResized();
return TRUE;
}
int get_cef_state_modifiers(guint state) {
int modifiers = 0;
if (state & GDK_SHIFT_MASK)
modifiers |= EVENTFLAG_SHIFT_DOWN;
if (state & GDK_LOCK_MASK)
modifiers |= EVENTFLAG_CAPS_LOCK_ON;
if (state & GDK_CONTROL_MASK)
modifiers |= EVENTFLAG_CONTROL_DOWN;
if (state & GDK_MOD1_MASK)
modifiers |= EVENTFLAG_ALT_DOWN;
if (state & GDK_BUTTON1_MASK)
modifiers |= EVENTFLAG_LEFT_MOUSE_BUTTON;
if (state & GDK_BUTTON2_MASK)
modifiers |= EVENTFLAG_MIDDLE_MOUSE_BUTTON;
if (state & GDK_BUTTON3_MASK)
modifiers |= EVENTFLAG_RIGHT_MOUSE_BUTTON;
return modifiers;
}
gint glarea_click_event(GtkWidget* widget,
GdkEventButton* event,
OSRWindow* window) {
CefRefPtr<CefBrowserHost> host = window->GetBrowserHost();
CefBrowserHost::MouseButtonType button_type = MBT_LEFT;
switch (event->button) {
case 1:
break;
case 2:
button_type = MBT_MIDDLE;
break;
case 3:
button_type = MBT_RIGHT;
break;
default:
// Other mouse buttons are not handled here.
return FALSE;
}
CefMouseEvent mouse_event;
mouse_event.x = event->x;
mouse_event.y = event->y;
window->ApplyPopupOffset(mouse_event.x, mouse_event.y);
mouse_event.modifiers = get_cef_state_modifiers(event->state);
bool mouse_up = (event->type == GDK_BUTTON_RELEASE);
if (!mouse_up)
gtk_widget_grab_focus(widget);
int click_count = 1;
switch (event->type) {
case GDK_2BUTTON_PRESS:
click_count = 2;
break;
case GDK_3BUTTON_PRESS:
click_count = 3;
break;
default:
break;
}
host->SendMouseClickEvent(mouse_event, button_type, mouse_up, click_count);
return TRUE;
}
gint glarea_move_event(GtkWidget* widget,
GdkEventMotion* event,
OSRWindow* window) {
gint x, y;
GdkModifierType state;
if (event->is_hint) {
gdk_window_get_pointer(event->window, &x, &y, &state);
} else {
x = (gint)event->x;
y = (gint)event->y;
state = (GdkModifierType)event->state;
}
CefRefPtr<CefBrowserHost> host = window->GetBrowserHost();
CefMouseEvent mouse_event;
mouse_event.x = x;
mouse_event.y = y;
window->ApplyPopupOffset(mouse_event.x, mouse_event.y);
mouse_event.modifiers = get_cef_state_modifiers(state);
bool mouse_leave = (event->type == GDK_LEAVE_NOTIFY);
host->SendMouseMoveEvent(mouse_event, mouse_leave);
return TRUE;
}
gint glarea_scroll_event(GtkWidget* widget,
GdkEventScroll* event,
OSRWindow* window) {
CefRefPtr<CefBrowserHost> host = window->GetBrowserHost();
CefMouseEvent mouse_event;
mouse_event.x = event->x;
mouse_event.y = event->y;
window->ApplyPopupOffset(mouse_event.x, mouse_event.y);
mouse_event.modifiers = get_cef_state_modifiers(event->state);
static const int scrollbarPixelsPerGtkTick = 40;
int deltaX = 0;
int deltaY = 0;
switch (event->direction) {
case GDK_SCROLL_UP:
deltaY = scrollbarPixelsPerGtkTick;
break;
case GDK_SCROLL_DOWN:
deltaY = -scrollbarPixelsPerGtkTick;
break;
case GDK_SCROLL_LEFT:
deltaX = scrollbarPixelsPerGtkTick;
break;
case GDK_SCROLL_RIGHT:
deltaX = -scrollbarPixelsPerGtkTick;
break;
}
host->SendMouseWheelEvent(mouse_event, deltaX, deltaY);
return TRUE;
}
gint glarea_key_event(GtkWidget* widget,
GdkEventKey* event,
OSRWindow* window) {
CefRefPtr<CefBrowserHost> host = window->GetBrowserHost();
CefKeyEvent key_event;
key_event.native_key_code = event->keyval;
key_event.modifiers = get_cef_state_modifiers(event->state);
if (event->type == GDK_KEY_PRESS) {
key_event.type = KEYEVENT_RAWKEYDOWN;
host->SendKeyEvent(key_event);
} else {
// Need to send both KEYUP and CHAR events.
key_event.type = KEYEVENT_KEYUP;
host->SendKeyEvent(key_event);
key_event.type = KEYEVENT_CHAR;
host->SendKeyEvent(key_event);
}
return TRUE;
}
gint glarea_focus_event(GtkWidget* widget,
GdkEventFocus* event,
OSRWindow* window) {
CefRefPtr<CefBrowserHost> host = window->GetBrowserHost();
host->SendFocusEvent(event->in == TRUE);
return TRUE;
}
void widget_get_rect_in_screen(GtkWidget* widget, GdkRectangle* r) {
gint x, y, w, h;
GdkRectangle extents;
GdkWindow* window = gtk_widget_get_parent_window(widget);
// Get parent's left-top screen coordinates.
gdk_window_get_root_origin(window, &x, &y);
// Get parent's width and height.
gdk_drawable_get_size(window, &w, &h);
// Get parent's extents including decorations.
gdk_window_get_frame_extents(window, &extents);
// X and Y calculations assume that left, right and bottom border sizes are
// all the same.
const gint border = (extents.width - w) / 2;
r->x = x + border + widget->allocation.x;
r->y = y + (extents.height - h) - border + widget->allocation.y;
r->width = widget->allocation.width;
r->height = widget->allocation.height;
}
class ScopedGLContext {
public:
ScopedGLContext(GtkWidget* widget, bool swap_buffers)
: swap_buffers_(swap_buffers) {
GdkGLContext* glcontext = gtk_widget_get_gl_context(widget);
gldrawable_ = gtk_widget_get_gl_drawable(widget);
is_valid_ = gdk_gl_drawable_gl_begin(gldrawable_, glcontext);
}
virtual ~ScopedGLContext() {
if (is_valid_) {
gdk_gl_drawable_gl_end(gldrawable_);
if(swap_buffers_) {
if (gdk_gl_drawable_is_double_buffered(gldrawable_))
gdk_gl_drawable_swap_buffers(gldrawable_);
else
glFlush();
}
}
}
bool IsValid() const { return is_valid_; }
private:
bool swap_buffers_;
GdkGLContext* glcontext_;
GdkGLDrawable* gldrawable_;
bool is_valid_;
};
} // namespace
// static
CefRefPtr<OSRWindow> OSRWindow::Create(OSRBrowserProvider* browser_provider,
bool transparent,
CefWindowHandle parentView) {
ASSERT(browser_provider);
if (!browser_provider)
return NULL;
return new OSRWindow(browser_provider, transparent, parentView);
}
// static
CefRefPtr<OSRWindow> OSRWindow::From(
CefRefPtr<ClientHandler::RenderHandler> renderHandler) {
return static_cast<OSRWindow*>(renderHandler.get());
}
void OSRWindow::OnBeforeClose(CefRefPtr<CefBrowser> browser) {
// Disconnect all signal handlers that reference |this|.
g_signal_handlers_disconnect_matched(glarea_, G_SIGNAL_MATCH_DATA, 0, 0,
NULL, NULL, this);
DisableGL();
}
bool OSRWindow::GetViewRect(CefRefPtr<CefBrowser> browser,
CefRect& rect) {
if (!glarea_)
return false;
// The simulated screen and view rectangle are the same. This is necessary
// for popup menus to be located and sized inside the view.
rect.x = rect.y = 0;
rect.width = glarea_->allocation.width;
rect.height = glarea_->allocation.height;
return true;
}
bool OSRWindow::GetScreenPoint(CefRefPtr<CefBrowser> browser,
int viewX,
int viewY,
int& screenX,
int& screenY) {
GdkRectangle screen_rect;
widget_get_rect_in_screen(glarea_, &screen_rect);
screenX = screen_rect.x + viewX;
screenY = screen_rect.y + viewY;
return true;
}
void OSRWindow::OnPopupShow(CefRefPtr<CefBrowser> browser,
bool show) {
if (!show) {
CefRect dirty_rect = renderer_.popup_rect();
renderer_.ClearPopupRects();
browser->GetHost()->Invalidate(dirty_rect, PET_VIEW);
}
renderer_.OnPopupShow(browser, show);
}
void OSRWindow::OnPopupSize(CefRefPtr<CefBrowser> browser,
const CefRect& rect) {
renderer_.OnPopupSize(browser, rect);
}
void OSRWindow::OnPaint(CefRefPtr<CefBrowser> browser,
PaintElementType type,
const RectList& dirtyRects,
const void* buffer,
int width, int height) {
if (painting_popup_) {
renderer_.OnPaint(browser, type, dirtyRects, buffer, width, height);
return;
}
if (!gl_enabled_)
EnableGL();
ScopedGLContext scoped_gl_context(glarea_, true);
if (!scoped_gl_context.IsValid())
return;
renderer_.OnPaint(browser, type, dirtyRects, buffer, width, height);
if (type == PET_VIEW && !renderer_.popup_rect().IsEmpty()) {
painting_popup_ = true;
CefRect client_popup_rect(0, 0,
renderer_.popup_rect().width,
renderer_.popup_rect().height);
browser->GetHost()->Invalidate(client_popup_rect, PET_POPUP);
painting_popup_ = false;
}
renderer_.Render();
}
void OSRWindow::OnCursorChange(CefRefPtr<CefBrowser> browser,
CefCursorHandle cursor) {
GtkWidget* window = gtk_widget_get_toplevel(glarea_);
GdkWindow* gdk_window = gtk_widget_get_window(window);
if (cursor->type == GDK_LAST_CURSOR)
cursor = NULL;
gdk_window_set_cursor(gdk_window, cursor);
}
void OSRWindow::Invalidate() {
if (!CefCurrentlyOn(TID_UI)) {
CefPostTask(TID_UI, NewCefRunnableMethod(this, &OSRWindow::Invalidate));
return;
}
// Don't post another task if the previous task is still pending.
if (render_task_pending_)
return;
render_task_pending_ = true;
// Render at 30fps.
static const int kRenderDelay = 1000 / 30;
CefPostDelayedTask(TID_UI, NewCefRunnableMethod(this, &OSRWindow::Render),
kRenderDelay);
}
bool OSRWindow::IsOverPopupWidget(int x, int y) const {
const CefRect& rc = renderer_.popup_rect();
int popup_right = rc.x + rc.width;
int popup_bottom = rc.y + rc.height;
return (x >= rc.x) && (x < popup_right) &&
(y >= rc.y) && (y < popup_bottom);
}
int OSRWindow::GetPopupXOffset() const {
return renderer_.original_popup_rect().x - renderer_.popup_rect().x;
}
int OSRWindow::GetPopupYOffset() const {
return renderer_.original_popup_rect().y - renderer_.popup_rect().y;
}
void OSRWindow::ApplyPopupOffset(int& x, int& y) const {
if (IsOverPopupWidget(x, y)) {
x += GetPopupXOffset();
y += GetPopupYOffset();
}
}
OSRWindow::OSRWindow(OSRBrowserProvider* browser_provider,
bool transparent,
CefWindowHandle parentView)
: renderer_(transparent),
browser_provider_(browser_provider),
gl_enabled_(false),
painting_popup_(false),
render_task_pending_(false) {
glarea_ = gtk_drawing_area_new();
ASSERT(glarea_);
GdkGLConfig* glconfig = gdk_gl_config_new_by_mode(
static_cast<GdkGLConfigMode>(GDK_GL_MODE_RGB |
GDK_GL_MODE_DEPTH |
GDK_GL_MODE_DOUBLE));
ASSERT(glconfig);
gtk_widget_set_gl_capability(glarea_, glconfig, NULL, TRUE,
GDK_GL_RGBA_TYPE);
gtk_widget_set_can_focus(glarea_, TRUE);
g_signal_connect(G_OBJECT(glarea_), "size_allocate",
G_CALLBACK(glarea_size_allocation), this);
gtk_widget_set_events(glarea_,
GDK_BUTTON_PRESS_MASK |
GDK_BUTTON_RELEASE_MASK |
GDK_KEY_PRESS_MASK |
GDK_KEY_RELEASE_MASK |
GDK_ENTER_NOTIFY_MASK |
GDK_LEAVE_NOTIFY_MASK |
GDK_POINTER_MOTION_MASK |
GDK_POINTER_MOTION_HINT_MASK |
GDK_SCROLL_MASK |
GDK_FOCUS_CHANGE_MASK);
g_signal_connect(G_OBJECT(glarea_), "button_press_event",
G_CALLBACK(glarea_click_event), this);
g_signal_connect(G_OBJECT(glarea_), "button_release_event",
G_CALLBACK(glarea_click_event), this);
g_signal_connect(G_OBJECT(glarea_), "key_press_event",
G_CALLBACK(glarea_key_event), this);
g_signal_connect(G_OBJECT(glarea_), "key_release_event",
G_CALLBACK(glarea_key_event), this);
g_signal_connect(G_OBJECT(glarea_), "enter_notify_event",
G_CALLBACK(glarea_move_event), this);
g_signal_connect(G_OBJECT(glarea_), "leave_notify_event",
G_CALLBACK(glarea_move_event), this);
g_signal_connect(G_OBJECT(glarea_), "motion_notify_event",
G_CALLBACK(glarea_move_event), this);
g_signal_connect(G_OBJECT(glarea_), "scroll_event",
G_CALLBACK(glarea_scroll_event), this);
g_signal_connect(G_OBJECT(glarea_), "focus_in_event",
G_CALLBACK(glarea_focus_event), this);
g_signal_connect(G_OBJECT(glarea_), "focus_out_event",
G_CALLBACK(glarea_focus_event), this);
gtk_container_add(GTK_CONTAINER(parentView), glarea_);
}
OSRWindow::~OSRWindow() {
}
void OSRWindow::Render() {
ASSERT(CefCurrentlyOn(TID_UI));
if (render_task_pending_)
render_task_pending_ = false;
if (!gl_enabled_)
EnableGL();
ScopedGLContext scoped_gl_context(glarea_, true);
if (!scoped_gl_context.IsValid())
return;
renderer_.Render();
}
void OSRWindow::EnableGL() {
ASSERT(CefCurrentlyOn(TID_UI));
if (gl_enabled_)
return;
ScopedGLContext scoped_gl_context(glarea_, false);
if (!scoped_gl_context.IsValid())
return;
renderer_.Initialize();
gl_enabled_ = true;
}
void OSRWindow::DisableGL() {
ASSERT(CefCurrentlyOn(TID_UI));
if (!gl_enabled_)
return;
ScopedGLContext scoped_gl_context(glarea_, false);
if (!scoped_gl_context.IsValid())
return;
renderer_.Cleanup();
gl_enabled_ = false;
}