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