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: |
57 | QFocusFramePrivate() { |
58 | widget = nullptr; |
59 | frameParent = nullptr; |
60 | sendChildEvents = false; |
61 | showFrameAboveWidget = false; |
62 | } |
63 | void updateSize(); |
64 | void update(); |
65 | }; |
66 | |
67 | void 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 | |
83 | void 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 | */ |
116 | void 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 | |
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, 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 | |
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 |
185 | QFocusFrame::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 | |
248 | QWidget * |
249 | QFocusFrame::widget() const |
250 | { |
251 | Q_D(const QFocusFrame); |
252 | return d->widget; |
253 | } |
254 | |
255 | |
256 | /*! \reimp */ |
257 | void |
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: &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 */ |
278 | bool |
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(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 */ |
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(event: e); |
357 | } |
358 | return true; |
359 | } |
360 | |
361 | QT_END_NAMESPACE |
362 | |
363 | #include "moc_qfocusframe.cpp" |
364 | |