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

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