1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtWidgets module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qfocusframe.h"
41#include "qstyle.h"
42#include "qbitmap.h"
43#include "qstylepainter.h"
44#include "qstyleoption.h"
45#include "qdebug.h"
46#include <private/qwidget_p.h>
47
48QT_BEGIN_NAMESPACE
49
50class QFocusFramePrivate : public QWidgetPrivate
51{
52 Q_DECLARE_PUBLIC(QFocusFrame)
53 QWidget *widget;
54 QWidget *frameParent;
55 bool showFrameAboveWidget;
56public:
57 QFocusFramePrivate() {
58 widget = nullptr;
59 frameParent = nullptr;
60 sendChildEvents = false;
61 showFrameAboveWidget = false;
62 }
63 void updateSize();
64 void update();
65};
66
67void QFocusFramePrivate::update()
68{
69 Q_Q(QFocusFrame);
70 q->setParent(frameParent);
71 updateSize();
72 if (q->parentWidget()->rect().intersects(r: q->geometry())) {
73 if (showFrameAboveWidget)
74 q->raise();
75 else
76 q->stackUnder(widget);
77 q->show();
78 } else {
79 q->hide();
80 }
81}
82
83void QFocusFramePrivate::updateSize()
84{
85 Q_Q(QFocusFrame);
86 if (!widget)
87 return;
88
89 QStyleOption opt;
90 q->initStyleOption(option: &opt);
91 int vmargin = q->style()->pixelMetric(metric: QStyle::PM_FocusFrameVMargin, option: &opt),
92 hmargin = q->style()->pixelMetric(metric: QStyle::PM_FocusFrameHMargin, option: &opt);
93 QPoint pos(widget->x(), widget->y());
94 if (q->parentWidget() != widget->parentWidget())
95 pos = widget->parentWidget()->mapTo(q->parentWidget(), pos);
96 QRect geom(pos.x()-hmargin, pos.y()-vmargin,
97 widget->width()+(hmargin*2), widget->height()+(vmargin*2));
98 if(q->geometry() == geom)
99 return;
100
101 q->setGeometry(geom);
102
103 opt.rect = q->rect();
104 QStyleHintReturnMask mask;
105 if (q->style()->styleHint(stylehint: QStyle::SH_FocusFrame_Mask, opt: &opt, widget: q, returnData: &mask))
106 q->setMask(mask.region);
107}
108
109/*!
110 Initialize \a option with the values from this QFocusFrame. This method is useful
111 for subclasses when they need a QStyleOption, but don't want to fill
112 in all the information themselves.
113
114 \sa QStyleOption::initFrom()
115*/
116void QFocusFrame::initStyleOption(QStyleOption *option) const
117{
118 if (!option)
119 return;
120
121 option->initFrom(w: this);
122}
123
124/*!
125 \class QFocusFrame
126 \brief The QFocusFrame widget provides a focus frame which can be
127 outside of a widget's normal paintable area.
128
129 \ingroup basicwidgets
130 \inmodule QtWidgets
131
132 Normally an application will not need to create its own
133 QFocusFrame as QStyle will handle this detail for
134 you. A style writer can optionally use a QFocusFrame to have a
135 focus area outside of the widget's paintable geometry. In this way
136 space need not be reserved for the widget to have focus but only
137 set on a QWidget with QFocusFrame::setWidget. It is, however,
138 legal to create your own QFocusFrame on a custom widget and set
139 its geometry manually via QWidget::setGeometry however you will
140 not get auto-placement when the focused widget changes size or
141 placement.
142*/
143
144/*!
145 Constructs a QFocusFrame.
146
147 The focus frame will not monitor \a parent for updates but rather
148 can be placed manually or by using QFocusFrame::setWidget. A
149 QFocusFrame sets Qt::WA_NoChildEventsForParent attribute; as a
150 result the parent will not receive a QEvent::ChildAdded event,
151 this will make it possible to manually set the geometry of the
152 QFocusFrame inside of a QSplitter or other child event monitoring
153 widget.
154
155 \sa QFocusFrame::setWidget()
156*/
157
158QFocusFrame::QFocusFrame(QWidget *parent)
159 : QWidget(*new QFocusFramePrivate, parent, { })
160{
161 setAttribute(Qt::WA_TransparentForMouseEvents);
162 setFocusPolicy(Qt::NoFocus);
163 setAttribute(Qt::WA_NoChildEventsForParent, on: true);
164 setAttribute(Qt::WA_AcceptDrops, on: style()->styleHint(stylehint: QStyle::SH_FocusFrame_AboveWidget, opt: nullptr, widget: this));
165}
166
167/*!
168 Destructor.
169*/
170
171QFocusFrame::~QFocusFrame()
172{
173}
174
175/*!
176 QFocusFrame will track changes to \a widget and resize itself automatically.
177 If the monitored widget's parent changes, QFocusFrame will follow the widget
178 and place itself around the widget automatically. If the monitored widget is deleted,
179 QFocusFrame will set it to zero.
180
181 \sa QFocusFrame::widget()
182*/
183
184void
185QFocusFrame::setWidget(QWidget *widget)
186{
187 Q_D(QFocusFrame);
188
189 if (style()->styleHint(stylehint: QStyle::SH_FocusFrame_AboveWidget, opt: nullptr, widget: this))
190 d->showFrameAboveWidget = true;
191 else
192 d->showFrameAboveWidget = false;
193
194 if (widget == d->widget)
195 return;
196 if (d->widget) {
197 // Remove event filters from the widget hierarchy.
198 QWidget *p = d->widget;
199 do {
200 p->removeEventFilter(obj: this);
201 if (!d->showFrameAboveWidget || p == d->frameParent)
202 break;
203 p = p->parentWidget();
204 }while (p);
205 }
206 if (widget && !widget->isWindow() && widget->parentWidget()->windowType() != Qt::SubWindow) {
207 d->widget = widget;
208 d->widget->installEventFilter(filterObj: this);
209 QWidget *p = widget->parentWidget();
210 QWidget *prev = nullptr;
211 if (d->showFrameAboveWidget) {
212 // Find the right parent for the focus frame.
213 while (p) {
214 // Traverse the hirerarchy of the 'widget' for setting event filter.
215 // During this if come across toolbar or a top level, use that
216 // as the parent for the focus frame. If we find a scroll area
217 // use its viewport as the parent.
218 bool isScrollArea = false;
219 if (p->isWindow() || p->inherits(classname: "QToolBar") || (isScrollArea = p->inherits(classname: "QAbstractScrollArea"))) {
220 d->frameParent = p;
221 // The previous one in the hierarchy will be the viewport.
222 if (prev && isScrollArea)
223 d->frameParent = prev;
224 break;
225 } else {
226 p->installEventFilter(filterObj: this);
227 prev = p;
228 p = p->parentWidget();
229 }
230 }
231 } else {
232 d->frameParent = p;
233 }
234 d->update();
235 } else {
236 d->widget = nullptr;
237 hide();
238 }
239}
240
241/*!
242 Returns the currently monitored widget for automatically resize and
243 update.
244
245 \sa QFocusFrame::setWidget()
246*/
247
248QWidget *
249QFocusFrame::widget() const
250{
251 Q_D(const QFocusFrame);
252 return d->widget;
253}
254
255
256/*! \reimp */
257void
258QFocusFrame::paintEvent(QPaintEvent *)
259{
260 Q_D(QFocusFrame);
261
262 if (!d->widget)
263 return;
264
265 QStylePainter p(this);
266 QStyleOption option;
267 initStyleOption(option: &option);
268 const int vmargin = style()->pixelMetric(metric: QStyle::PM_FocusFrameVMargin, option: &option);
269 const int hmargin = style()->pixelMetric(metric: QStyle::PM_FocusFrameHMargin, option: &option);
270 QWidgetPrivate *wd = qt_widget_private(widget: d->widget);
271 QRect rect = wd->clipRect().adjusted(xp1: 0, yp1: 0, xp2: hmargin*2, yp2: vmargin*2);
272 p.setClipRect(rect);
273 p.drawControl(ce: QStyle::CE_FocusFrame, opt: option);
274}
275
276
277/*! \reimp */
278bool
279QFocusFrame::eventFilter(QObject *o, QEvent *e)
280{
281 Q_D(QFocusFrame);
282 if(o == d->widget) {
283 switch(e->type()) {
284 case QEvent::Move:
285 case QEvent::Resize:
286 d->updateSize();
287 break;
288 case QEvent::Hide:
289 case QEvent::StyleChange:
290 hide();
291 break;
292 case QEvent::ParentChange:
293 if (d->showFrameAboveWidget) {
294 QWidget *w = d->widget;
295 setWidget(nullptr);
296 setWidget(w);
297 } else {
298 d->update();
299 }
300 break;
301 case QEvent::Show:
302 d->update();
303 show();
304 break;
305 case QEvent::PaletteChange:
306 setPalette(d->widget->palette());
307 break;
308 case QEvent::ZOrderChange:
309 if (style()->styleHint(stylehint: QStyle::SH_FocusFrame_AboveWidget, opt: nullptr, widget: this))
310 raise();
311 else
312 stackUnder(d->widget);
313 break;
314 case QEvent::Destroy:
315 setWidget(nullptr);
316 break;
317 default:
318 break;
319 }
320 } else if (d->showFrameAboveWidget) {
321 // Handle changes in the parent widgets we are monitoring.
322 switch(e->type()) {
323 case QEvent::Move:
324 case QEvent::Resize:
325 d->updateSize();
326 break;
327 case QEvent::ZOrderChange:
328 raise();
329 break;
330 default:
331 break;
332 }
333 }
334 return false;
335}
336
337/*! \reimp */
338bool QFocusFrame::event(QEvent *e)
339{
340 Q_D(QFocusFrame);
341
342 switch (e->type()) {
343 case QEvent::Move:
344 case QEvent::Resize:
345 if (d->widget) {
346 // When we're tracking a widget, we don't allow anyone to move the focus frame around.
347 // We do our best with event filters to make it stay on top of the widget, so trying to
348 // move the frame somewhere else will be flaky at best. This can e.g happen for general
349 // purpose code, like QAbstractScrollView, that bulk-moves all children a certain distance.
350 // So we need to call updateSize() when that happens to ensure that the focus frame stays
351 // on top of the widget.
352 d->updateSize();
353 }
354 break;
355 default:
356 return QWidget::event(event: e);
357 }
358 return true;
359}
360
361QT_END_NAMESPACE
362
363#include "moc_qfocusframe.cpp"
364

source code of qtbase/src/widgets/widgets/qfocusframe.cpp