blob: 894dca4fa6e4c63d797f342962dc234d6b18f14a [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtWebEngine module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "render_widget_host_view_qt_delegate_widget.h"
#include "qwebenginepage_p.h"
#include "qwebengineview.h"
#include <QGuiApplication>
#include <QLayout>
#include <QMouseEvent>
#include <QOpenGLContext>
#include <QResizeEvent>
#include <QSGAbstractRenderer>
#include <QSGNode>
#include <QWindow>
#include <QtQuick/private/qquickwindow_p.h>
namespace QtWebEngineCore {
class RenderWidgetHostViewQuickItem : public QQuickItem {
public:
RenderWidgetHostViewQuickItem(RenderWidgetHostViewQtDelegateClient *client) : m_client(client)
{
setFlag(ItemHasContents, true);
// Mark that this item should receive focus when the parent QQuickWidget receives focus.
setFocus(true);
}
protected:
bool event(QEvent *event) override
{
if (event->type() == QEvent::ShortcutOverride)
return m_client->forwardEvent(event);
return QQuickItem::event(event);
}
void focusInEvent(QFocusEvent *event) override
{
m_client->forwardEvent(event);
}
void focusOutEvent(QFocusEvent *event) override
{
m_client->forwardEvent(event);
}
void inputMethodEvent(QInputMethodEvent *event) override
{
m_client->forwardEvent(event);
}
QSGNode *updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) override
{
return m_client->updatePaintNode(oldNode);
}
QVariant inputMethodQuery(Qt::InputMethodQuery query) const override
{
return m_client->inputMethodQuery(query);
}
private:
RenderWidgetHostViewQtDelegateClient *m_client;
};
RenderWidgetHostViewQtDelegateWidget::RenderWidgetHostViewQtDelegateWidget(RenderWidgetHostViewQtDelegateClient *client, QWidget *parent)
: QQuickWidget(parent)
, m_client(client)
, m_rootItem(new RenderWidgetHostViewQuickItem(client))
, m_isPopup(false)
{
setFocusPolicy(Qt::StrongFocus);
QSurfaceFormat format;
format.setDepthBufferSize(24);
format.setStencilBufferSize(8);
#ifndef QT_NO_OPENGL
QOpenGLContext *globalSharedContext = QOpenGLContext::globalShareContext();
if (globalSharedContext) {
QSurfaceFormat sharedFormat = globalSharedContext->format();
#ifdef Q_OS_OSX
// Check that the default QSurfaceFormat OpenGL profile is compatible with the global OpenGL
// shared context profile, otherwise this could lead to a nasty crash.
QSurfaceFormat defaultFormat = QSurfaceFormat::defaultFormat();
if (defaultFormat.profile() != sharedFormat.profile()
&& defaultFormat.profile() == QSurfaceFormat::CoreProfile
&& defaultFormat.version() >= qMakePair(3, 2)) {
qFatal("QWebEngine: Default QSurfaceFormat OpenGL profile is not compatible with the "
"global shared context OpenGL profile. Please make sure you set a compatible "
"QSurfaceFormat before the QtGui application instance is created.");
}
#endif
int major;
int minor;
QSurfaceFormat::OpenGLContextProfile profile;
#ifdef Q_OS_MACOS
// Due to QTBUG-63180, requesting the sharedFormat.majorVersion() on macOS will lead to
// a failed creation of QQuickWidget shared context. Thus make sure to request the
// major version specified in the defaultFormat instead.
major = defaultFormat.majorVersion();
minor = defaultFormat.minorVersion();
profile = defaultFormat.profile();
#else
major = sharedFormat.majorVersion();
minor = sharedFormat.minorVersion();
profile = sharedFormat.profile();
#endif
// Make sure the OpenGL profile of the QQuickWidget matches the shared context profile.
// It covers the following cases:
// 1) Desktop OpenGL Core Profile.
// 2) Windows ANGLE OpenGL ES profile.
if (sharedFormat.profile() == QSurfaceFormat::CoreProfile
#ifdef Q_OS_WIN
|| globalSharedContext->isOpenGLES()
#endif
) {
format.setMajorVersion(major);
format.setMinorVersion(minor);
format.setProfile(profile);
}
}
setFormat(format);
#endif
setMouseTracking(true);
setAttribute(Qt::WA_AcceptTouchEvents);
setAttribute(Qt::WA_OpaquePaintEvent);
setAttribute(Qt::WA_AlwaysShowToolTips);
setContent(QUrl(), nullptr, m_rootItem.data());
connectRemoveParentBeforeParentDelete();
}
RenderWidgetHostViewQtDelegateWidget::~RenderWidgetHostViewQtDelegateWidget()
{
QWebEnginePagePrivate::bindPageAndWidget(nullptr, this);
}
void RenderWidgetHostViewQtDelegateWidget::connectRemoveParentBeforeParentDelete()
{
disconnect(m_parentDestroyedConnection);
if (QWidget *parent = parentWidget()) {
m_parentDestroyedConnection = connect(parent, &QObject::destroyed,
this,
&RenderWidgetHostViewQtDelegateWidget::removeParentBeforeParentDelete);
} else {
m_parentDestroyedConnection = QMetaObject::Connection();
}
}
void RenderWidgetHostViewQtDelegateWidget::removeParentBeforeParentDelete()
{
// Unset the parent, because parent is being destroyed, but the owner of this
// RenderWidgetHostViewQtDelegateWidget is actually a RenderWidgetHostViewQt instance.
setParent(nullptr);
// If this widget represents a popup window, make sure to close it, so that if the popup was the
// last visible top level window, the application event loop can quit if it deems it necessarry.
if (m_isPopup)
close();
}
void RenderWidgetHostViewQtDelegateWidget::initAsPopup(const QRect& screenRect)
{
m_isPopup = true;
// The keyboard events are supposed to go to the parent RenderHostView
// so the WebUI popups should never have focus. Besides, if the parent view
// loses focus, WebKit will cause its associated popups (including this one)
// to be destroyed.
setAttribute(Qt::WA_ShowWithoutActivating);
setFocusPolicy(Qt::NoFocus);
setWindowFlags(Qt::Popup | Qt::FramelessWindowHint | Qt::WindowDoesNotAcceptFocus);
setGeometry(screenRect);
show();
}
void RenderWidgetHostViewQtDelegateWidget::closeEvent(QCloseEvent *event)
{
Q_UNUSED(event);
// If a close event was received from the window manager (e.g. when moving the parent window,
// clicking outside the popup area)
// make sure to notify the Chromium WebUI popup and its underlying
// RenderWidgetHostViewQtDelegate instance to be closed.
if (m_isPopup)
m_client->closePopup();
}
QRectF RenderWidgetHostViewQtDelegateWidget::viewGeometry() const
{
return QRectF(mapToGlobal(pos()), size());
}
QRect RenderWidgetHostViewQtDelegateWidget::windowGeometry() const
{
if (!window())
return QRect();
return window()->frameGeometry();
}
void RenderWidgetHostViewQtDelegateWidget::setKeyboardFocus()
{
// The root item always has focus within the root focus scope:
Q_ASSERT(m_rootItem->hasFocus());
setFocus();
}
bool RenderWidgetHostViewQtDelegateWidget::hasKeyboardFocus()
{
// The root item always has focus within the root focus scope:
Q_ASSERT(m_rootItem->hasFocus());
return hasFocus();
}
void RenderWidgetHostViewQtDelegateWidget::lockMouse()
{
grabMouse();
}
void RenderWidgetHostViewQtDelegateWidget::unlockMouse()
{
releaseMouse();
}
void RenderWidgetHostViewQtDelegateWidget::show()
{
m_rootItem->setVisible(true);
// Check if we're attached to a QWebEngineView, we don't
// want to show anything else than popups as top-level.
if (parent() || m_isPopup) {
QQuickWidget::show();
}
}
void RenderWidgetHostViewQtDelegateWidget::hide()
{
m_rootItem->setVisible(false);
QQuickWidget::hide();
}
bool RenderWidgetHostViewQtDelegateWidget::isVisible() const
{
return QQuickWidget::isVisible() && m_rootItem->isVisible();
}
QWindow* RenderWidgetHostViewQtDelegateWidget::window() const
{
const QWidget* root = QQuickWidget::window();
return root ? root->windowHandle() : 0;
}
QSGTexture *RenderWidgetHostViewQtDelegateWidget::createTextureFromImage(const QImage &image)
{
return quickWindow()->createTextureFromImage(image, QQuickWindow::TextureCanUseAtlas);
}
QSGLayer *RenderWidgetHostViewQtDelegateWidget::createLayer()
{
QSGRenderContext *renderContext = QQuickWindowPrivate::get(quickWindow())->context;
return renderContext->sceneGraphContext()->createLayer(renderContext);
}
QSGImageNode *RenderWidgetHostViewQtDelegateWidget::createImageNode()
{
return quickWindow()->createImageNode();
}
QSGRectangleNode *RenderWidgetHostViewQtDelegateWidget::createRectangleNode()
{
return quickWindow()->createRectangleNode();
}
void RenderWidgetHostViewQtDelegateWidget::update()
{
m_rootItem->update();
}
void RenderWidgetHostViewQtDelegateWidget::updateCursor(const QCursor &cursor)
{
QQuickWidget::setCursor(cursor);
}
void RenderWidgetHostViewQtDelegateWidget::resize(int width, int height)
{
QQuickWidget::resize(width, height);
}
void RenderWidgetHostViewQtDelegateWidget::move(const QPoint &screenPos)
{
Q_ASSERT(m_isPopup);
QQuickWidget::move(screenPos);
}
void RenderWidgetHostViewQtDelegateWidget::inputMethodStateChanged(bool editorVisible, bool passwordInput)
{
QQuickWidget::setAttribute(Qt::WA_InputMethodEnabled, editorVisible && !passwordInput);
qApp->inputMethod()->update(Qt::ImQueryInput | Qt::ImEnabled | Qt::ImHints);
if (qApp->inputMethod()->isVisible() != editorVisible)
qApp->inputMethod()->setVisible(editorVisible);
}
void RenderWidgetHostViewQtDelegateWidget::setInputMethodHints(Qt::InputMethodHints hints)
{
QQuickWidget::setInputMethodHints(hints);
}
void RenderWidgetHostViewQtDelegateWidget::setClearColor(const QColor &color)
{
QQuickWidget::setClearColor(color);
// QQuickWidget is usually blended by punching holes into widgets
// above it to simulate the visual stacking order. If we want it to be
// transparent we have to throw away the proper stacking order and always
// blend the complete normal widgets backing store under it.
bool isTranslucent = color.alpha() < 255;
setAttribute(Qt::WA_AlwaysStackOnTop, isTranslucent);
setAttribute(Qt::WA_OpaquePaintEvent, !isTranslucent);
update();
}
QVariant RenderWidgetHostViewQtDelegateWidget::inputMethodQuery(Qt::InputMethodQuery query) const
{
return m_client->inputMethodQuery(query);
}
void RenderWidgetHostViewQtDelegateWidget::resizeEvent(QResizeEvent *resizeEvent)
{
QQuickWidget::resizeEvent(resizeEvent);
m_client->visualPropertiesChanged();
}
void RenderWidgetHostViewQtDelegateWidget::showEvent(QShowEvent *event)
{
QQuickWidget::showEvent(event);
// We don't have a way to catch a top-level window change with QWidget
// but a widget will most likely be shown again if it changes, so do
// the reconnection at this point.
for (const QMetaObject::Connection &c : qAsConst(m_windowConnections))
disconnect(c);
m_windowConnections.clear();
if (QWindow *w = window()) {
m_windowConnections.append(connect(w, SIGNAL(xChanged(int)), SLOT(onWindowPosChanged())));
m_windowConnections.append(connect(w, SIGNAL(yChanged(int)), SLOT(onWindowPosChanged())));
}
m_client->visualPropertiesChanged();
m_client->notifyShown();
}
void RenderWidgetHostViewQtDelegateWidget::hideEvent(QHideEvent *event)
{
QQuickWidget::hideEvent(event);
m_client->notifyHidden();
}
bool RenderWidgetHostViewQtDelegateWidget::copySurface(const QRect &rect, const QSize &size, QImage &image)
{
QPixmap pixmap = rect.isEmpty() ? QQuickWidget::grab(QQuickWidget::rect()) : QQuickWidget::grab(rect);
if (pixmap.isNull())
return false;
image = pixmap.toImage().scaled(size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
return true;
}
bool RenderWidgetHostViewQtDelegateWidget::event(QEvent *event)
{
bool handled = false;
// Track parent to make sure we don't get deleted.
switch (event->type()) {
case QEvent::ParentChange:
connectRemoveParentBeforeParentDelete();
break;
default:
break;
}
// Mimic QWidget::event() by ignoring mouse, keyboard, touch and tablet events if the widget is
// disabled.
if (!isEnabled()) {
switch (event->type()) {
case QEvent::TabletPress:
case QEvent::TabletRelease:
case QEvent::TabletMove:
case QEvent::MouseButtonPress:
case QEvent::MouseButtonRelease:
case QEvent::MouseButtonDblClick:
case QEvent::MouseMove:
case QEvent::TouchBegin:
case QEvent::TouchUpdate:
case QEvent::TouchEnd:
case QEvent::TouchCancel:
case QEvent::ContextMenu:
case QEvent::KeyPress:
case QEvent::KeyRelease:
#ifndef QT_NO_WHEELEVENT
case QEvent::Wheel:
#endif
return false;
default:
break;
}
}
switch (event->type()) {
case QEvent::FocusIn:
case QEvent::FocusOut:
// We forward focus events later, once they have made it to the m_rootItem.
return QQuickWidget::event(event);
case QEvent::DragEnter:
case QEvent::DragLeave:
case QEvent::DragMove:
case QEvent::Drop:
case QEvent::HoverEnter:
case QEvent::HoverLeave:
case QEvent::HoverMove:
// Let the parent handle these events.
return false;
default:
break;
}
if (event->type() == QEvent::MouseButtonDblClick) {
// QWidget keeps the Qt4 behavior where the DblClick event would replace the Press event.
// QtQuick is different by sending both the Press and DblClick events for the second press
// where we can simply ignore the DblClick event.
QMouseEvent *dblClick = static_cast<QMouseEvent *>(event);
QMouseEvent press(QEvent::MouseButtonPress, dblClick->localPos(), dblClick->windowPos(), dblClick->screenPos(),
dblClick->button(), dblClick->buttons(), dblClick->modifiers(), dblClick->source());
press.setTimestamp(dblClick->timestamp());
handled = m_client->forwardEvent(&press);
} else
handled = m_client->forwardEvent(event);
if (!handled)
return QQuickWidget::event(event);
// Most events are accepted by default, but tablet events are not:
event->accept();
return true;
}
void RenderWidgetHostViewQtDelegateWidget::onWindowPosChanged()
{
m_client->visualPropertiesChanged();
}
} // namespace QtWebEngineCore