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 | |
17 | QT_BEGIN_NAMESPACE |
18 | |
19 | using namespace Qt::StringLiterals; |
20 | |
21 | #define RANGE 4 |
22 | |
23 | static bool resizeHorizontalDirectionFixed = false; |
24 | static bool resizeVerticalDirectionFixed = false; |
25 | |
26 | QWidgetResizeHandler::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 | |
39 | void QWidgetResizeHandler::setEnabled(bool b) |
40 | { |
41 | if (b == enabled) |
42 | return; |
43 | |
44 | enabled = b; |
45 | if (!enabled) |
46 | setMouseCursor(Nowhere); |
47 | } |
48 | |
49 | bool QWidgetResizeHandler::isEnabled() const |
50 | { |
51 | return enabled; |
52 | } |
53 | |
54 | bool 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 | |
131 | void 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 | |
256 | void 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 | |
294 | void 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 | |
418 | void 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 | |
448 | QT_END_NAMESPACE |
449 | |
450 | #include "moc_qwidgetresizehandler_p.cpp" |
451 | |