diff options
Diffstat (limited to 'src/webenginequick/ui_delegates_manager.cpp')
-rw-r--r-- | src/webenginequick/ui_delegates_manager.cpp | 740 |
1 files changed, 740 insertions, 0 deletions
diff --git a/src/webenginequick/ui_delegates_manager.cpp b/src/webenginequick/ui_delegates_manager.cpp new file mode 100644 index 000000000..756b3429e --- /dev/null +++ b/src/webenginequick/ui_delegates_manager.cpp @@ -0,0 +1,740 @@ +/**************************************************************************** +** +** 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 |