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