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
54QT_BEGIN_NAMESPACE
55
56#define RANGE 4
57
58static bool resizeHorizontalDirectionFixed = false;
59static 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
65QWidgetResizeHandler::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*>(object: widget);
72 range = frame ? frame->frameWidth() : RANGE;
73 range = qMax(RANGE, b: range);
74 activeForMove = activeForResize = true;
75 widget->installEventFilter(filterObj: this);
76}
77
78void 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
89bool 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
98bool 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(margins: QMargins(range, range, range, range));
123 const QPoint cursorPoint = widget->mapFromGlobal(e->globalPos());
124 if (!widgetRect.contains(p: 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(e: 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
193void 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(p: pos))
214 mode = Center;
215 else
216 mode = Nowhere;
217
218 if (widget->isMinimized() || !isActive(ac: 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(attribute: 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(p: 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(a: pp.x(), b: desktop.left());
253 pp.ry() = qMax(a: pp.y(), b: desktop.top());
254 p.rx() = qMin(a: p.x(), b: desktop.right());
255 p.ry() = qMin(a: p.y(), b: desktop.bottom());
256 }
257
258 QSize ms = qSmartMinSize(w: 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(otherSize: widget->minimumSize()).expandedTo(otherSize: QSize(mw, mh))
272 .boundedTo(otherSize: 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(p: pp);
305 break;
306 default:
307 break;
308 }
309
310 geom = QRect(geom.topLeft(),
311 geom.size().expandedTo(otherSize: widget->minimumSize())
312 .expandedTo(otherSize: QSize(mw, mh))
313 .boundedTo(otherSize: maxsize));
314
315 if (geom != widget->geometry() &&
316 (widget->isWindow() || widget->parentWidget()->rect().intersects(r: geom))) {
317 if (mode == Center)
318 widget->move(geom.topLeft());
319 else
320 widget->setGeometry(geom);
321 }
322}
323
324void 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*>(o: children.at(i))) {
332 if (!w->testAttribute(attribute: 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
362void 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
486void 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
516void 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
533QT_END_NAMESPACE
534
535#include "moc_qwidgetresizehandler_p.cpp"
536

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