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

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

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