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 
48 QT_BEGIN_NAMESPACE
49 
50 class QFocusFramePrivate : public QWidgetPrivate
51 {
52     Q_DECLARE_PUBLIC(QFocusFrame)
53     QWidget *widget;
54     QWidget *frameParent;
55     bool showFrameAboveWidget;
56 public:
QFocusFramePrivate()57     QFocusFramePrivate() {
58         widget = nullptr;
59         frameParent = nullptr;
60         sendChildEvents = false;
61         showFrameAboveWidget = false;
62     }
63     void updateSize();
64     void update();
65 };
66 
update()67 void QFocusFramePrivate::update()
68 {
69     Q_Q(QFocusFrame);
70     q->setParent(frameParent);
71     updateSize();
72     if (q->parentWidget()->rect().intersects(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 
updateSize()83 void QFocusFramePrivate::updateSize()
84 {
85     Q_Q(QFocusFrame);
86     if (!widget)
87         return;
88 
89     QStyleOption opt;
90     q->initStyleOption(&opt);
91     int vmargin = q->style()->pixelMetric(QStyle::PM_FocusFrameVMargin, &opt),
92         hmargin = q->style()->pixelMetric(QStyle::PM_FocusFrameHMargin, &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(QStyle::SH_FocusFrame_Mask, &opt, q, &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 */
initStyleOption(QStyleOption * option) const116 void QFocusFrame::initStyleOption(QStyleOption *option) const
117 {
118     if (!option)
119         return;
120 
121     option->initFrom(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 
QFocusFrame(QWidget * parent)158 QFocusFrame::QFocusFrame(QWidget *parent)
159     : QWidget(*new QFocusFramePrivate, parent, { })
160 {
161     setAttribute(Qt::WA_TransparentForMouseEvents);
162     setFocusPolicy(Qt::NoFocus);
163     setAttribute(Qt::WA_NoChildEventsForParent, true);
164     setAttribute(Qt::WA_AcceptDrops, style()->styleHint(QStyle::SH_FocusFrame_AboveWidget, nullptr, this));
165 }
166 
167 /*!
168     Destructor.
169 */
170 
~QFocusFrame()171 QFocusFrame::~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 
184 void
setWidget(QWidget * widget)185 QFocusFrame::setWidget(QWidget *widget)
186 {
187     Q_D(QFocusFrame);
188 
189     if (style()->styleHint(QStyle::SH_FocusFrame_AboveWidget, nullptr, 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(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(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("QToolBar") || (isScrollArea = p->inherits("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(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 
248 QWidget *
widget() const249 QFocusFrame::widget() const
250 {
251     Q_D(const QFocusFrame);
252     return d->widget;
253 }
254 
255 
256 /*! \reimp */
257 void
paintEvent(QPaintEvent *)258 QFocusFrame::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);
268     const int vmargin = style()->pixelMetric(QStyle::PM_FocusFrameVMargin, &option);
269     const int hmargin = style()->pixelMetric(QStyle::PM_FocusFrameHMargin, &option);
270     QWidgetPrivate *wd = qt_widget_private(d->widget);
271     QRect rect = wd->clipRect().adjusted(0, 0, hmargin*2, vmargin*2);
272     p.setClipRect(rect);
273     p.drawControl(QStyle::CE_FocusFrame, option);
274 }
275 
276 
277 /*! \reimp */
278 bool
eventFilter(QObject * o,QEvent * e)279 QFocusFrame::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(QStyle::SH_FocusFrame_AboveWidget, nullptr, 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 */
event(QEvent * e)338 bool 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(e);
357     }
358     return true;
359 }
360 
361 QT_END_NAMESPACE
362 
363 #include "moc_qfocusframe.cpp"
364