diff options
author | Peter Varga <pvarga@inf.u-szeged.hu> | 2021-06-14 16:20:26 +0200 |
---|---|---|
committer | Peter Varga <pvarga@inf.u-szeged.hu> | 2022-06-19 03:08:06 +0200 |
commit | 19c6b0a22f20a33c8c1fd4bdea29468fa8b4e10e (patch) | |
tree | 03631f39583e2b18b06b373f364f00f3d5ab17de /src/webenginequick/ui_delegates_manager.cpp | |
parent | e2d3beb65653bb96342a80b436d8935e903fdd70 (diff) | |
download | qtwebengine-19c6b0a22f20a33c8c1fd4bdea29468fa8b4e10e.tar.gz |
Support HTML5 <datalist> element
The datalist uses Chromium's autofill component to fetch and filter
predefined options in the list and autocomplete the field with the
selected option. Autofill component is added to build and bound to
WebEngine. All the unnecessary autofill features for datalist are
supposed to be disabled: payment/credit card support, password manager,
save profile data, store suggestions in database etc.
Custom popups for the dropdown are implemented for Widget and Quick.
Scrolling in dropdown is not implemented in this change.
Fixes: QTBUG-54433
Pick-to: 6.4
Change-Id: I155d02d6dbc9d88fbca4bc5f55b76c19d0ba7a9d
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'src/webenginequick/ui_delegates_manager.cpp')
-rw-r--r-- | src/webenginequick/ui_delegates_manager.cpp | 158 |
1 files changed, 157 insertions, 1 deletions
diff --git a/src/webenginequick/ui_delegates_manager.cpp b/src/webenginequick/ui_delegates_manager.cpp index 5a01ea2f0..06b72348c 100644 --- a/src/webenginequick/ui_delegates_manager.cpp +++ b/src/webenginequick/ui_delegates_manager.cpp @@ -43,6 +43,7 @@ #include "api/qquickwebengineview_p_p.h" #include <authentication_dialog_controller.h> +#include <autofill_popup_controller.h> #include <color_chooser_controller.h> #include <file_picker_controller.h> #include <javascript_dialog_controller.h> @@ -58,6 +59,9 @@ #include <QtQml/qqmlcontext.h> #include <QtQml/qqmlengine.h> #include <QtQml/qqmlproperty.h> +#include <QtQuick/qquickwindow.h> + +#include <algorithm> // Uncomment for QML debugging //#define UI_DELEGATES_DEBUG @@ -129,7 +133,7 @@ UIDelegatesManager::UIDelegatesManager(QQuickWebEngineView *view) : m_view(view) , m_toolTip(nullptr) , m_touchSelectionMenu(nullptr) - FOR_EACH_COMPONENT_TYPE(COMPONENT_MEMBER_INIT, NO_SEPARATOR) + , m_autofillPopup(nullptr) FOR_EACH_COMPONENT_TYPE(COMPONENT_MEMBER_INIT, NO_SEPARATOR) { } @@ -567,6 +571,158 @@ void UIDelegatesManager::hideTouchSelectionMenu() QTimer::singleShot(0, m_view, [this] { m_touchSelectionMenu.reset(); }); } +bool AutofillPopupEventFilter::eventFilter(QObject *object, QEvent *event) +{ + if (event->type() == QEvent::ShortcutOverride) { + QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event); + if (keyEvent->key() == Qt::Key_Escape) { + m_manager->hideAutofillPopup(); + return true; + } + + // Ignore shortcuts while the popup is open. It may result unwanted + // edit commands sent to Chromium that blocks the key press. + event->ignore(); + return true; + } + + // AutofillPopupControllerImpl::HandleKeyPressEvent() + // chrome/browser/ui/autofill/autofill_popup_controller_impl.cc + + if (event->type() == QEvent::KeyPress) { + QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event); + switch (keyEvent->key()) { + case Qt::Key_Up: + m_controller->selectPreviousSuggestion(); + return true; + case Qt::Key_Down: + m_controller->selectNextSuggestion(); + return true; + case Qt::Key_PageUp: + m_controller->selectFirstSuggestion(); + return true; + case Qt::Key_PageDown: + m_controller->selectLastSuggestion(); + return true; + case Qt::Key_Escape: + m_manager->hideAutofillPopup(); + return true; + case Qt::Key_Enter: + case Qt::Key_Return: + m_controller->acceptSuggestion(); + return true; + case Qt::Key_Delete: + // Remove suggestion is not supported for datalist. + // Forward delete to view to be able to remove selected text. + break; + case Qt::Key_Tab: + m_controller->acceptSuggestion(); + break; + default: + break; + } + } + + // Do not forward release events of the overridden key presses. + if (event->type() == QEvent::KeyRelease) { + QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event); + switch (keyEvent->key()) { + case Qt::Key_Up: + case Qt::Key_Down: + case Qt::Key_PageUp: + case Qt::Key_PageDown: + case Qt::Key_Escape: + case Qt::Key_Enter: + case Qt::Key_Return: + return true; + default: + break; + } + } + + return QObject::eventFilter(object, event); +} + +void UIDelegatesManager::showAutofillPopup(QtWebEngineCore::AutofillPopupController *controller, + QPointF pos, int width, bool autoselectFirstSuggestion) +{ + static const int padding = 1; + static const int itemHeight = 20; + const int proposedHeight = itemHeight * (controller->model()->rowCount()) + padding * 2; + + bool popupWasNull = false; + if (m_autofillPopup.isNull()) { + popupWasNull = true; + if (!ensureComponentLoaded(AutofillPopup)) + return; + + QQmlContext *context = qmlContext(m_view); + m_autofillPopup.reset(autofillPopupComponent->beginCreate(context)); + if (QQuickItem *item = qobject_cast<QQuickItem *>(m_autofillPopup.data())) + item->setParentItem(m_view); + m_autofillPopup->setParent(m_view); + } + + m_autofillPopup->setProperty("controller", QVariant::fromValue(controller)); + m_autofillPopup->setProperty("x", pos.x()); + m_autofillPopup->setProperty("y", pos.y()); + m_autofillPopup->setProperty("width", width); + m_autofillPopup->setProperty("height", + std::min(proposedHeight, qRound(m_view->height() - pos.y()))); + m_autofillPopup->setProperty("padding", padding); + m_autofillPopup->setProperty("itemHeight", itemHeight); + + if (popupWasNull) { + QQmlProperty selectedSignal(m_autofillPopup.data(), QStringLiteral("onSelected")); + CHECK_QML_SIGNAL_PROPERTY(selectedSignal, autofillPopupComponent->url()); + static int selectSuggestionIndex = + controller->metaObject()->indexOfSlot("selectSuggestion(int)"); + QObject::connect(m_autofillPopup.data(), selectedSignal.method(), controller, + controller->metaObject()->method(selectSuggestionIndex)); + + QQmlProperty acceptedSignal(m_autofillPopup.data(), QStringLiteral("onAccepted")); + CHECK_QML_SIGNAL_PROPERTY(acceptedSignal, autofillPopupComponent->url()); + static int acceptSuggestionIndex = + controller->metaObject()->indexOfSlot("acceptSuggestion()"); + QObject::connect(m_autofillPopup.data(), acceptedSignal.method(), controller, + controller->metaObject()->method(acceptSuggestionIndex)); + + QObject::connect(controller, &QtWebEngineCore::AutofillPopupController::currentIndexChanged, + [this](const QModelIndex &index) { + QMetaObject::invokeMethod(m_autofillPopup.data(), "setCurrentIndex", + Qt::DirectConnection, + Q_ARG(QVariant, index.row())); + }); + + autofillPopupComponent->completeCreate(); + + m_view->window()->installEventFilter( + new AutofillPopupEventFilter(controller, this, m_autofillPopup.data())); + + QMetaObject::invokeMethod(m_autofillPopup.data(), "open"); + controller->notifyPopupShown(); + } + + if (autoselectFirstSuggestion) + controller->selectFirstSuggestion(); +} + +void UIDelegatesManager::hideAutofillPopup() +{ + if (!m_autofillPopup) + return; + + QTimer::singleShot(0, m_view, [this] { + if (m_autofillPopup) { + QtWebEngineCore::AutofillPopupController *controller = + m_autofillPopup->property("controller") + .value<QtWebEngineCore::AutofillPopupController *>(); + m_autofillPopup.reset(); + controller->notifyPopupHidden(); + } + }); +} + bool UIDelegatesManager::initializeImportDirs(QStringList &dirs, QQmlEngine *engine) { const QStringList paths = engine->importPathList(); |