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 | |
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*>(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 | |
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 | |
89 | 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 | |
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(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 | |
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(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 | |
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*>(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 | |
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 | |
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 | |
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 | |