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
20QT_BEGIN_NAMESPACE
21
22Q_LOGGING_CATEGORY(lcFocusFrame, "qt.quick.controls.focusframe")
23
24QQuickFocusFrameDescription QQuickFocusFrameDescription::Invalid = { .target: nullptr, .margins: QQuickStyleMargins(), .radius: 0 };
25QScopedPointer<QQuickItem> QQuickFocusFrame::m_focusFrame;
26
27QQuickFocusFrame::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
37void 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
61QQuickFocusFrameDescription 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
126QT_END_NAMESPACE
127

source code of qtdeclarative/src/quicknativestyle/util/qquickfocusframe.cpp