blob: 756b3429e3ba5d46ba05f6feb86a22bec7e7286f [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 "ui_delegates_manager.h"
#include "api/qquickwebengineview_p.h"
#include <authentication_dialog_controller.h>
#include <color_chooser_controller.h>
#include <file_picker_controller.h>
#include <javascript_dialog_controller.h>
#include <touch_selection_menu_controller.h>
#include <web_contents_adapter_client.h>
#include <QFileInfo>
#include <QQmlContext>
#include <QQmlEngine>
#include <QQmlProperty>
#include <QQuickWindow>
#include <QCursor>
#include <QList>
#include <QScreen>
#include <QTimer>
#include <QGuiApplication>
// Uncomment for QML debugging
//#define UI_DELEGATES_DEBUG
namespace QtWebEngineCore {
#define NO_SEPARATOR
#if defined(Q_CC_MSVC) && !defined(Q_CC_CLANG)
#define FILE_NAME_CASE_STATEMENT(TYPE, COMPONENT) \
case UIDelegatesManager::TYPE:\
return QString::fromLatin1(#TYPE ##".qml");
#else
#define FILE_NAME_CASE_STATEMENT(TYPE, COMPONENT) \
case UIDelegatesManager::TYPE:\
return QStringLiteral(#TYPE".qml");
#endif
static QString fileNameForComponent(UIDelegatesManager::ComponentType type)
{
switch (type) {
FOR_EACH_COMPONENT_TYPE(FILE_NAME_CASE_STATEMENT, NO_SEPARATOR)
default:
Q_UNREACHABLE();
}
return QString();
}
static QPoint calculateToolTipPosition(QPoint &position, QSize &toolTip) {
QRect screen;
const QList<QScreen *> screens = QGuiApplication::screens();
for (const QScreen *src : screens)
if (src->availableGeometry().contains(position))
screen = src->availableGeometry();
position += QPoint(2, 16);
if (position.x() + toolTip.width() > screen.x() + screen.width())
position.rx() -= 4 + toolTip.width();
if (position.y() + toolTip.height() > screen.y() + screen.height())
position.ry() -= 24 + toolTip.height();
if (position.y() < screen.y())
position.setY(screen.y());
if (position.x() + toolTip.width() > screen.x() + screen.width())
position.setX(screen.x() + screen.width() - toolTip.width());
if (position.x() < screen.x())
position.setX(screen.x());
if (position.y() + toolTip.height() > screen.y() + screen.height())
position.setY(screen.y() + screen.height() - toolTip.height());
return position;
}
const char *defaultPropertyName(QObject *obj)
{
const QMetaObject *metaObject = obj->metaObject();
int idx = metaObject->indexOfClassInfo("DefaultProperty");
if (-1 == idx)
return 0;
QMetaClassInfo info = metaObject->classInfo(idx);
return info.value();
}
#define COMPONENT_MEMBER_INIT(TYPE, COMPONENT) \
, COMPONENT##Component(0)
UIDelegatesManager::UIDelegatesManager(QQuickWebEngineView *view)
: m_view(view)
, m_toolTip(nullptr)
, m_touchSelectionMenu(nullptr)
FOR_EACH_COMPONENT_TYPE(COMPONENT_MEMBER_INIT, NO_SEPARATOR)
{
}
UIDelegatesManager::~UIDelegatesManager()
{
}
#define COMPONENT_MEMBER_CASE_STATEMENT(TYPE, COMPONENT) \
case TYPE: \
component = &COMPONENT##Component; \
break;
bool UIDelegatesManager::initializeImportDirs(QStringList &dirs, QQmlEngine *engine)
{
const QStringList paths = engine->importPathList();
for (const QString &path : paths) {
QString importPath = path % QLatin1String("/QtWebEngine/Controls1Delegates/");
// resource paths have to be tested using the ":/" prefix
if (importPath.startsWith(QLatin1String("qrc:/")))
importPath.remove(0, 3);
QFileInfo fi(importPath);
if (fi.exists())
dirs << fi.absolutePath();
}
return !dirs.isEmpty();
}
bool UIDelegatesManager::ensureComponentLoaded(ComponentType type)
{
QQmlEngine* engine = qmlEngine(m_view);
if (m_importDirs.isEmpty() && !initializeImportDirs(m_importDirs, engine))
return false;
QQmlComponent **component;
switch (type) {
FOR_EACH_COMPONENT_TYPE(COMPONENT_MEMBER_CASE_STATEMENT, NO_SEPARATOR)
default:
Q_UNREACHABLE();
return false;
}
QString fileName(fileNameForComponent(type));
#ifndef UI_DELEGATES_DEBUG
if (*component)
return true;
#else // Unconditionally reload the components each time.
fprintf(stderr, "%s: %s\n", Q_FUNC_INFO, qPrintable(fileName));
#endif
if (!engine)
return false;
for (const QString &importDir : qAsConst(m_importDirs)) {
const QString componentFilePath = importDir % QLatin1Char('/') % fileName;
if (!QFileInfo(componentFilePath).exists())
continue;
// FIXME: handle async loading
*component = (new QQmlComponent(engine,
importDir.startsWith(QLatin1String(":/")) ? QUrl(QLatin1String("qrc") + componentFilePath)
: QUrl::fromLocalFile(componentFilePath),
QQmlComponent::PreferSynchronous, m_view));
if ((*component)->status() != QQmlComponent::Ready) {
const QList<QQmlError> errs = (*component)->errors();
for (const QQmlError &err : errs)
qWarning("QtWebEngine: component error: %s\n", qPrintable(err.toString()));
delete *component;
*component = nullptr;
return false;
}
return true;
}
return false;
}
#define CHECK_QML_SIGNAL_PROPERTY(prop, location) \
if (!prop.isSignalProperty()) \
qWarning("%s is missing %s signal property.\n", qPrintable(location.toString()), qPrintable(prop.name()));
void UIDelegatesManager::addMenuItem(QQuickWebEngineAction *action, QObject *menu, bool checkable, bool checked)
{
Q_ASSERT(action);
if (!ensureComponentLoaded(MenuItem))
return;
QObject *it = menuItemComponent->beginCreate(qmlContext(m_view));
QQmlProperty(it, QStringLiteral("text")).write(action->text());
QQmlProperty(it, QStringLiteral("iconName")).write(action->iconName());
QQmlProperty(it, QStringLiteral("enabled")).write(action->isEnabled());
QQmlProperty(it, QStringLiteral("checkable")).write(checkable);
QQmlProperty(it, QStringLiteral("checked")).write(checked);
QQmlProperty signal(it, QStringLiteral("onTriggered"));
CHECK_QML_SIGNAL_PROPERTY(signal, menuItemComponent->url());
const QMetaObject *actionMeta = action->metaObject();
QObject::connect(it, signal.method(), action, actionMeta->method(actionMeta->indexOfSlot("trigger()")));
menuItemComponent->completeCreate();
it->setParent(menu);
QQmlListReference entries(menu, defaultPropertyName(menu), qmlEngine(m_view));
if (entries.isValid())
entries.append(it);
}
void UIDelegatesManager::addMenuSeparator(QObject *menu)
{
if (!ensureComponentLoaded(MenuSeparator))
return;
QQmlContext *itemContext = qmlContext(m_view);
QObject *sep = menuSeparatorComponent->create(itemContext);
sep->setParent(menu);
QQmlListReference entries(menu, defaultPropertyName(menu), qmlEngine(m_view));
if (entries.isValid() && entries.count() > 0)
entries.append(sep);
}
QObject *UIDelegatesManager::addMenu(QObject *parentMenu, const QString &title, const QPoint& pos)
{
Q_ASSERT(parentMenu);
if (!ensureComponentLoaded(Menu))
return nullptr;
QQmlContext *context = qmlContext(m_view);
QObject *menu = menuComponent->beginCreate(context);
// set visual parent for non-Window-based menus
if (QQuickItem *item = qobject_cast<QQuickItem*>(menu))
item->setParentItem(m_view);
if (!title.isEmpty())
QQmlProperty(menu, QStringLiteral("title")).write(title);
if (!pos.isNull())
menu->setProperty("pos", pos);
menu->setParent(parentMenu);
QQmlProperty doneSignal(menu, QStringLiteral("onDone"));
static int deleteLaterIndex = menu->metaObject()->indexOfSlot("deleteLater()");
CHECK_QML_SIGNAL_PROPERTY(doneSignal, menuComponent->url());
QObject::connect(menu, doneSignal.method(), menu, menu->metaObject()->method(deleteLaterIndex));
QQmlListReference entries(parentMenu, defaultPropertyName(parentMenu), qmlEngine(m_view));
if (entries.isValid())
entries.append(menu);
menuComponent->completeCreate();
return menu;
}
#define ASSIGN_DIALOG_COMPONENT_DATA_CASE_STATEMENT(TYPE, COMPONENT) \
case TYPE:\
dialogComponent = COMPONENT##Component; \
break;
void UIDelegatesManager::showDialog(QSharedPointer<JavaScriptDialogController> dialogController)
{
Q_ASSERT(!dialogController.isNull());
ComponentType dialogComponentType = Invalid;
QString title;
switch (dialogController->type()) {
case WebContentsAdapterClient::AlertDialog:
dialogComponentType = AlertDialog;
title = tr("Javascript Alert - %1").arg(m_view->url().toString());
break;
case WebContentsAdapterClient::ConfirmDialog:
dialogComponentType = ConfirmDialog;
title = tr("Javascript Confirm - %1").arg(m_view->url().toString());
break;
case WebContentsAdapterClient::PromptDialog:
dialogComponentType = PromptDialog;
title = tr("Javascript Prompt - %1").arg(m_view->url().toString());
break;
case WebContentsAdapterClient::UnloadDialog:
dialogComponentType = ConfirmDialog;
title = tr("Are you sure you want to leave this page?");
break;
case WebContentsAdapterClient::InternalAuthorizationDialog:
dialogComponentType = ConfirmDialog;
title = dialogController->title();
break;
default:
Q_UNREACHABLE();
}
if (!ensureComponentLoaded(dialogComponentType)) {
// Let the controller know it couldn't be loaded
qWarning("Failed to load dialog, rejecting.");
dialogController->reject();
return;
}
QQmlComponent *dialogComponent = nullptr;
switch (dialogComponentType) {
FOR_EACH_COMPONENT_TYPE(ASSIGN_DIALOG_COMPONENT_DATA_CASE_STATEMENT, NO_SEPARATOR)
default:
Q_UNREACHABLE();
}
QQmlContext *context = qmlContext(m_view);
QObject *dialog = dialogComponent->beginCreate(context);
// set visual parent for non-Window-based dialogs
if (QQuickItem *item = qobject_cast<QQuickItem*>(dialog))
item->setParentItem(m_view);
dialog->setParent(m_view);
QQmlProperty textProp(dialog, QStringLiteral("text"));
if (dialogController->type() == WebContentsAdapterClient::UnloadDialog)
textProp.write(tr("Changes that you made may not be saved."));
else
textProp.write(dialogController->message());
QQmlProperty titleProp(dialog, QStringLiteral("title"));
titleProp.write(title);
QQmlProperty acceptSignal(dialog, QStringLiteral("onAccepted"));
QQmlProperty rejectSignal(dialog, QStringLiteral("onRejected"));
CHECK_QML_SIGNAL_PROPERTY(acceptSignal, dialogComponent->url());
CHECK_QML_SIGNAL_PROPERTY(rejectSignal, dialogComponent->url());
static int acceptIndex = dialogController->metaObject()->indexOfSlot("accept()");
QObject::connect(dialog, acceptSignal.method(), dialogController.data(), dialogController->metaObject()->method(acceptIndex));
static int rejectIndex = dialogController->metaObject()->indexOfSlot("reject()");
QObject::connect(dialog, rejectSignal.method(), dialogController.data(), dialogController->metaObject()->method(rejectIndex));
if (dialogComponentType == PromptDialog) {
QQmlProperty promptProp(dialog, QStringLiteral("prompt"));
promptProp.write(dialogController->defaultPrompt());
QQmlProperty inputSignal(dialog, QStringLiteral("onInput"));
CHECK_QML_SIGNAL_PROPERTY(inputSignal, dialogComponent->url());
static int setTextIndex = dialogController->metaObject()->indexOfSlot("textProvided(QString)");
QObject::connect(dialog, inputSignal.method(), dialogController.data(), dialogController->metaObject()->method(setTextIndex));
}
dialogComponent->completeCreate();
QObject::connect(dialogController.data(), &JavaScriptDialogController::dialogCloseRequested, dialog, &QObject::deleteLater);
QMetaObject::invokeMethod(dialog, "open");
}
void UIDelegatesManager::showColorDialog(QSharedPointer<ColorChooserController> controller)
{
if (!ensureComponentLoaded(ColorDialog)) {
// Let the controller know it couldn't be loaded
qWarning("Failed to load dialog, rejecting.");
controller->reject();
return;
}
QQmlContext *context = qmlContext(m_view);
QObject *colorDialog = colorDialogComponent->beginCreate(context);
if (QQuickItem *item = qobject_cast<QQuickItem*>(colorDialog))
item->setParentItem(m_view);
colorDialog->setParent(m_view);
if (controller->initialColor().isValid())
colorDialog->setProperty("color", controller->initialColor());
QQmlProperty selectedColorSignal(colorDialog, QStringLiteral("onSelectedColor"));
CHECK_QML_SIGNAL_PROPERTY(selectedColorSignal, colorDialogComponent->url());
QQmlProperty rejectedSignal(colorDialog, QStringLiteral("onRejected"));
CHECK_QML_SIGNAL_PROPERTY(rejectedSignal, colorDialogComponent->url());
static int acceptIndex = controller->metaObject()->indexOfSlot("accept(QVariant)");
QObject::connect(colorDialog, selectedColorSignal.method(), controller.data(), controller->metaObject()->method(acceptIndex));
static int rejectIndex = controller->metaObject()->indexOfSlot("reject()");
QObject::connect(colorDialog, rejectedSignal.method(), controller.data(), controller->metaObject()->method(rejectIndex));
// delete later
static int deleteLaterIndex = colorDialog->metaObject()->indexOfSlot("deleteLater()");
QObject::connect(colorDialog, selectedColorSignal.method(), colorDialog, colorDialog->metaObject()->method(deleteLaterIndex));
QObject::connect(colorDialog, rejectedSignal.method(), colorDialog, colorDialog->metaObject()->method(deleteLaterIndex));
colorDialogComponent->completeCreate();
QMetaObject::invokeMethod(colorDialog, "open");
}
void UIDelegatesManager::showDialog(QSharedPointer<AuthenticationDialogController> dialogController)
{
Q_ASSERT(!dialogController.isNull());
if (!ensureComponentLoaded(AuthenticationDialog)) {
// Let the controller know it couldn't be loaded
qWarning("Failed to load authentication dialog, rejecting.");
dialogController->reject();
return;
}
QQmlContext *context = qmlContext(m_view);
QObject *authenticationDialog = authenticationDialogComponent->beginCreate(context);
// set visual parent for non-Window-based dialogs
if (QQuickItem *item = qobject_cast<QQuickItem*>(authenticationDialog))
item->setParentItem(m_view);
authenticationDialog->setParent(m_view);
QString introMessage;
if (dialogController->isProxy()) {
introMessage = tr("Connect to proxy \"%1\" using:");
introMessage = introMessage.arg(dialogController->host().toHtmlEscaped());
} else {
const QUrl url = dialogController->url();
introMessage = tr("Enter username and password for \"%1\" at %2://%3");
introMessage = introMessage.arg(dialogController->realm(), url.scheme(), url.host());
}
QQmlProperty textProp(authenticationDialog, QStringLiteral("text"));
textProp.write(introMessage);
QQmlProperty acceptSignal(authenticationDialog, QStringLiteral("onAccepted"));
QQmlProperty rejectSignal(authenticationDialog, QStringLiteral("onRejected"));
CHECK_QML_SIGNAL_PROPERTY(acceptSignal, authenticationDialogComponent->url());
CHECK_QML_SIGNAL_PROPERTY(rejectSignal, authenticationDialogComponent->url());
static int acceptIndex = dialogController->metaObject()->indexOfSlot("accept(QString,QString)");
static int deleteLaterIndex = authenticationDialog->metaObject()->indexOfSlot("deleteLater()");
QObject::connect(authenticationDialog, acceptSignal.method(), dialogController.data(), dialogController->metaObject()->method(acceptIndex));
QObject::connect(authenticationDialog, acceptSignal.method(), authenticationDialog, authenticationDialog->metaObject()->method(deleteLaterIndex));
static int rejectIndex = dialogController->metaObject()->indexOfSlot("reject()");
QObject::connect(authenticationDialog, rejectSignal.method(), dialogController.data(), dialogController->metaObject()->method(rejectIndex));
QObject::connect(authenticationDialog, rejectSignal.method(), authenticationDialog, authenticationDialog->metaObject()->method(deleteLaterIndex));
authenticationDialogComponent->completeCreate();
QMetaObject::invokeMethod(authenticationDialog, "open");
}
void UIDelegatesManager::showFilePicker(QSharedPointer<FilePickerController> controller)
{
if (!ensureComponentLoaded(FilePicker))
return;
QQmlContext *context = qmlContext(m_view);
QObject *filePicker = filePickerComponent->beginCreate(context);
if (QQuickItem *item = qobject_cast<QQuickItem*>(filePicker))
item->setParentItem(m_view);
filePicker->setParent(m_view);
filePickerComponent->completeCreate();
// Fine-tune some properties depending on the mode.
switch (controller->mode()) {
case FilePickerController::Open:
break;
case FilePickerController::Save:
filePicker->setProperty("selectExisting", false);
break;
case FilePickerController::OpenMultiple:
filePicker->setProperty("selectMultiple", true);
break;
case FilePickerController::UploadFolder:
filePicker->setProperty("selectFolder", true);
break;
default:
Q_UNREACHABLE();
}
filePicker->setProperty("nameFilters", FilePickerController::nameFilters(controller->acceptedMimeTypes()));
QQmlProperty filesPickedSignal(filePicker, QStringLiteral("onFilesSelected"));
CHECK_QML_SIGNAL_PROPERTY(filesPickedSignal, filePickerComponent->url());
QQmlProperty rejectSignal(filePicker, QStringLiteral("onRejected"));
CHECK_QML_SIGNAL_PROPERTY(rejectSignal, filePickerComponent->url());
static int acceptedIndex = controller->metaObject()->indexOfSlot("accepted(QVariant)");
QObject::connect(filePicker, filesPickedSignal.method(), controller.data(), controller->metaObject()->method(acceptedIndex));
static int rejectedIndex = controller->metaObject()->indexOfSlot("rejected()");
QObject::connect(filePicker, rejectSignal.method(), controller.data(), controller->metaObject()->method(rejectedIndex));
// delete when done.
static int deleteLaterIndex = filePicker->metaObject()->indexOfSlot("deleteLater()");
QObject::connect(filePicker, filesPickedSignal.method(), filePicker, filePicker->metaObject()->method(deleteLaterIndex));
QObject::connect(filePicker, rejectSignal.method(), filePicker, filePicker->metaObject()->method(deleteLaterIndex));
QMetaObject::invokeMethod(filePicker, "open");
}
class TemporaryCursorMove
{
public:
TemporaryCursorMove(const QQuickItem *item, const QPoint &pos)
{
if (pos.isNull() || !item->contains(pos))
return;
const QPoint oldPos = QCursor::pos();
const QPoint globalPos = item->mapToGlobal(QPointF(pos)).toPoint();
if (oldPos == globalPos)
return;
m_oldCursorPos = oldPos;
QCursor::setPos(globalPos);
}
~TemporaryCursorMove()
{
if (!m_oldCursorPos.isNull())
QCursor::setPos(m_oldCursorPos);
}
private:
QPoint m_oldCursorPos;
};
void UIDelegatesManager::showMenu(QObject *menu)
{
// QtQuick.Controls.Menu.popup() always shows the menu under the mouse cursor, i.e. the menu's
// position we set above is ignored. Work around the problem by moving the mouse cursor
// temporarily to the right position.
TemporaryCursorMove tcm(m_view, menu->property("pos").toPoint());
QMetaObject::invokeMethod(menu, "popup");
}
void UIDelegatesManager::showToolTip(const QString &text)
{
if (text.isEmpty()) {
m_toolTip.reset();
return;
}
if (!ensureComponentLoaded(ToolTip))
return;
if (!m_toolTip.isNull())
return;
QQmlContext *context = qmlContext(m_view);
m_toolTip.reset(toolTipComponent->beginCreate(context));
if (QQuickItem *item = qobject_cast<QQuickItem *>(m_toolTip.data()))
item->setParentItem(m_view);
m_toolTip->setParent(m_view);
toolTipComponent->completeCreate();
QQmlProperty(m_toolTip.data(), QStringLiteral("text")).write(text);
int height = QQmlProperty(m_toolTip.data(), QStringLiteral("height")).read().toInt();
int width = QQmlProperty(m_toolTip.data(), QStringLiteral("width")).read().toInt();
QSize toolTipSize(width, height);
QPoint position = m_view->cursor().pos();
position = m_view->mapFromGlobal(calculateToolTipPosition(position, toolTipSize)).toPoint();
QQmlProperty(m_toolTip.data(), QStringLiteral("x")).write(position.x());
QQmlProperty(m_toolTip.data(), QStringLiteral("y")).write(position.y());
QMetaObject::invokeMethod(m_toolTip.data(), "open");
}
QQuickItem *UIDelegatesManager::createTouchHandle()
{
if (!ensureComponentLoaded(TouchHandle))
return nullptr;
QQmlContext *context = qmlContext(m_view);
QObject *touchHandle = touchHandleComponent->beginCreate(context);
QQuickItem *item = qobject_cast<QQuickItem *>(touchHandle);
Q_ASSERT(item);
item->setParentItem(m_view);
touchHandleComponent->completeCreate();
return item;
}
void UIDelegatesManager::showTouchSelectionMenu(QtWebEngineCore::TouchSelectionMenuController *menuController, const QRect &bounds, const int spacing)
{
if (!ensureComponentLoaded(TouchSelectionMenu))
return;
QQmlContext *context = qmlContext(m_view);
m_touchSelectionMenu.reset(touchSelectionMenuComponent->beginCreate(context));
if (QQuickItem *item = qobject_cast<QQuickItem *>(m_touchSelectionMenu.data()))
item->setParentItem(m_view);
m_touchSelectionMenu->setParent(m_view);
QQmlProperty(m_touchSelectionMenu.data(), QStringLiteral("width")).write(bounds.width());
QQmlProperty(m_touchSelectionMenu.data(), QStringLiteral("height")).write(bounds.height());
QQmlProperty(m_touchSelectionMenu.data(), QStringLiteral("x")).write(bounds.x());
QQmlProperty(m_touchSelectionMenu.data(), QStringLiteral("y")).write(bounds.y());
QQmlProperty(m_touchSelectionMenu.data(), QStringLiteral("border.width")).write(spacing);
// Cut button
bool cutEnabled = menuController->isCommandEnabled(TouchSelectionMenuController::Cut);
QQmlProperty(m_touchSelectionMenu.data(), QStringLiteral("isCutEnabled")).write(cutEnabled);
if (cutEnabled) {
QQmlProperty cutSignal(m_touchSelectionMenu.data(), QStringLiteral("onCutTriggered"));
CHECK_QML_SIGNAL_PROPERTY(cutSignal, touchSelectionMenuComponent->url());
int cutIndex = menuController->metaObject()->indexOfSlot("cut()");
QObject::connect(m_touchSelectionMenu.data(), cutSignal.method(), menuController, menuController->metaObject()->method(cutIndex));
}
// Copy button
bool copyEnabled = menuController->isCommandEnabled(TouchSelectionMenuController::Copy);
QQmlProperty(m_touchSelectionMenu.data(), QStringLiteral("isCopyEnabled")).write(copyEnabled);
if (copyEnabled) {
QQmlProperty copySignal(m_touchSelectionMenu.data(), QStringLiteral("onCopyTriggered"));
CHECK_QML_SIGNAL_PROPERTY(copySignal, touchSelectionMenuComponent->url());
int copyIndex = menuController->metaObject()->indexOfSlot("copy()");
QObject::connect(m_touchSelectionMenu.data(), copySignal.method(), menuController, menuController->metaObject()->method(copyIndex));
}
// Paste button
bool pasteEnabled = menuController->isCommandEnabled(TouchSelectionMenuController::Paste);
QQmlProperty(m_touchSelectionMenu.data(), QStringLiteral("isPasteEnabled")).write(pasteEnabled);
if (pasteEnabled) {
QQmlProperty pasteSignal(m_touchSelectionMenu.data(), QStringLiteral("onPasteTriggered"));
CHECK_QML_SIGNAL_PROPERTY(pasteSignal, touchSelectionMenuComponent->url());
int pasteIndex = menuController->metaObject()->indexOfSlot("paste()");
QObject::connect(m_touchSelectionMenu.data(), pasteSignal.method(), menuController, menuController->metaObject()->method(pasteIndex));
}
// Context menu button
QQmlProperty contextMenuSignal(m_touchSelectionMenu.data(), QStringLiteral("onContextMenuTriggered"));
CHECK_QML_SIGNAL_PROPERTY(contextMenuSignal, touchSelectionMenuComponent->url());
int contextMenuIndex = menuController->metaObject()->indexOfSlot("runContextMenu()");
QObject::connect(m_touchSelectionMenu.data(), contextMenuSignal.method(), menuController, menuController->metaObject()->method(contextMenuIndex));
touchSelectionMenuComponent->completeCreate();
}
void UIDelegatesManager::hideTouchSelectionMenu()
{
QTimer::singleShot(0, m_view, [this] { m_touchSelectionMenu.reset(); });
}
UI2DelegatesManager::UI2DelegatesManager(QQuickWebEngineView *view) : UIDelegatesManager(view)
{
}
bool UI2DelegatesManager::initializeImportDirs(QStringList &dirs, QQmlEngine *engine)
{
const QStringList paths = engine->importPathList();
for (const QString &path : paths) {
QString controls2ImportPath = path % QLatin1String("/QtWebEngine/Controls2Delegates/");
QString controls1ImportPath = path % QLatin1String("/QtWebEngine/Controls1Delegates/");
// resource paths have to be tested using the ":/" prefix
if (controls2ImportPath.startsWith(QLatin1String("qrc:/"))) {
controls2ImportPath.remove(0, 3);
controls1ImportPath.remove(0, 3);
}
QFileInfo fi2(controls2ImportPath);
if (fi2.exists())
dirs << fi2.absolutePath();
QFileInfo fi1(controls1ImportPath);
if (fi1.exists())
dirs << fi1.absolutePath();
}
return !dirs.isEmpty();
}
QObject *UI2DelegatesManager::addMenu(QObject *parentMenu, const QString &title, const QPoint &pos)
{
Q_ASSERT(parentMenu);
if (!ensureComponentLoaded(Menu))
return nullptr;
QQmlContext *context = qmlContext(m_view);
QObject *menu = menuComponent->beginCreate(context);
// set visual parent for non-Window-based menus
if (QQuickItem *item = qobject_cast<QQuickItem*>(menu))
item->setParentItem(m_view);
if (!title.isEmpty())
menu->setProperty("title", title);
if (!pos.isNull()) {
menu->setProperty("x", pos.x());
menu->setProperty("y", pos.y());
}
menu->setParent(parentMenu);
QQmlProperty doneSignal(menu, QStringLiteral("onDone"));
CHECK_QML_SIGNAL_PROPERTY(doneSignal, menuComponent->url())
static int deleteLaterIndex = menu->metaObject()->indexOfSlot("deleteLater()");
QObject::connect(menu, doneSignal.method(), menu, menu->metaObject()->method(deleteLaterIndex));
menuComponent->completeCreate();
return menu;
}
void UI2DelegatesManager::addMenuItem(QQuickWebEngineAction *action, QObject *menu, bool checkable, bool checked)
{
Q_ASSERT(action);
if (!ensureComponentLoaded(MenuItem))
return;
QObject *it = menuItemComponent->beginCreate(qmlContext(m_view));
it->setProperty("text", action->text());
it->setProperty("enabled", action->isEnabled());
it->setProperty("checked", checked);
it->setProperty("checkable", checkable);
QQmlProperty signal(it, QStringLiteral("onTriggered"));
CHECK_QML_SIGNAL_PROPERTY(signal, menuItemComponent->url());
const QMetaObject *actionMeta = action->metaObject();
QObject::connect(it, signal.method(), action, actionMeta->method(actionMeta->indexOfSlot("trigger()")));
menuItemComponent->completeCreate();
it->setParent(menu);
QQmlListReference entries(menu, defaultPropertyName(menu), qmlEngine(m_view));
if (entries.isValid())
entries.append(it);
}
void UI2DelegatesManager::showMenu(QObject *menu)
{
QMetaObject::invokeMethod(menu, "open");
}
} // namespace QtWebEngineCore