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
57 QT_BEGIN_NAMESPACE
58
qt_sizegrip_topLevelWidget(QWidget * w)59 static 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
66 class QSizeGripPrivate : public QWidgetPrivate
67 {
68 Q_DECLARE_PUBLIC(QSizeGrip)
69 public:
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;
atBottom() const82 inline bool atBottom() const
83 {
84 return m_corner == Qt::BottomRightCorner || m_corner == Qt::BottomLeftCorner;
85 }
86
atLeft() const87 inline bool atLeft() const
88 {
89 return m_corner == Qt::BottomLeftCorner || m_corner == Qt::TopLeftCorner;
90 }
91
updateTopLevelWidget()92 void updateTopLevelWidget()
93 {
94 Q_Q(QSizeGrip);
95 QWidget *w = qt_sizegrip_topLevelWidget(q);
96 if (tlw == w)
97 return;
98 if (tlw)
99 tlw->removeEventFilter(q);
100 tlw = w;
101 if (tlw)
102 tlw->installEventFilter(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.
_q_showIfNotHidden()109 void _q_showIfNotHidden()
110 {
111 Q_Q(QSizeGrip);
112 bool showSizeGrip = !(q->isHidden() && q->testAttribute(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
QSizeGripPrivate()127 QSizeGripPrivate::QSizeGripPrivate()
128 : dxMax(0)
129 , dyMax(0)
130 , gotMousePress(false)
131 , tlw(nullptr)
132 , m_platformSizeGrip(false)
133 {
134 }
135
corner() const136 Qt::Corner QSizeGripPrivate::corner() const
137 {
138 Q_Q(const QSizeGrip);
139 QWidget *tlw = qt_sizegrip_topLevelWidget(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 */
QSizeGrip(QWidget * parent)197 QSizeGrip::QSizeGrip(QWidget * parent)
198 : QWidget(*new QSizeGripPrivate, parent, { })
199 {
200 Q_D(QSizeGrip);
201 d->init();
202 }
203
204
init()205 void 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 */
~QSizeGrip()222 QSizeGrip::~QSizeGrip()
223 {
224 }
225
226 /*!
227 \reimp
228 */
sizeHint() const229 QSize QSizeGrip::sizeHint() const
230 {
231 QStyleOption opt(0);
232 opt.init(this);
233 return (style()->sizeFromContents(QStyle::CT_SizeGrip, &opt, QSize(13, 13), this).
234 expandedTo(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 */
paintEvent(QPaintEvent * event)244 void QSizeGrip::paintEvent(QPaintEvent *event)
245 {
246 Q_UNUSED(event);
247 Q_D(QSizeGrip);
248 QPainter painter(this);
249 QStyleOptionSizeGrip opt;
250 opt.init(this);
251 opt.corner = d->m_corner;
252 style()->drawControl(QStyle::CE_SizeGrip, &opt, &painter, 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
edgesFromCorner(Qt::Corner corner)263 static 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
mousePressEvent(QMouseEvent * e)274 void QSizeGrip::mousePressEvent(QMouseEvent * e)
275 {
276 if (e->button() != Qt::LeftButton) {
277 QWidget::mousePressEvent(e);
278 return;
279 }
280
281 Q_D(QSizeGrip);
282 QWidget *tlw = qt_sizegrip_topLevelWidget(this);
283 d->p = e->globalPos();
284 d->gotMousePress = true;
285 d->r = tlw->geometry();
286
287 // Does the platform provide size grip support?
288 d->m_platformSizeGrip = false;
289 if (tlw->isWindow()
290 && tlw->windowHandle()
291 && !(tlw->windowFlags() & Qt::X11BypassWindowManagerHint)
292 && !tlw->testAttribute(Qt::WA_DontShowOnScreen)
293 && !tlw->hasHeightForWidth()) {
294 QPlatformWindow *platformWindow = tlw->windowHandle()->handle();
295 const Qt::Edges edges = edgesFromCorner(d->m_corner);
296 if (!QGuiApplication::platformName().contains(QStringLiteral("xcb"))) // ### FIXME QTBUG-69716
297 d->m_platformSizeGrip = platformWindow && platformWindow->startSystemResize(edges);
298 }
299
300 if (d->m_platformSizeGrip)
301 return;
302
303 // Find available desktop/workspace geometry.
304 QRect availableGeometry;
305 bool hasVerticalSizeConstraint = true;
306 bool hasHorizontalSizeConstraint = true;
307 if (tlw->isWindow())
308 availableGeometry = QDesktopWidgetPrivate::availableGeometry(tlw);
309 else {
310 const QWidget *tlwParent = tlw->parentWidget();
311 // Check if tlw is inside QAbstractScrollArea/QScrollArea.
312 // If that's the case tlw->parentWidget() will return the viewport
313 // and tlw->parentWidget()->parentWidget() will return the scroll area.
314 #if QT_CONFIG(scrollarea)
315 QAbstractScrollArea *scrollArea = qobject_cast<QAbstractScrollArea *>(tlwParent->parentWidget());
316 if (scrollArea) {
317 hasHorizontalSizeConstraint = scrollArea->horizontalScrollBarPolicy() == Qt::ScrollBarAlwaysOff;
318 hasVerticalSizeConstraint = scrollArea->verticalScrollBarPolicy() == Qt::ScrollBarAlwaysOff;
319 }
320 #endif // QT_CONFIG(scrollarea)
321 availableGeometry = tlwParent->contentsRect();
322 }
323
324 // Find frame geometries, title bar height, and decoration sizes.
325 const QRect frameGeometry = tlw->frameGeometry();
326 const int titleBarHeight = qMax(tlw->geometry().y() - frameGeometry.y(), 0);
327 const int bottomDecoration = qMax(frameGeometry.height() - tlw->height() - titleBarHeight, 0);
328 const int leftRightDecoration = qMax((frameGeometry.width() - tlw->width()) / 2, 0);
329
330 // Determine dyMax depending on whether the sizegrip is at the bottom
331 // of the widget or not.
332 if (d->atBottom()) {
333 if (hasVerticalSizeConstraint)
334 d->dyMax = availableGeometry.bottom() - d->r.bottom() - bottomDecoration;
335 else
336 d->dyMax = INT_MAX;
337 } else {
338 if (hasVerticalSizeConstraint)
339 d->dyMax = availableGeometry.y() - d->r.y() + titleBarHeight;
340 else
341 d->dyMax = -INT_MAX;
342 }
343
344 // In RTL mode, the size grip is to the left; find dxMax from the desktop/workspace
345 // geometry, the size grip geometry and the width of the decoration.
346 if (d->atLeft()) {
347 if (hasHorizontalSizeConstraint)
348 d->dxMax = availableGeometry.x() - d->r.x() + leftRightDecoration;
349 else
350 d->dxMax = -INT_MAX;
351 } else {
352 if (hasHorizontalSizeConstraint)
353 d->dxMax = availableGeometry.right() - d->r.right() - leftRightDecoration;
354 else
355 d->dxMax = INT_MAX;
356 }
357 }
358
359
360 /*!
361 \fn void QSizeGrip::mouseMoveEvent(QMouseEvent * event)
362 Resizes the top-level widget containing this widget. The mouse
363 move event is passed in the \a event parameter.
364 */
mouseMoveEvent(QMouseEvent * e)365 void QSizeGrip::mouseMoveEvent(QMouseEvent * e)
366 {
367 Q_D(QSizeGrip);
368 if (e->buttons() != Qt::LeftButton || d->m_platformSizeGrip) {
369 QWidget::mouseMoveEvent(e);
370 return;
371 }
372
373 QWidget* tlw = qt_sizegrip_topLevelWidget(this);
374 if (!d->gotMousePress || tlw->testAttribute(Qt::WA_WState_ConfigPending))
375 return;
376
377 QPoint np(e->globalPos());
378
379 // Don't extend beyond the available geometry; bound to dyMax and dxMax.
380 QSize ns;
381 if (d->atBottom())
382 ns.rheight() = d->r.height() + qMin(np.y() - d->p.y(), d->dyMax);
383 else
384 ns.rheight() = d->r.height() - qMax(np.y() - d->p.y(), d->dyMax);
385
386 if (d->atLeft())
387 ns.rwidth() = d->r.width() - qMax(np.x() - d->p.x(), d->dxMax);
388 else
389 ns.rwidth() = d->r.width() + qMin(np.x() - d->p.x(), d->dxMax);
390
391 ns = QLayout::closestAcceptableSize(tlw, ns);
392
393 QPoint p;
394 QRect nr(p, ns);
395 if (d->atBottom()) {
396 if (d->atLeft())
397 nr.moveTopRight(d->r.topRight());
398 else
399 nr.moveTopLeft(d->r.topLeft());
400 } else {
401 if (d->atLeft())
402 nr.moveBottomRight(d->r.bottomRight());
403 else
404 nr.moveBottomLeft(d->r.bottomLeft());
405 }
406
407 tlw->setGeometry(nr);
408 }
409
410 /*!
411 \reimp
412 */
mouseReleaseEvent(QMouseEvent * mouseEvent)413 void QSizeGrip::mouseReleaseEvent(QMouseEvent *mouseEvent)
414 {
415 if (mouseEvent->button() == Qt::LeftButton) {
416 Q_D(QSizeGrip);
417 d->gotMousePress = false;
418 d->p = QPoint();
419 } else {
420 QWidget::mouseReleaseEvent(mouseEvent);
421 }
422 }
423
424 /*!
425 \reimp
426 */
moveEvent(QMoveEvent *)427 void QSizeGrip::moveEvent(QMoveEvent * /*moveEvent*/)
428 {
429 Q_D(QSizeGrip);
430 // We're inside a resize operation; no update necessary.
431 if (!d->p.isNull())
432 return;
433
434 d->m_corner = d->corner();
435 #if !defined(QT_NO_CURSOR)
436 setCursor(d->m_corner == Qt::TopLeftCorner || d->m_corner == Qt::BottomRightCorner
437 ? Qt::SizeFDiagCursor : Qt::SizeBDiagCursor);
438 #endif
439 }
440
441 /*!
442 \reimp
443 */
showEvent(QShowEvent * showEvent)444 void QSizeGrip::showEvent(QShowEvent *showEvent)
445 {
446 QWidget::showEvent(showEvent);
447 }
448
449 /*!
450 \reimp
451 */
hideEvent(QHideEvent * hideEvent)452 void QSizeGrip::hideEvent(QHideEvent *hideEvent)
453 {
454 QWidget::hideEvent(hideEvent);
455 }
456
457 /*!
458 \reimp
459 */
setVisible(bool visible)460 void QSizeGrip::setVisible(bool visible)
461 {
462 QWidget::setVisible(visible);
463 }
464
465 /*! \reimp */
eventFilter(QObject * o,QEvent * e)466 bool QSizeGrip::eventFilter(QObject *o, QEvent *e)
467 {
468 Q_D(QSizeGrip);
469 if ((isHidden() && testAttribute(Qt::WA_WState_ExplicitShowHide))
470 || e->type() != QEvent::WindowStateChange
471 || o != d->tlw) {
472 return QWidget::eventFilter(o, e);
473 }
474 Qt::WindowStates sizeGripNotVisibleState = Qt::WindowFullScreen;
475 sizeGripNotVisibleState |= Qt::WindowMaximized;
476 // Don't show the size grip if the tlw is maximized or in full screen mode.
477 setVisible(!(d->tlw->windowState() & sizeGripNotVisibleState));
478 setAttribute(Qt::WA_WState_ExplicitShowHide, false);
479 return QWidget::eventFilter(o, e);
480 }
481
482 /*!
483 \reimp
484 */
event(QEvent * event)485 bool QSizeGrip::event(QEvent *event)
486 {
487 return QWidget::event(event);
488 }
489
490 QT_END_NAMESPACE
491
492 #include "moc_qsizegrip.cpp"
493