1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qwidgetresizehandler_p.h"
5
6#include "qframe.h"
7#include "qapplication.h"
8#include "private/qwidget_p.h"
9#include "qcursor.h"
10#if QT_CONFIG(sizegrip)
11#include "qsizegrip.h"
12#endif
13#include "qevent.h"
14#include "qdebug.h"
15#include "private/qlayoutengine_p.h"
16
17QT_BEGIN_NAMESPACE
18
19using namespace Qt::StringLiterals;
20
21#define RANGE 4
22
23static bool resizeHorizontalDirectionFixed = false;
24static bool resizeVerticalDirectionFixed = false;
25
26QWidgetResizeHandler::QWidgetResizeHandler(QWidget *parent, QWidget *cw)
27 : QObject(parent), widget(parent), childWidget(cw ? cw : parent),
28 fw(0), extrahei(0), buttonDown(false), active(false)
29{
30 mode = Nowhere;
31 widget->setMouseTracking(true);
32 QFrame *frame = qobject_cast<QFrame*>(object: widget);
33 range = frame ? frame->frameWidth() : RANGE;
34 range = qMax(RANGE, b: range);
35 enabled = true;
36 widget->installEventFilter(filterObj: this);
37}
38
39void QWidgetResizeHandler::setEnabled(bool b)
40{
41 if (b == enabled)
42 return;
43
44 enabled = b;
45 if (!enabled)
46 setMouseCursor(Nowhere);
47}
48
49bool QWidgetResizeHandler::isEnabled() const
50{
51 return enabled;
52}
53
54bool QWidgetResizeHandler::eventFilter(QObject *o, QEvent *ee)
55{
56 if (!isEnabled()
57 || (ee->type() != QEvent::MouseButtonPress
58 && ee->type() != QEvent::MouseButtonRelease
59 && ee->type() != QEvent::MouseMove
60 && ee->type() != QEvent::KeyPress
61 && ee->type() != QEvent::ShortcutOverride)
62 )
63 return false;
64
65 Q_ASSERT(o == widget);
66 QWidget *w = widget;
67 if (QApplication::activePopupWidget()) {
68 if (buttonDown && ee->type() == QEvent::MouseButtonRelease)
69 buttonDown = false;
70 return false;
71 }
72
73 switch (ee->type()) {
74 case QEvent::MouseButtonPress: {
75 QMouseEvent *e = static_cast<QMouseEvent *>(ee);
76 if (w->isMaximized())
77 break;
78 const QRect widgetRect = widget->rect().marginsAdded(margins: QMargins(range, range, range, range));
79 const QPoint cursorPoint = widget->mapFromGlobal(e->globalPosition().toPoint());
80 if (!widgetRect.contains(p: cursorPoint))
81 return false;
82 if (e->button() == Qt::LeftButton) {
83 buttonDown = false;
84 emit activate();
85 mouseMoveEvent(e);
86 buttonDown = true;
87 moveOffset = widget->mapFromGlobal(e->globalPosition().toPoint());
88 invertedMoveOffset = widget->rect().bottomRight() - moveOffset;
89 if (mode != Center)
90 return true;
91 }
92 } break;
93 case QEvent::MouseButtonRelease:
94 if (w->isMaximized())
95 break;
96 if (static_cast<QMouseEvent *>(ee)->button() == Qt::LeftButton) {
97 active = false;
98 buttonDown = false;
99 widget->releaseMouse();
100 widget->releaseKeyboard();
101 if (mode != Center)
102 return true;
103 }
104 break;
105 case QEvent::MouseMove: {
106 if (w->isMaximized())
107 break;
108 QMouseEvent *e = static_cast<QMouseEvent *>(ee);
109 buttonDown = buttonDown && (e->buttons() & Qt::LeftButton); // safety, state machine broken!
110 mouseMoveEvent(e);
111 if (mode != Center)
112 return true;
113 } break;
114 case QEvent::KeyPress:
115 keyPressEvent(e: static_cast<QKeyEvent *>(ee));
116 break;
117 case QEvent::ShortcutOverride:
118 buttonDown &= ((QGuiApplication::mouseButtons() & Qt::LeftButton) != Qt::NoButton);
119 if (buttonDown) {
120 ee->accept();
121 return true;
122 }
123 break;
124 default:
125 break;
126 }
127
128 return false;
129}
130
131void QWidgetResizeHandler::mouseMoveEvent(QMouseEvent *e)
132{
133 QPoint pos = widget->mapFromGlobal(e->globalPosition().toPoint());
134 if (!active && !buttonDown) {
135 if (pos.y() <= range && pos.x() <= range)
136 mode = TopLeft;
137 else if (pos.y() >= widget->height()-range && pos.x() >= widget->width()-range)
138 mode = BottomRight;
139 else if (pos.y() >= widget->height()-range && pos.x() <= range)
140 mode = BottomLeft;
141 else if (pos.y() <= range && pos.x() >= widget->width()-range)
142 mode = TopRight;
143 else if (pos.y() <= range)
144 mode = Top;
145 else if (pos.y() >= widget->height()-range)
146 mode = Bottom;
147 else if (pos.x() <= range)
148 mode = Left;
149 else if ( pos.x() >= widget->width()-range)
150 mode = Right;
151 else if (widget->rect().contains(p: pos))
152 mode = Center;
153 else
154 mode = Nowhere;
155
156 if (widget->isMinimized() || !isEnabled())
157 mode = Center;
158#ifndef QT_NO_CURSOR
159 setMouseCursor(mode);
160#endif
161 return;
162 }
163
164 if (mode == Center)
165 return;
166
167 if (widget->testAttribute(attribute: Qt::WA_WState_ConfigPending))
168 return;
169
170
171 QPoint globalPos = (!widget->isWindow() && widget->parentWidget()) ?
172 widget->parentWidget()->mapFromGlobal(e->globalPosition().toPoint()) : e->globalPosition().toPoint();
173 if (!widget->isWindow() && !widget->parentWidget()->rect().contains(p: globalPos)) {
174 if (globalPos.x() < 0)
175 globalPos.rx() = 0;
176 if (globalPos.y() < 0)
177 globalPos.ry() = 0;
178 if (globalPos.x() > widget->parentWidget()->width())
179 globalPos.rx() = widget->parentWidget()->width();
180 if (globalPos.y() > widget->parentWidget()->height())
181 globalPos.ry() = widget->parentWidget()->height();
182 }
183
184 QPoint p = globalPos + invertedMoveOffset;
185 QPoint pp = globalPos - moveOffset;
186
187 // Workaround for window managers which refuse to move a tool window partially offscreen.
188 if (QGuiApplication::platformName() == "xcb"_L1) {
189 const QRect desktop = QWidgetPrivate::availableScreenGeometry(widget);
190 pp.rx() = qMax(a: pp.x(), b: desktop.left());
191 pp.ry() = qMax(a: pp.y(), b: desktop.top());
192 p.rx() = qMin(a: p.x(), b: desktop.right());
193 p.ry() = qMin(a: p.y(), b: desktop.bottom());
194 }
195
196 QSize ms = qSmartMinSize(w: childWidget);
197 int mw = ms.width();
198 int mh = ms.height();
199 if (childWidget != widget) {
200 mw += 2 * fw;
201 mh += 2 * fw + extrahei;
202 }
203
204 QSize maxsize(childWidget->maximumSize());
205 if (childWidget != widget)
206 maxsize += QSize(2 * fw, 2 * fw + extrahei);
207 QSize mpsize(widget->geometry().right() - pp.x() + 1,
208 widget->geometry().bottom() - pp.y() + 1);
209 mpsize = mpsize.expandedTo(otherSize: widget->minimumSize()).expandedTo(otherSize: QSize(mw, mh))
210 .boundedTo(otherSize: maxsize);
211 QPoint mp(widget->geometry().right() - mpsize.width() + 1,
212 widget->geometry().bottom() - mpsize.height() + 1);
213
214 QRect geom = widget->geometry();
215
216 switch (mode) {
217 case TopLeft:
218 geom = QRect(mp, widget->geometry().bottomRight()) ;
219 break;
220 case BottomRight:
221 geom = QRect(widget->geometry().topLeft(), p) ;
222 break;
223 case BottomLeft:
224 geom = QRect(QPoint(mp.x(), widget->geometry().y()), QPoint(widget->geometry().right(), p.y())) ;
225 break;
226 case TopRight:
227 geom = QRect(QPoint(widget->geometry().x(), mp.y()), QPoint(p.x(), widget->geometry().bottom())) ;
228 break;
229 case Top:
230 geom = QRect(QPoint(widget->geometry().left(), mp.y()), widget->geometry().bottomRight()) ;
231 break;
232 case Bottom:
233 geom = QRect(widget->geometry().topLeft(), QPoint(widget->geometry().right(), p.y())) ;
234 break;
235 case Left:
236 geom = QRect(QPoint(mp.x(), widget->geometry().top()), widget->geometry().bottomRight()) ;
237 break;
238 case Right:
239 geom = QRect(widget->geometry().topLeft(), QPoint(p.x(), widget->geometry().bottom())) ;
240 break;
241 default:
242 break;
243 }
244
245 geom = QRect(geom.topLeft(),
246 geom.size().expandedTo(otherSize: widget->minimumSize())
247 .expandedTo(otherSize: QSize(mw, mh))
248 .boundedTo(otherSize: maxsize));
249
250 if (geom != widget->geometry() &&
251 (widget->isWindow() || widget->parentWidget()->rect().intersects(r: geom))) {
252 widget->setGeometry(geom);
253 }
254}
255
256void QWidgetResizeHandler::setMouseCursor(MousePosition m)
257{
258#ifdef QT_NO_CURSOR
259 Q_UNUSED(m);
260#else
261 QObjectList children = widget->children();
262 for (int i = 0; i < children.size(); ++i) {
263 if (QWidget *w = qobject_cast<QWidget*>(o: children.at(i))) {
264 if (!w->testAttribute(attribute: Qt::WA_SetCursor)) {
265 w->setCursor(Qt::ArrowCursor);
266 }
267 }
268 }
269
270 switch (m) {
271 case TopLeft:
272 case BottomRight:
273 widget->setCursor(Qt::SizeFDiagCursor);
274 break;
275 case BottomLeft:
276 case TopRight:
277 widget->setCursor(Qt::SizeBDiagCursor);
278 break;
279 case Top:
280 case Bottom:
281 widget->setCursor(Qt::SizeVerCursor);
282 break;
283 case Left:
284 case Right:
285 widget->setCursor(Qt::SizeHorCursor);
286 break;
287 default:
288 widget->setCursor(Qt::ArrowCursor);
289 break;
290 }
291#endif // QT_NO_CURSOR
292}
293
294void QWidgetResizeHandler::keyPressEvent(QKeyEvent * e)
295{
296 if (!isResizing())
297 return;
298 bool is_control = e->modifiers() & Qt::ControlModifier;
299 int delta = is_control?1:8;
300 QPoint pos = QCursor::pos();
301 switch (e->key()) {
302 case Qt::Key_Left:
303 pos.rx() -= delta;
304 if (pos.x() <= QGuiApplication::primaryScreen()->virtualGeometry().left()) {
305 if (mode == TopLeft || mode == BottomLeft) {
306 moveOffset.rx() += delta;
307 invertedMoveOffset.rx() += delta;
308 } else {
309 moveOffset.rx() -= delta;
310 invertedMoveOffset.rx() -= delta;
311 }
312 }
313 if (isResizing() && !resizeHorizontalDirectionFixed) {
314 resizeHorizontalDirectionFixed = true;
315 if (mode == BottomRight)
316 mode = BottomLeft;
317 else if (mode == TopRight)
318 mode = TopLeft;
319#ifndef QT_NO_CURSOR
320 setMouseCursor(mode);
321 widget->grabMouse(widget->cursor());
322#else
323 widget->grabMouse();
324#endif
325 }
326 break;
327 case Qt::Key_Right:
328 pos.rx() += delta;
329 if (pos.x() >= QGuiApplication::primaryScreen()->virtualGeometry().right()) {
330 if (mode == TopRight || mode == BottomRight) {
331 moveOffset.rx() += delta;
332 invertedMoveOffset.rx() += delta;
333 } else {
334 moveOffset.rx() -= delta;
335 invertedMoveOffset.rx() -= delta;
336 }
337 }
338 if (isResizing() && !resizeHorizontalDirectionFixed) {
339 resizeHorizontalDirectionFixed = true;
340 if (mode == BottomLeft)
341 mode = BottomRight;
342 else if (mode == TopLeft)
343 mode = TopRight;
344#ifndef QT_NO_CURSOR
345 setMouseCursor(mode);
346 widget->grabMouse(widget->cursor());
347#else
348 widget->grabMouse();
349#endif
350 }
351 break;
352 case Qt::Key_Up:
353 pos.ry() -= delta;
354 if (pos.y() <= QGuiApplication::primaryScreen()->virtualGeometry().top()) {
355 if (mode == TopLeft || mode == TopRight) {
356 moveOffset.ry() += delta;
357 invertedMoveOffset.ry() += delta;
358 } else {
359 moveOffset.ry() -= delta;
360 invertedMoveOffset.ry() -= delta;
361 }
362 }
363 if (isResizing() && !resizeVerticalDirectionFixed) {
364 resizeVerticalDirectionFixed = true;
365 if (mode == BottomLeft)
366 mode = TopLeft;
367 else if (mode == BottomRight)
368 mode = TopRight;
369#ifndef QT_NO_CURSOR
370 setMouseCursor(mode);
371 widget->grabMouse(widget->cursor());
372#else
373 widget->grabMouse();
374#endif
375 }
376 break;
377 case Qt::Key_Down:
378 pos.ry() += delta;
379 if (pos.y() >= QGuiApplication::primaryScreen()->virtualGeometry().bottom()) {
380 if (mode == BottomLeft || mode == BottomRight) {
381 moveOffset.ry() += delta;
382 invertedMoveOffset.ry() += delta;
383 } else {
384 moveOffset.ry() -= delta;
385 invertedMoveOffset.ry() -= delta;
386 }
387 }
388 if (isResizing() && !resizeVerticalDirectionFixed) {
389 resizeVerticalDirectionFixed = true;
390 if (mode == TopLeft)
391 mode = BottomLeft;
392 else if (mode == TopRight)
393 mode = BottomRight;
394#ifndef QT_NO_CURSOR
395 setMouseCursor(mode);
396 widget->grabMouse(widget->cursor());
397#else
398 widget->grabMouse();
399#endif
400 }
401 break;
402 case Qt::Key_Space:
403 case Qt::Key_Return:
404 case Qt::Key_Enter:
405 case Qt::Key_Escape:
406 active = false;
407 widget->releaseMouse();
408 widget->releaseKeyboard();
409 buttonDown = false;
410 break;
411 default:
412 return;
413 }
414 QCursor::setPos(pos);
415}
416
417
418void QWidgetResizeHandler::doResize()
419{
420 if (!enabled)
421 return;
422
423 active = true;
424 moveOffset = widget->mapFromGlobal(QCursor::pos());
425 if (moveOffset.x() < widget->width()/2) {
426 if (moveOffset.y() < widget->height()/2)
427 mode = TopLeft;
428 else
429 mode = BottomLeft;
430 } else {
431 if (moveOffset.y() < widget->height()/2)
432 mode = TopRight;
433 else
434 mode = BottomRight;
435 }
436 invertedMoveOffset = widget->rect().bottomRight() - moveOffset;
437#ifndef QT_NO_CURSOR
438 setMouseCursor(mode);
439 widget->grabMouse(widget->cursor() );
440#else
441 widget->grabMouse();
442#endif
443 widget->grabKeyboard();
444 resizeHorizontalDirectionFixed = false;
445 resizeVerticalDirectionFixed = false;
446}
447
448QT_END_NAMESPACE
449
450#include "moc_qwidgetresizehandler_p.cpp"
451

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