| 1 | // Copyright (C) 2023 The Qt Company Ltd. | 
| 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only | 
| 3 |  | 
| 4 | #include "qquickfocusframe.h" | 
| 5 |  | 
| 6 | #include <private/qquickitem_p.h> | 
| 7 |  | 
| 8 | #include <QtCore/qmetaobject.h> | 
| 9 |  | 
| 10 | #include <QtGui/qguiapplication.h> | 
| 11 |  | 
| 12 | #include <QtQml/qqmlengine.h> | 
| 13 | #include <QtQml/qqmlcontext.h> | 
| 14 | #include <QtQml/qqmlcomponent.h> | 
| 15 |  | 
| 16 | #include <QtQuick/qquickitem.h> | 
| 17 |  | 
| 18 | #include "items/qquickstyleitem.h" | 
| 19 |  | 
| 20 | QT_BEGIN_NAMESPACE | 
| 21 |  | 
| 22 | Q_LOGGING_CATEGORY(lcFocusFrame, "qt.quick.controls.focusframe" ) | 
| 23 |  | 
| 24 | QQuickFocusFrameDescription QQuickFocusFrameDescription::Invalid = { .target: nullptr, .margins: QQuickStyleMargins(), .radius: 0 }; | 
| 25 | QScopedPointer<QQuickItem> QQuickFocusFrame::m_focusFrame; | 
| 26 |  | 
| 27 | QQuickFocusFrame::QQuickFocusFrame() | 
| 28 | { | 
| 29 |     connect(qGuiApp, signal: &QGuiApplication::focusObjectChanged, context: this, slot: [this](QObject *focusObject){ | 
| 30 |         if (auto item = qobject_cast<QQuickItem *>(o: focusObject)) | 
| 31 |             moveToItem(item); | 
| 32 |         else | 
| 33 |             moveToItem(item: nullptr); | 
| 34 |     }); | 
| 35 | } | 
| 36 |  | 
| 37 | void QQuickFocusFrame::moveToItem(QQuickItem *item) | 
| 38 | { | 
| 39 |     if (!m_focusFrame) { | 
| 40 |         const auto context = QQmlEngine::contextForObject(item); | 
| 41 |         // In certain cases like QQuickWebEngineView, the item | 
| 42 |         // gets focus even though it has no QQmlEngine associated with its context. | 
| 43 |         // We need the engine for creating the focus frame component. | 
| 44 |         if (!context || !context->engine()) | 
| 45 |             return; | 
| 46 |         m_focusFrame.reset(other: createFocusFrame(context)); | 
| 47 |         if (!m_focusFrame) { | 
| 48 |             qWarning() << "Failed to create FocusFrame" ; | 
| 49 |             return; | 
| 50 |         } | 
| 51 |         QQuickItemPrivate::get(item: m_focusFrame.get())->setTransparentForPositioner(true); | 
| 52 |     } | 
| 53 |  | 
| 54 |     const QQuickFocusFrameDescription &config = getDescriptionForItem(focusItem: item); | 
| 55 |     QMetaObject::invokeMethod(obj: m_focusFrame.data(), member: "moveToItem" , | 
| 56 |                               Q_ARG(QVariant, QVariant::fromValue(config.target)), | 
| 57 |                               Q_ARG(QVariant, QVariant::fromValue(config.margins)), | 
| 58 |                               Q_ARG(QVariant, QVariant::fromValue(config.radius))); | 
| 59 | } | 
| 60 |  | 
| 61 | QQuickFocusFrameDescription QQuickFocusFrame::getDescriptionForItem(QQuickItem *focusItem) const | 
| 62 | { | 
| 63 |     if (!focusItem) | 
| 64 |         return QQuickFocusFrameDescription::Invalid; | 
| 65 |  | 
| 66 |     qCDebug(lcFocusFrame) << "new focusobject:"  << focusItem; | 
| 67 |     const auto parentItem = focusItem->parentItem(); | 
| 68 |     if (!parentItem) | 
| 69 |         return QQuickFocusFrameDescription::Invalid; | 
| 70 |  | 
| 71 |     // The item that gets active focus can be a child of the control (e.g | 
| 72 |     // editable ComboBox). In that case, resolve the actual control first. | 
| 73 |     const auto proxy = focusItem->property(name: "__focusFrameControl" ).value<QQuickItem *>(); | 
| 74 |     const auto control = proxy ? proxy : focusItem; | 
| 75 |     auto target = control->property(name: "__focusFrameTarget" ).value<QQuickItem *>(); | 
| 76 |     qCDebug(lcFocusFrame) << "target:"  << target; | 
| 77 |     qCDebug(lcFocusFrame) << "control:"  << control; | 
| 78 |  | 
| 79 |     if (!target) { | 
| 80 |         // __focusFrameTarget points to the item in the control that should | 
| 81 |         // get the focus frame. This is usually the control itself, but can | 
| 82 |         // sometimes be a child (CheckBox). We anyway require | 
| 83 |         // this property to be set if we are to show the focus frame around | 
| 84 |         // the control in the first place. So for controls that don't want | 
| 85 |         // a frame (ProgressBar), we simply skip setting it. | 
| 86 |         // Also, we should never show a focus frame around custom controls. | 
| 87 |         // None of the built-in styles do that, so to be consistent, we | 
| 88 |         // shouldn't either. Besides, drawing a focus frame around an unknown | 
| 89 |         // item without any way to turn it off can easily be unwanted. A better | 
| 90 |         // way for custom controls to get a native focus frame is for us to offer | 
| 91 |         // a FocusFrame control (QTBUG-86818). | 
| 92 |         return QQuickFocusFrameDescription::Invalid; | 
| 93 |     } | 
| 94 |  | 
| 95 |     // If the control gives us a QQuickStyleItem, we use that to configure the focus frame. | 
| 96 |     // By default we assume that the background delegate is a QQuickStyleItem, but the | 
| 97 |     // control can override this by setting __focusFrameStyleItem. | 
| 98 |     const auto styleItemProperty = control->property(name: "__focusFrameStyleItem" ); | 
| 99 |     auto item = styleItemProperty.value<QQuickItem *>(); | 
| 100 |     if (!item) { | 
| 101 |         const auto styleItemProperty = control->property(name: "background" ); | 
| 102 |         item = styleItemProperty.value<QQuickItem *>(); | 
| 103 |     } | 
| 104 |     qCDebug(lcFocusFrame) << "styleItem:"  << item; | 
| 105 |     if (!item) | 
| 106 |         return QQuickFocusFrameDescription::Invalid; | 
| 107 |     if (QQuickStyleItem *styleItem = qobject_cast<QQuickStyleItem *>(object: item)) | 
| 108 |         return { .target: target, .margins: QQuickStyleMargins(styleItem->layoutMargins()), .radius: styleItem->focusFrameRadius() }; | 
| 109 |  | 
| 110 |     // Some controls don't have a QQuickStyleItem. But if the __focusFrameStyleItem | 
| 111 |     // has a "__focusFrameRadius" property set, we show a default focus frame using the specified radius instead. | 
| 112 |     const QVariant focusFrameRadiusVariant = item->property(name: "__focusFrameRadius" ); | 
| 113 |     if (focusFrameRadiusVariant.isValid()) { | 
| 114 |         qCDebug(lcFocusFrame) << "'focusFrameRadius' property found, showing a default focus frame" ; | 
| 115 |         const QStyleOption opt; | 
| 116 |         const qreal radius = qMax(a: 0.0, b: focusFrameRadiusVariant.toReal()); | 
| 117 |         return { .target: target, .margins: QQuickStyleMargins(), .radius: radius }; | 
| 118 |     } | 
| 119 |  | 
| 120 |     // The application has set a custom delegate on the control. In that | 
| 121 |     // case, it's the delegates responsibility to draw a focus frame. | 
| 122 |     qCDebug(lcFocusFrame) << "custom delegates in use, skip showing focus frame" ; | 
| 123 |     return QQuickFocusFrameDescription::Invalid; | 
| 124 | } | 
| 125 |  | 
| 126 | QT_END_NAMESPACE | 
| 127 |  |