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 "qwidgetresizehandler_p.h"
41 
42 #include "qframe.h"
43 #include "qapplication.h"
44 #include "qdesktopwidget.h"
45 #include <private/qdesktopwidget_p.h>
46 #include "qcursor.h"
47 #if QT_CONFIG(sizegrip)
48 #include "qsizegrip.h"
49 #endif
50 #include "qevent.h"
51 #include "qdebug.h"
52 #include "private/qlayoutengine_p.h"
53 
54 QT_BEGIN_NAMESPACE
55 
56 #define RANGE 4
57 
58 static bool resizeHorizontalDirectionFixed = false;
59 static bool resizeVerticalDirectionFixed = false;
60 
61 // ### fixme: Qt 6: No longer export QWidgetResizeHandler and remove "Move"
62 // functionality. Currently, only the resize functionality is used by QDockWidget.
63 // Historically, the class was used in Qt 3's QWorkspace (predecessor to QMdiArea).
64 
QWidgetResizeHandler(QWidget * parent,QWidget * cw)65 QWidgetResizeHandler::QWidgetResizeHandler(QWidget *parent, QWidget *cw)
66     : QObject(parent), widget(parent), childWidget(cw ? cw : parent),
67       fw(0), extrahei(0), buttonDown(false), moveResizeMode(false), sizeprotect(true), movingEnabled(true)
68 {
69     mode = Nowhere;
70     widget->setMouseTracking(true);
71     QFrame *frame = qobject_cast<QFrame*>(widget);
72     range = frame ? frame->frameWidth() : RANGE;
73     range = qMax(RANGE, range);
74     activeForMove = activeForResize = true;
75     widget->installEventFilter(this);
76 }
77 
setActive(Action ac,bool b)78 void QWidgetResizeHandler::setActive(Action ac, bool b)
79 {
80     if (ac & Move)
81         activeForMove = b;
82     if (ac & Resize)
83         activeForResize = b;
84 
85     if (!isActive())
86         setMouseCursor(Nowhere);
87 }
88 
isActive(Action ac) const89 bool QWidgetResizeHandler::isActive(Action ac) const
90 {
91     bool b = false;
92     if (ac & Move) b = activeForMove;
93     if (ac & Resize) b |= activeForResize;
94 
95     return b;
96 }
97 
eventFilter(QObject * o,QEvent * ee)98 bool QWidgetResizeHandler::eventFilter(QObject *o, QEvent *ee)
99 {
100     if (!isActive()
101         || (ee->type() != QEvent::MouseButtonPress
102             && ee->type() != QEvent::MouseButtonRelease
103             && ee->type() != QEvent::MouseMove
104             && ee->type() != QEvent::KeyPress
105             && ee->type() != QEvent::ShortcutOverride)
106         )
107         return false;
108 
109     Q_ASSERT(o == widget);
110     QWidget *w = widget;
111     if (QApplication::activePopupWidget()) {
112         if (buttonDown && ee->type() == QEvent::MouseButtonRelease)
113             buttonDown = false;
114         return false;
115     }
116 
117     switch (ee->type()) {
118     case QEvent::MouseButtonPress: {
119         QMouseEvent *e = static_cast<QMouseEvent *>(ee);
120         if (w->isMaximized())
121             break;
122         const QRect widgetRect = widget->rect().marginsAdded(QMargins(range, range, range, range));
123         const QPoint cursorPoint = widget->mapFromGlobal(e->globalPos());
124         if (!widgetRect.contains(cursorPoint))
125             return false;
126         if (e->button() == Qt::LeftButton) {
127             buttonDown = false;
128             emit activate();
129             bool me = movingEnabled;
130             movingEnabled = (me && o == widget);
131             mouseMoveEvent(e);
132             movingEnabled = me;
133             buttonDown = true;
134             moveOffset = widget->mapFromGlobal(e->globalPos());
135             invertedMoveOffset = widget->rect().bottomRight() - moveOffset;
136             if (mode == Center) {
137                 if (movingEnabled)
138                     return true;
139             } else {
140                 return true;
141             }
142         }
143     } break;
144     case QEvent::MouseButtonRelease:
145         if (w->isMaximized())
146             break;
147         if (static_cast<QMouseEvent *>(ee)->button() == Qt::LeftButton) {
148             moveResizeMode = false;
149             buttonDown = false;
150             widget->releaseMouse();
151             widget->releaseKeyboard();
152             if (mode == Center) {
153                 if (movingEnabled)
154                     return true;
155             } else {
156                 return true;
157             }
158         }
159         break;
160     case QEvent::MouseMove: {
161         if (w->isMaximized())
162             break;
163         QMouseEvent *e = static_cast<QMouseEvent *>(ee);
164         buttonDown = buttonDown && (e->buttons() & Qt::LeftButton); // safety, state machine broken!
165         bool me = movingEnabled;
166         movingEnabled = (me && o == widget && (buttonDown || moveResizeMode));
167         mouseMoveEvent(e);
168         movingEnabled = me;
169         if (mode == Center) {
170             if (movingEnabled)
171                 return true;
172         } else {
173             return true;
174         }
175     } break;
176     case QEvent::KeyPress:
177         keyPressEvent(static_cast<QKeyEvent *>(ee));
178         break;
179     case QEvent::ShortcutOverride:
180         buttonDown &= ((QGuiApplication::mouseButtons() & Qt::LeftButton) != Qt::NoButton);
181         if (buttonDown) {
182             ee->accept();
183             return true;
184         }
185         break;
186     default:
187         break;
188     }
189 
190     return false;
191 }
192 
mouseMoveEvent(QMouseEvent * e)193 void QWidgetResizeHandler::mouseMoveEvent(QMouseEvent *e)
194 {
195     QPoint pos = widget->mapFromGlobal(e->globalPos());
196     if (!moveResizeMode && !buttonDown) {
197         if (pos.y() <= range && pos.x() <= range)
198             mode = TopLeft;
199         else if (pos.y() >= widget->height()-range && pos.x() >= widget->width()-range)
200             mode = BottomRight;
201         else if (pos.y() >= widget->height()-range && pos.x() <= range)
202             mode = BottomLeft;
203         else if (pos.y() <= range && pos.x() >= widget->width()-range)
204             mode = TopRight;
205         else if (pos.y() <= range)
206             mode = Top;
207         else if (pos.y() >= widget->height()-range)
208             mode = Bottom;
209         else if (pos.x() <= range)
210             mode = Left;
211         else if ( pos.x() >= widget->width()-range)
212             mode = Right;
213         else if (widget->rect().contains(pos))
214             mode = Center;
215         else
216             mode = Nowhere;
217 
218         if (widget->isMinimized() || !isActive(Resize))
219             mode = Center;
220 #ifndef QT_NO_CURSOR
221         setMouseCursor(mode);
222 #endif
223         return;
224     }
225 
226     if (mode == Center && !movingEnabled)
227         return;
228 
229     if (widget->testAttribute(Qt::WA_WState_ConfigPending))
230         return;
231 
232 
233     QPoint globalPos = (!widget->isWindow() && widget->parentWidget()) ?
234                        widget->parentWidget()->mapFromGlobal(e->globalPos()) : e->globalPos();
235     if (!widget->isWindow() && !widget->parentWidget()->rect().contains(globalPos)) {
236         if (globalPos.x() < 0)
237             globalPos.rx() = 0;
238         if (globalPos.y() < 0)
239             globalPos.ry() = 0;
240         if (sizeprotect && globalPos.x() > widget->parentWidget()->width())
241             globalPos.rx() = widget->parentWidget()->width();
242         if (sizeprotect && globalPos.y() > widget->parentWidget()->height())
243             globalPos.ry() = widget->parentWidget()->height();
244     }
245 
246     QPoint p = globalPos + invertedMoveOffset;
247     QPoint pp = globalPos - moveOffset;
248 
249     // Workaround for window managers which refuse to move a tool window partially offscreen.
250     if (QGuiApplication::platformName() == QLatin1String("xcb")) {
251         const QRect desktop = QDesktopWidgetPrivate::availableGeometry(widget);
252         pp.rx() = qMax(pp.x(), desktop.left());
253         pp.ry() = qMax(pp.y(), desktop.top());
254         p.rx() = qMin(p.x(), desktop.right());
255         p.ry() = qMin(p.y(), desktop.bottom());
256     }
257 
258     QSize ms = qSmartMinSize(childWidget);
259     int mw = ms.width();
260     int mh = ms.height();
261     if (childWidget != widget) {
262         mw += 2 * fw;
263         mh += 2 * fw + extrahei;
264     }
265 
266     QSize maxsize(childWidget->maximumSize());
267     if (childWidget != widget)
268         maxsize += QSize(2 * fw, 2 * fw + extrahei);
269     QSize mpsize(widget->geometry().right() - pp.x() + 1,
270                   widget->geometry().bottom() - pp.y() + 1);
271     mpsize = mpsize.expandedTo(widget->minimumSize()).expandedTo(QSize(mw, mh))
272                     .boundedTo(maxsize);
273     QPoint mp(widget->geometry().right() - mpsize.width() + 1,
274                widget->geometry().bottom() - mpsize.height() + 1);
275 
276     QRect geom = widget->geometry();
277 
278     switch (mode) {
279     case TopLeft:
280         geom = QRect(mp, widget->geometry().bottomRight()) ;
281         break;
282     case BottomRight:
283         geom = QRect(widget->geometry().topLeft(), p) ;
284         break;
285     case BottomLeft:
286         geom = QRect(QPoint(mp.x(), widget->geometry().y()), QPoint(widget->geometry().right(), p.y())) ;
287         break;
288     case TopRight:
289         geom = QRect(QPoint(widget->geometry().x(), mp.y()), QPoint(p.x(), widget->geometry().bottom())) ;
290         break;
291     case Top:
292         geom = QRect(QPoint(widget->geometry().left(), mp.y()), widget->geometry().bottomRight()) ;
293         break;
294     case Bottom:
295         geom = QRect(widget->geometry().topLeft(), QPoint(widget->geometry().right(), p.y())) ;
296         break;
297     case Left:
298         geom = QRect(QPoint(mp.x(), widget->geometry().top()), widget->geometry().bottomRight()) ;
299         break;
300     case Right:
301         geom = QRect(widget->geometry().topLeft(), QPoint(p.x(), widget->geometry().bottom())) ;
302         break;
303     case Center:
304         geom.moveTopLeft(pp);
305         break;
306     default:
307         break;
308     }
309 
310     geom = QRect(geom.topLeft(),
311                   geom.size().expandedTo(widget->minimumSize())
312                              .expandedTo(QSize(mw, mh))
313                              .boundedTo(maxsize));
314 
315     if (geom != widget->geometry() &&
316         (widget->isWindow() || widget->parentWidget()->rect().intersects(geom))) {
317         if (mode == Center)
318             widget->move(geom.topLeft());
319         else
320             widget->setGeometry(geom);
321     }
322 }
323 
setMouseCursor(MousePosition m)324 void QWidgetResizeHandler::setMouseCursor(MousePosition m)
325 {
326 #ifdef QT_NO_CURSOR
327     Q_UNUSED(m);
328 #else
329     QObjectList children = widget->children();
330     for (int i = 0; i < children.size(); ++i) {
331         if (QWidget *w = qobject_cast<QWidget*>(children.at(i))) {
332             if (!w->testAttribute(Qt::WA_SetCursor)) {
333                 w->setCursor(Qt::ArrowCursor);
334             }
335         }
336     }
337 
338     switch (m) {
339     case TopLeft:
340     case BottomRight:
341         widget->setCursor(Qt::SizeFDiagCursor);
342         break;
343     case BottomLeft:
344     case TopRight:
345         widget->setCursor(Qt::SizeBDiagCursor);
346         break;
347     case Top:
348     case Bottom:
349         widget->setCursor(Qt::SizeVerCursor);
350         break;
351     case Left:
352     case Right:
353         widget->setCursor(Qt::SizeHorCursor);
354         break;
355     default:
356         widget->setCursor(Qt::ArrowCursor);
357         break;
358     }
359 #endif // QT_NO_CURSOR
360 }
361 
keyPressEvent(QKeyEvent * e)362 void QWidgetResizeHandler::keyPressEvent(QKeyEvent * e)
363 {
364     if (!isMove() && !isResize())
365         return;
366     bool is_control = e->modifiers() & Qt::ControlModifier;
367     int delta = is_control?1:8;
368     QPoint pos = QCursor::pos();
369     switch (e->key()) {
370     case Qt::Key_Left:
371         pos.rx() -= delta;
372         if (pos.x() <= QDesktopWidgetPrivate::geometry().left()) {
373             if (mode == TopLeft || mode == BottomLeft) {
374                 moveOffset.rx() += delta;
375                 invertedMoveOffset.rx() += delta;
376             } else {
377                 moveOffset.rx() -= delta;
378                 invertedMoveOffset.rx() -= delta;
379             }
380         }
381         if (isResize() && !resizeHorizontalDirectionFixed) {
382             resizeHorizontalDirectionFixed = true;
383             if (mode == BottomRight)
384                 mode = BottomLeft;
385             else if (mode == TopRight)
386                 mode = TopLeft;
387 #ifndef QT_NO_CURSOR
388             setMouseCursor(mode);
389             widget->grabMouse(widget->cursor());
390 #else
391             widget->grabMouse();
392 #endif
393         }
394         break;
395     case Qt::Key_Right:
396         pos.rx() += delta;
397         if (pos.x() >= QDesktopWidgetPrivate::geometry().right()) {
398             if (mode == TopRight || mode == BottomRight) {
399                 moveOffset.rx() += delta;
400                 invertedMoveOffset.rx() += delta;
401             } else {
402                 moveOffset.rx() -= delta;
403                 invertedMoveOffset.rx() -= delta;
404             }
405         }
406         if (isResize() && !resizeHorizontalDirectionFixed) {
407             resizeHorizontalDirectionFixed = true;
408             if (mode == BottomLeft)
409                 mode = BottomRight;
410             else if (mode == TopLeft)
411                 mode = TopRight;
412 #ifndef QT_NO_CURSOR
413             setMouseCursor(mode);
414             widget->grabMouse(widget->cursor());
415 #else
416             widget->grabMouse();
417 #endif
418         }
419         break;
420     case Qt::Key_Up:
421         pos.ry() -= delta;
422         if (pos.y() <= QDesktopWidgetPrivate::geometry().top()) {
423             if (mode == TopLeft || mode == TopRight) {
424                 moveOffset.ry() += delta;
425                 invertedMoveOffset.ry() += delta;
426             } else {
427                 moveOffset.ry() -= delta;
428                 invertedMoveOffset.ry() -= delta;
429             }
430         }
431         if (isResize() && !resizeVerticalDirectionFixed) {
432             resizeVerticalDirectionFixed = true;
433             if (mode == BottomLeft)
434                 mode = TopLeft;
435             else if (mode == BottomRight)
436                 mode = TopRight;
437 #ifndef QT_NO_CURSOR
438             setMouseCursor(mode);
439             widget->grabMouse(widget->cursor());
440 #else
441             widget->grabMouse();
442 #endif
443         }
444         break;
445     case Qt::Key_Down:
446         pos.ry() += delta;
447         if (pos.y() >= QDesktopWidgetPrivate::geometry().bottom()) {
448             if (mode == BottomLeft || mode == BottomRight) {
449                 moveOffset.ry() += delta;
450                 invertedMoveOffset.ry() += delta;
451             } else {
452                 moveOffset.ry() -= delta;
453                 invertedMoveOffset.ry() -= delta;
454             }
455         }
456         if (isResize() && !resizeVerticalDirectionFixed) {
457             resizeVerticalDirectionFixed = true;
458             if (mode == TopLeft)
459                 mode = BottomLeft;
460             else if (mode == TopRight)
461                 mode = BottomRight;
462 #ifndef QT_NO_CURSOR
463             setMouseCursor(mode);
464             widget->grabMouse(widget->cursor());
465 #else
466             widget->grabMouse();
467 #endif
468         }
469         break;
470     case Qt::Key_Space:
471     case Qt::Key_Return:
472     case Qt::Key_Enter:
473     case Qt::Key_Escape:
474         moveResizeMode = false;
475         widget->releaseMouse();
476         widget->releaseKeyboard();
477         buttonDown = false;
478         break;
479     default:
480         return;
481     }
482     QCursor::setPos(pos);
483 }
484 
485 
doResize()486 void QWidgetResizeHandler::doResize()
487 {
488     if (!activeForResize)
489         return;
490 
491     moveResizeMode = true;
492     moveOffset = widget->mapFromGlobal(QCursor::pos());
493     if (moveOffset.x() < widget->width()/2) {
494         if (moveOffset.y() < widget->height()/2)
495             mode = TopLeft;
496         else
497             mode = BottomLeft;
498     } else {
499         if (moveOffset.y() < widget->height()/2)
500             mode = TopRight;
501         else
502             mode = BottomRight;
503     }
504     invertedMoveOffset = widget->rect().bottomRight() - moveOffset;
505 #ifndef QT_NO_CURSOR
506     setMouseCursor(mode);
507     widget->grabMouse(widget->cursor() );
508 #else
509     widget->grabMouse();
510 #endif
511     widget->grabKeyboard();
512     resizeHorizontalDirectionFixed = false;
513     resizeVerticalDirectionFixed = false;
514 }
515 
doMove()516 void QWidgetResizeHandler::doMove()
517 {
518     if (!activeForMove)
519         return;
520 
521     mode = Center;
522     moveResizeMode = true;
523     moveOffset = widget->mapFromGlobal(QCursor::pos());
524     invertedMoveOffset = widget->rect().bottomRight() - moveOffset;
525 #ifndef QT_NO_CURSOR
526     widget->grabMouse(Qt::SizeAllCursor);
527 #else
528     widget->grabMouse();
529 #endif
530     widget->grabKeyboard();
531 }
532 
533 QT_END_NAMESPACE
534 
535 #include "moc_qwidgetresizehandler_p.cpp"
536