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 "qsizegrip.h"
41
42#include "qapplication.h"
43#include "qevent.h"
44#include "qpainter.h"
45#include "qwindow.h"
46#include <qpa/qplatformwindow.h>
47#include "qstyle.h"
48#include "qstyleoption.h"
49#include "qlayout.h"
50#include "qdebug.h"
51#include <QDesktopWidget>
52
53#include <private/qwidget_p.h>
54#include <private/qdesktopwidget_p.h>
55#include <QtWidgets/qabstractscrollarea.h>
56
57QT_BEGIN_NAMESPACE
58
59static QWidget *qt_sizegrip_topLevelWidget(QWidget* w)
60{
61 while (w && !w->isWindow() && w->windowType() != Qt::SubWindow)
62 w = w->parentWidget();
63 return w;
64}
65
66class QSizeGripPrivate : public QWidgetPrivate
67{
68 Q_DECLARE_PUBLIC(QSizeGrip)
69public:
70 QSizeGripPrivate();
71 void init();
72 QPoint p;
73 QRect r;
74 int d;
75 int dxMax;
76 int dyMax;
77 Qt::Corner m_corner;
78 bool gotMousePress;
79 QPointer<QWidget> tlw;
80
81 Qt::Corner corner() const;
82 inline bool atBottom() const
83 {
84 return m_corner == Qt::BottomRightCorner || m_corner == Qt::BottomLeftCorner;
85 }
86
87 inline bool atLeft() const
88 {
89 return m_corner == Qt::BottomLeftCorner || m_corner == Qt::TopLeftCorner;
90 }
91
92 void updateTopLevelWidget()
93 {
94 Q_Q(QSizeGrip);
95 QWidget *w = qt_sizegrip_topLevelWidget(w: q);
96 if (tlw == w)
97 return;
98 if (tlw)
99 tlw->removeEventFilter(obj: q);
100 tlw = w;
101 if (tlw)
102 tlw->installEventFilter(filterObj: q);
103 }
104
105 // This slot is invoked by QLayout when the size grip is added to
106 // a layout or reparented after the tlw is shown. This re-implementation is basically
107 // the same as QWidgetPrivate::_q_showIfNotHidden except that it checks
108 // for Qt::WindowFullScreen and Qt::WindowMaximized as well.
109 void _q_showIfNotHidden()
110 {
111 Q_Q(QSizeGrip);
112 bool showSizeGrip = !(q->isHidden() && q->testAttribute(attribute: Qt::WA_WState_ExplicitShowHide));
113 updateTopLevelWidget();
114 if (tlw && showSizeGrip) {
115 Qt::WindowStates sizeGripNotVisibleState = Qt::WindowFullScreen;
116 sizeGripNotVisibleState |= Qt::WindowMaximized;
117 // Don't show the size grip if the tlw is maximized or in full screen mode.
118 showSizeGrip = !(tlw->windowState() & sizeGripNotVisibleState);
119 }
120 if (showSizeGrip)
121 q->setVisible(true);
122 }
123
124 bool m_platformSizeGrip;
125};
126
127QSizeGripPrivate::QSizeGripPrivate()
128 : dxMax(0)
129 , dyMax(0)
130 , gotMousePress(false)
131 , tlw(nullptr)
132 , m_platformSizeGrip(false)
133{
134}
135
136Qt::Corner QSizeGripPrivate::corner() const
137{
138 Q_Q(const QSizeGrip);
139 QWidget *tlw = qt_sizegrip_topLevelWidget(w: const_cast<QSizeGrip *>(q));
140 const QPoint sizeGripPos = q->mapTo(tlw, QPoint(0, 0));
141 bool isAtBottom = sizeGripPos.y() >= tlw->height() / 2;
142 bool isAtLeft = sizeGripPos.x() <= tlw->width() / 2;
143 if (isAtLeft)
144 return isAtBottom ? Qt::BottomLeftCorner : Qt::TopLeftCorner;
145 else
146 return isAtBottom ? Qt::BottomRightCorner : Qt::TopRightCorner;
147}
148
149/*!
150 \class QSizeGrip
151
152 \brief The QSizeGrip class provides a resize handle for resizing top-level windows.
153
154 \ingroup mainwindow-classes
155 \ingroup basicwidgets
156 \inmodule QtWidgets
157
158 This widget works like the standard Windows resize handle. In the
159 X11 version this resize handle generally works differently from
160 the one provided by the system if the X11 window manager does not
161 support necessary modern post-ICCCM specifications.
162
163 Put this widget anywhere in a widget tree and the user can use it
164 to resize the top-level window or any widget with the Qt::SubWindow
165 flag set. Generally, this should be in the lower right-hand corner.
166
167 Note that QStatusBar already uses this widget, so if you have a
168 status bar (e.g., you are using QMainWindow), then you don't need
169 to use this widget explicitly. The same goes for QDialog, for which
170 you can just call \l {QDialog::setSizeGripEnabled()}
171 {QDialog::setSizeGripEnabled()}.
172
173 On some platforms the size grip automatically hides itself when the
174 window is shown full screen or maximised.
175
176 \table 50%
177 \row \li \inlineimage fusion-statusbar-sizegrip.png Screenshot of a Fusion style size grip
178 \li A size grip widget at the bottom-right corner of a main window, shown in the
179 \l{Qt Widget Gallery}{Fusion widget style}.
180 \endtable
181
182 The QSizeGrip class inherits QWidget and reimplements the \l
183 {QWidget::mousePressEvent()}{mousePressEvent()} and \l
184 {QWidget::mouseMoveEvent()}{mouseMoveEvent()} functions to feature
185 the resize functionality, and the \l
186 {QWidget::paintEvent()}{paintEvent()} function to render the
187 size grip widget.
188
189 \sa QStatusBar, QWidget::windowState()
190*/
191
192
193/*!
194 Constructs a resize corner as a child widget of the given \a
195 parent.
196*/
197QSizeGrip::QSizeGrip(QWidget * parent)
198 : QWidget(*new QSizeGripPrivate, parent, { })
199{
200 Q_D(QSizeGrip);
201 d->init();
202}
203
204
205void QSizeGripPrivate::init()
206{
207 Q_Q(QSizeGrip);
208 m_corner = q->isLeftToRight() ? Qt::BottomRightCorner : Qt::BottomLeftCorner;
209
210#if !defined(QT_NO_CURSOR)
211 q->setCursor(m_corner == Qt::TopLeftCorner || m_corner == Qt::BottomRightCorner
212 ? Qt::SizeFDiagCursor : Qt::SizeBDiagCursor);
213#endif
214 q->setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed));
215 updateTopLevelWidget();
216}
217
218
219/*!
220 Destroys this size grip.
221*/
222QSizeGrip::~QSizeGrip()
223{
224}
225
226/*!
227 \reimp
228*/
229QSize QSizeGrip::sizeHint() const
230{
231 QStyleOption opt(0);
232 opt.init(w: this);
233 return (style()->sizeFromContents(ct: QStyle::CT_SizeGrip, opt: &opt, contentsSize: QSize(13, 13), w: this).
234 expandedTo(otherSize: QApplication::globalStrut()));
235}
236
237/*!
238 Paints the resize grip.
239
240 Resize grips are usually rendered as small diagonal textured lines
241 in the lower-right corner. The paint event is passed in the \a
242 event parameter.
243*/
244void QSizeGrip::paintEvent(QPaintEvent *event)
245{
246 Q_UNUSED(event);
247 Q_D(QSizeGrip);
248 QPainter painter(this);
249 QStyleOptionSizeGrip opt;
250 opt.init(w: this);
251 opt.corner = d->m_corner;
252 style()->drawControl(element: QStyle::CE_SizeGrip, opt: &opt, p: &painter, w: this);
253}
254
255/*!
256 \fn void QSizeGrip::mousePressEvent(QMouseEvent * event)
257
258 Receives the mouse press events for the widget, and primes the
259 resize operation. The mouse press event is passed in the \a event
260 parameter.
261*/
262
263static Qt::Edges edgesFromCorner(Qt::Corner corner)
264{
265 switch (corner) {
266 case Qt::TopLeftCorner: return Qt::TopEdge | Qt::LeftEdge;
267 case Qt::TopRightCorner: return Qt::TopEdge | Qt::RightEdge;
268 case Qt::BottomLeftCorner: return Qt::BottomEdge | Qt::LeftEdge;
269 case Qt::BottomRightCorner: return Qt::BottomEdge | Qt::RightEdge;
270 }
271 return Qt::Edges{};
272}
273
274static bool usePlatformSizeGrip(const QWidget *tlw)
275{
276 const QString &platformName = QGuiApplication::platformName();
277 if (platformName.contains(s: u"xcb")) // ### FIXME QTBUG-69716
278 return false;
279 if (tlw->testAttribute(attribute: Qt::WA_TranslucentBackground)
280 && platformName == u"windows") {
281 return false; // QTBUG-90628, flicker when using translucency
282 }
283 return true;
284}
285
286void QSizeGrip::mousePressEvent(QMouseEvent * e)
287{
288 if (e->button() != Qt::LeftButton) {
289 QWidget::mousePressEvent(event: e);
290 return;
291 }
292
293 Q_D(QSizeGrip);
294 QWidget *tlw = qt_sizegrip_topLevelWidget(w: this);
295 d->p = e->globalPos();
296 d->gotMousePress = true;
297 d->r = tlw->geometry();
298
299 // Does the platform provide size grip support?
300 d->m_platformSizeGrip = false;
301 if (tlw->isWindow()
302 && tlw->windowHandle()
303 && !(tlw->windowFlags() & Qt::X11BypassWindowManagerHint)
304 && !tlw->testAttribute(attribute: Qt::WA_DontShowOnScreen)
305 && !tlw->hasHeightForWidth()
306 && usePlatformSizeGrip(tlw)) {
307 QPlatformWindow *platformWindow = tlw->windowHandle()->handle();
308 const Qt::Edges edges = edgesFromCorner(corner: d->m_corner);
309 d->m_platformSizeGrip = platformWindow->startSystemResize(edges);
310 }
311
312 if (d->m_platformSizeGrip)
313 return;
314
315 // Find available desktop/workspace geometry.
316 QRect availableGeometry;
317 bool hasVerticalSizeConstraint = true;
318 bool hasHorizontalSizeConstraint = true;
319 if (tlw->isWindow())
320 availableGeometry = QDesktopWidgetPrivate::availableGeometry(widget: tlw);
321 else {
322 const QWidget *tlwParent = tlw->parentWidget();
323 // Check if tlw is inside QAbstractScrollArea/QScrollArea.
324 // If that's the case tlw->parentWidget() will return the viewport
325 // and tlw->parentWidget()->parentWidget() will return the scroll area.
326#if QT_CONFIG(scrollarea)
327 QAbstractScrollArea *scrollArea = qobject_cast<QAbstractScrollArea *>(object: tlwParent->parentWidget());
328 if (scrollArea) {
329 hasHorizontalSizeConstraint = scrollArea->horizontalScrollBarPolicy() == Qt::ScrollBarAlwaysOff;
330 hasVerticalSizeConstraint = scrollArea->verticalScrollBarPolicy() == Qt::ScrollBarAlwaysOff;
331 }
332#endif // QT_CONFIG(scrollarea)
333 availableGeometry = tlwParent->contentsRect();
334 }
335
336 // Find frame geometries, title bar height, and decoration sizes.
337 const QRect frameGeometry = tlw->frameGeometry();
338 const int titleBarHeight = qMax(a: tlw->geometry().y() - frameGeometry.y(), b: 0);
339 const int bottomDecoration = qMax(a: frameGeometry.height() - tlw->height() - titleBarHeight, b: 0);
340 const int leftRightDecoration = qMax(a: (frameGeometry.width() - tlw->width()) / 2, b: 0);
341
342 // Determine dyMax depending on whether the sizegrip is at the bottom
343 // of the widget or not.
344 if (d->atBottom()) {
345 if (hasVerticalSizeConstraint)
346 d->dyMax = availableGeometry.bottom() - d->r.bottom() - bottomDecoration;
347 else
348 d->dyMax = INT_MAX;
349 } else {
350 if (hasVerticalSizeConstraint)
351 d->dyMax = availableGeometry.y() - d->r.y() + titleBarHeight;
352 else
353 d->dyMax = -INT_MAX;
354 }
355
356 // In RTL mode, the size grip is to the left; find dxMax from the desktop/workspace
357 // geometry, the size grip geometry and the width of the decoration.
358 if (d->atLeft()) {
359 if (hasHorizontalSizeConstraint)
360 d->dxMax = availableGeometry.x() - d->r.x() + leftRightDecoration;
361 else
362 d->dxMax = -INT_MAX;
363 } else {
364 if (hasHorizontalSizeConstraint)
365 d->dxMax = availableGeometry.right() - d->r.right() - leftRightDecoration;
366 else
367 d->dxMax = INT_MAX;
368 }
369}
370
371
372/*!
373 \fn void QSizeGrip::mouseMoveEvent(QMouseEvent * event)
374 Resizes the top-level widget containing this widget. The mouse
375 move event is passed in the \a event parameter.
376*/
377void QSizeGrip::mouseMoveEvent(QMouseEvent * e)
378{
379 Q_D(QSizeGrip);
380 if (e->buttons() != Qt::LeftButton || d->m_platformSizeGrip) {
381 QWidget::mouseMoveEvent(event: e);
382 return;
383 }
384
385 QWidget* tlw = qt_sizegrip_topLevelWidget(w: this);
386 if (!d->gotMousePress || tlw->testAttribute(attribute: Qt::WA_WState_ConfigPending))
387 return;
388
389 QPoint np(e->globalPos());
390
391 // Don't extend beyond the available geometry; bound to dyMax and dxMax.
392 QSize ns;
393 if (d->atBottom())
394 ns.rheight() = d->r.height() + qMin(a: np.y() - d->p.y(), b: d->dyMax);
395 else
396 ns.rheight() = d->r.height() - qMax(a: np.y() - d->p.y(), b: d->dyMax);
397
398 if (d->atLeft())
399 ns.rwidth() = d->r.width() - qMax(a: np.x() - d->p.x(), b: d->dxMax);
400 else
401 ns.rwidth() = d->r.width() + qMin(a: np.x() - d->p.x(), b: d->dxMax);
402
403 ns = QLayout::closestAcceptableSize(w: tlw, s: ns);
404
405 QPoint p;
406 QRect nr(p, ns);
407 if (d->atBottom()) {
408 if (d->atLeft())
409 nr.moveTopRight(p: d->r.topRight());
410 else
411 nr.moveTopLeft(p: d->r.topLeft());
412 } else {
413 if (d->atLeft())
414 nr.moveBottomRight(p: d->r.bottomRight());
415 else
416 nr.moveBottomLeft(p: d->r.bottomLeft());
417 }
418
419 tlw->setGeometry(nr);
420}
421
422/*!
423 \reimp
424*/
425void QSizeGrip::mouseReleaseEvent(QMouseEvent *mouseEvent)
426{
427 if (mouseEvent->button() == Qt::LeftButton) {
428 Q_D(QSizeGrip);
429 d->gotMousePress = false;
430 d->p = QPoint();
431 } else {
432 QWidget::mouseReleaseEvent(event: mouseEvent);
433 }
434}
435
436/*!
437 \reimp
438*/
439void QSizeGrip::moveEvent(QMoveEvent * /*moveEvent*/)
440{
441 Q_D(QSizeGrip);
442 // We're inside a resize operation; no update necessary.
443 if (!d->p.isNull())
444 return;
445
446 d->m_corner = d->corner();
447#if !defined(QT_NO_CURSOR)
448 setCursor(d->m_corner == Qt::TopLeftCorner || d->m_corner == Qt::BottomRightCorner
449 ? Qt::SizeFDiagCursor : Qt::SizeBDiagCursor);
450#endif
451}
452
453/*!
454 \reimp
455*/
456void QSizeGrip::showEvent(QShowEvent *showEvent)
457{
458 QWidget::showEvent(event: showEvent);
459}
460
461/*!
462 \reimp
463*/
464void QSizeGrip::hideEvent(QHideEvent *hideEvent)
465{
466 QWidget::hideEvent(event: hideEvent);
467}
468
469/*!
470 \reimp
471*/
472void QSizeGrip::setVisible(bool visible)
473{
474 QWidget::setVisible(visible);
475}
476
477/*! \reimp */
478bool QSizeGrip::eventFilter(QObject *o, QEvent *e)
479{
480 Q_D(QSizeGrip);
481 if ((isHidden() && testAttribute(attribute: Qt::WA_WState_ExplicitShowHide))
482 || e->type() != QEvent::WindowStateChange
483 || o != d->tlw) {
484 return QWidget::eventFilter(watched: o, event: e);
485 }
486 Qt::WindowStates sizeGripNotVisibleState = Qt::WindowFullScreen;
487 sizeGripNotVisibleState |= Qt::WindowMaximized;
488 // Don't show the size grip if the tlw is maximized or in full screen mode.
489 setVisible(!(d->tlw->windowState() & sizeGripNotVisibleState));
490 setAttribute(Qt::WA_WState_ExplicitShowHide, on: false);
491 return QWidget::eventFilter(watched: o, event: e);
492}
493
494/*!
495 \reimp
496*/
497bool QSizeGrip::event(QEvent *event)
498{
499 return QWidget::event(event);
500}
501
502QT_END_NAMESPACE
503
504#include "moc_qsizegrip.cpp"
505

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