/**************************************************************************** ** ** 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // 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 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 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(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 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(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 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(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 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(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 controller) { if (!ensureComponentLoaded(FilePicker)) return; QQmlContext *context = qmlContext(m_view); QObject *filePicker = filePickerComponent->beginCreate(context); if (QQuickItem *item = qobject_cast(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(); } 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 (!ensureComponentLoaded(ToolTip)) return; if (text.isEmpty()) { m_toolTip.reset(); return; } if (!m_toolTip.isNull()) return; QQmlContext *context = qmlContext(m_view); m_toolTip.reset(toolTipComponent->beginCreate(context)); if (QQuickItem *item = qobject_cast(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(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(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(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