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

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