| /**************************************************************************** |
| ** |
| ** 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 |