1// Copyright (C) 2020 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 "private/qwindow_p.h"
5#include "qwidgetwindow_p.h"
6#include "qlayout.h"
7
8#include "private/qwidget_p.h"
9#include "private/qapplication_p.h"
10#if QT_CONFIG(accessibility)
11#include <QtGui/qaccessible.h>
12#endif
13#include <private/qwidgetrepaintmanager_p.h>
14#include <qpa/qwindowsysteminterface_p.h>
15#include <qpa/qplatformtheme.h>
16#include <qpa/qplatformwindow.h>
17#include <private/qgesturemanager_p.h>
18#include <private/qhighdpiscaling_p.h>
19
20QT_BEGIN_NAMESPACE
21
22using namespace Qt::StringLiterals;
23
24Q_WIDGETS_EXPORT extern bool qt_tab_all_widgets();
25
26Q_WIDGETS_EXPORT QWidget *qt_button_down = nullptr; // widget got last button-down
27
28// popup control
29QWidget *qt_popup_down = nullptr; // popup that contains the pressed widget
30extern int openPopupCount;
31bool qt_popup_down_closed = false; // qt_popup_down has been closed
32bool qt_replay_popup_mouse_event = false;
33extern bool qt_try_modal(QWidget *widget, QEvent::Type type);
34
35class QWidgetWindowPrivate : public QWindowPrivate
36{
37 Q_DECLARE_PUBLIC(QWidgetWindow)
38public:
39 void setVisible(bool visible) override
40 {
41 Q_Q(QWidgetWindow);
42 if (QWidget *widget = q->widget()) {
43 // Check if the widget was already hidden, as this indicates it was done
44 // explicitly and not because the parent window in this case made it hidden.
45 // In which case do not automatically show the widget when the parent
46 // window is shown.
47 const bool wasExplicitShowHide = widget->testAttribute(attribute: Qt::WA_WState_ExplicitShowHide);
48 const bool wasHidden = widget->testAttribute(attribute: Qt::WA_WState_Hidden);
49 QWidgetPrivate::get(w: widget)->setVisible(visible);
50 if (wasExplicitShowHide) {
51 widget->setAttribute(Qt::WA_WState_ExplicitShowHide, on: wasExplicitShowHide);
52 widget->setAttribute(Qt::WA_WState_Hidden, on: wasHidden);
53 }
54 } else {
55 QWindowPrivate::setVisible(visible);
56 }
57 }
58
59 QWindow *eventReceiver() override {
60 Q_Q(QWidgetWindow);
61 QWindow *w = q;
62 while (w->parent() && qobject_cast<QWidgetWindow *>(object: w) && qobject_cast<QWidgetWindow *>(object: w->parent())) {
63 w = w->parent();
64 }
65 return w;
66 }
67
68 void clearFocusObject() override
69 {
70 Q_Q(QWidgetWindow);
71 QWidget *widget = q->widget();
72 if (widget && widget->focusWidget())
73 widget->focusWidget()->clearFocus();
74 }
75
76 QRectF closestAcceptableGeometry(const QRectF &rect) const override;
77
78 void processSafeAreaMarginsChanged() override
79 {
80 Q_Q(QWidgetWindow);
81 if (QWidget *widget = q->widget())
82 QWidgetPrivate::get(w: widget)->updateContentsRect();
83 }
84
85 bool participatesInLastWindowClosed() const override;
86 bool treatAsVisible() const override;
87};
88
89QRectF QWidgetWindowPrivate::closestAcceptableGeometry(const QRectF &rect) const
90{
91 Q_Q(const QWidgetWindow);
92 const QWidget *widget = q->widget();
93 if (!widget || !widget->isWindow() || !widget->hasHeightForWidth())
94 return QRect();
95 const QSize oldSize = rect.size().toSize();
96 const QSize newSize = QLayout::closestAcceptableSize(w: widget, s: oldSize);
97 if (newSize == oldSize)
98 return QRectF();
99 const int dw = newSize.width() - oldSize.width();
100 const int dh = newSize.height() - oldSize.height();
101 QRectF result = rect;
102 const QRectF currentGeometry(widget->geometry());
103 const qreal topOffset = result.top() - currentGeometry.top();
104 const qreal bottomOffset = result.bottom() - currentGeometry.bottom();
105 if (qAbs(t: topOffset) > qAbs(t: bottomOffset))
106 result.setTop(result.top() - dh); // top edge drag
107 else
108 result.setBottom(result.bottom() + dh); // bottom edge drag
109 const qreal leftOffset = result.left() - currentGeometry.left();
110 const qreal rightOffset = result.right() - currentGeometry.right();
111 if (qAbs(t: leftOffset) > qAbs(t: rightOffset))
112 result.setLeft(result.left() - dw); // left edge drag
113 else
114 result.setRight(result.right() + dw); // right edge drag
115 return result;
116}
117
118bool q_evaluateRhiConfig(const QWidget *w, QPlatformBackingStoreRhiConfig *outConfig, QSurface::SurfaceType *outType);
119
120QWidgetWindow::QWidgetWindow(QWidget *widget)
121 : QWindow(*new QWidgetWindowPrivate(), nullptr)
122 , m_widget(widget)
123{
124 updateObjectName();
125 if (!QCoreApplication::testAttribute(attribute: Qt::AA_ForceRasterWidgets)) {
126 QSurface::SurfaceType type = QSurface::RasterSurface;
127 q_evaluateRhiConfig(w: m_widget, outConfig: nullptr, outType: &type);
128 setSurfaceType(type);
129 }
130
131 connect(sender: widget, signal: &QObject::objectNameChanged, context: this, slot: &QWidgetWindow::updateObjectName);
132 connect(sender: this, signal: &QWidgetWindow::screenChanged, context: this, slot: &QWidgetWindow::handleScreenChange);
133}
134
135QWidgetWindow::~QWidgetWindow()
136{
137}
138
139#if QT_CONFIG(accessibility)
140QAccessibleInterface *QWidgetWindow::accessibleRoot() const
141{
142 if (m_widget)
143 return QAccessible::queryAccessibleInterface(m_widget);
144 return nullptr;
145}
146#endif
147
148QObject *QWidgetWindow::focusObject() const
149{
150 QWidget *windowWidget = m_widget;
151 if (!windowWidget)
152 return nullptr;
153
154 // A window can't have a focus object if it's being destroyed.
155 if (QWidgetPrivate::get(w: windowWidget)->data.in_destructor)
156 return nullptr;
157
158 QWidget *widget = windowWidget->focusWidget();
159
160 if (!widget)
161 widget = windowWidget;
162
163 QObject *focusObj = QWidgetPrivate::get(w: widget)->focusObject();
164 if (focusObj)
165 return focusObj;
166
167 return widget;
168}
169
170void QWidgetWindow::setNativeWindowVisibility(bool visible)
171{
172 Q_D(QWidgetWindow);
173 // Call base class setVisible() implementation to run the QWindow
174 // visibility logic. Don't call QWidgetWindowPrivate::setVisible()
175 // since that will recurse back into QWidget code.
176 d->QWindowPrivate::setVisible(visible);
177}
178
179static inline bool shouldBePropagatedToWidget(QEvent *event)
180{
181 switch (event->type()) {
182 // Handing show events to widgets would cause them to be triggered twice
183 case QEvent::Show:
184 case QEvent::Hide:
185 case QEvent::Timer:
186 case QEvent::DynamicPropertyChange:
187 case QEvent::ChildAdded:
188 case QEvent::ChildRemoved:
189 case QEvent::Paint:
190 case QEvent::Close: // Propagated manually in closeEvent
191 return false;
192 default:
193 return true;
194 }
195}
196
197bool QWidgetWindow::event(QEvent *event)
198{
199 if (!m_widget)
200 return QWindow::event(event);
201
202 switch (event->type()) {
203 case QEvent::Enter:
204 case QEvent::Leave:
205 handleEnterLeaveEvent(event);
206 return true;
207
208 // these should not be sent to QWidget, the corresponding events
209 // are sent by QApplicationPrivate::notifyActiveWindowChange()
210 case QEvent::FocusIn:
211 handleFocusInEvent(static_cast<QFocusEvent *>(event));
212 Q_FALLTHROUGH();
213 case QEvent::FocusOut: {
214#if QT_CONFIG(accessibility)
215 QAccessible::State state;
216 state.active = true;
217 QAccessibleStateChangeEvent ev(m_widget, state);
218 QAccessible::updateAccessibility(event: &ev);
219#endif
220 return false; }
221
222 case QEvent::FocusAboutToChange:
223 if (QApplicationPrivate::focus_widget) {
224 if (QApplicationPrivate::focus_widget->testAttribute(attribute: Qt::WA_InputMethodEnabled))
225 QGuiApplication::inputMethod()->commit();
226
227 QGuiApplication::forwardEvent(receiver: QApplicationPrivate::focus_widget, event);
228 }
229 return true;
230
231 case QEvent::KeyPress:
232 case QEvent::KeyRelease:
233 case QEvent::ShortcutOverride:
234 handleKeyEvent(static_cast<QKeyEvent *>(event));
235 return true;
236
237 case QEvent::MouseMove:
238 case QEvent::MouseButtonPress:
239 case QEvent::MouseButtonRelease:
240 case QEvent::MouseButtonDblClick:
241 handleMouseEvent(static_cast<QMouseEvent *>(event));
242 return true;
243
244 case QEvent::NonClientAreaMouseMove:
245 case QEvent::NonClientAreaMouseButtonPress:
246 case QEvent::NonClientAreaMouseButtonRelease:
247 case QEvent::NonClientAreaMouseButtonDblClick:
248 handleNonClientAreaMouseEvent(static_cast<QMouseEvent *>(event));
249 return true;
250
251 case QEvent::TouchBegin:
252 case QEvent::TouchUpdate:
253 case QEvent::TouchEnd:
254 case QEvent::TouchCancel:
255 handleTouchEvent(static_cast<QTouchEvent *>(event));
256 return true;
257
258 case QEvent::Move:
259 handleMoveEvent(static_cast<QMoveEvent *>(event));
260 return true;
261
262 case QEvent::Resize:
263 handleResizeEvent(static_cast<QResizeEvent *>(event));
264 return true;
265
266#if QT_CONFIG(wheelevent)
267 case QEvent::Wheel:
268 handleWheelEvent(static_cast<QWheelEvent *>(event));
269 return true;
270#endif
271
272#if QT_CONFIG(draganddrop)
273 case QEvent::DragEnter:
274 handleDragEnterEvent(static_cast<QDragEnterEvent *>(event));
275 return true;
276 case QEvent::DragMove:
277 handleDragMoveEvent(static_cast<QDragMoveEvent *>(event));
278 return true;
279 case QEvent::DragLeave:
280 handleDragLeaveEvent(static_cast<QDragLeaveEvent *>(event));
281 return true;
282 case QEvent::Drop:
283 handleDropEvent(static_cast<QDropEvent *>(event));
284 return true;
285#endif
286
287 case QEvent::Expose:
288 handleExposeEvent(static_cast<QExposeEvent *>(event));
289 return true;
290
291 case QEvent::WindowStateChange:
292 QWindow::event(event); // Update QWindow::Visibility and emit signals.
293 handleWindowStateChangedEvent(event: static_cast<QWindowStateChangeEvent *>(event));
294 return true;
295
296 case QEvent::ThemeChange: {
297 QEvent widgetEvent(QEvent::ThemeChange);
298 QCoreApplication::forwardEvent(receiver: m_widget, event: &widgetEvent, originatingEvent: event);
299 }
300 return true;
301
302#if QT_CONFIG(tabletevent)
303 case QEvent::TabletPress:
304 case QEvent::TabletMove:
305 case QEvent::TabletRelease:
306 handleTabletEvent(static_cast<QTabletEvent *>(event));
307 return true;
308#endif
309
310#ifndef QT_NO_GESTURES
311 case QEvent::NativeGesture:
312 handleGestureEvent(static_cast<QNativeGestureEvent *>(event));
313 return true;
314#endif
315
316#ifndef QT_NO_CONTEXTMENU
317 case QEvent::ContextMenu:
318 handleContextMenuEvent(static_cast<QContextMenuEvent *>(event));
319 return true;
320#endif // QT_NO_CONTEXTMENU
321
322 case QEvent::WindowBlocked:
323 qt_button_down = nullptr;
324 break;
325
326 case QEvent::UpdateRequest:
327 // This is not the same as an UpdateRequest for a QWidget. That just
328 // syncs the backing store while here we also must mark as dirty.
329 m_widget->repaint();
330 return true;
331
332 case QEvent::DevicePixelRatioChange:
333 handleDevicePixelRatioChange();
334 break;
335
336 default:
337 break;
338 }
339
340 if (shouldBePropagatedToWidget(event) && QCoreApplication::forwardEvent(receiver: m_widget, event))
341 return true;
342
343 return QWindow::event(event);
344}
345
346QPointer<QWidget> qt_last_mouse_receiver = nullptr;
347
348void QWidgetWindow::handleEnterLeaveEvent(QEvent *event)
349{
350 // Ignore all enter/leave events from QPA if we are not on the first-level context menu.
351 // This prevents duplicated events on most platforms. Fake events will be delivered in
352 // QWidgetWindow::handleMouseEvent(QMouseEvent *). Make an exception whether the widget
353 // is already under mouse - let the mouse leave.
354 if (QApplicationPrivate::inPopupMode() && m_widget != QApplication::activePopupWidget() && !m_widget->underMouse())
355 return;
356
357 if (event->type() == QEvent::Leave) {
358 QWidget *enter = nullptr;
359 // Check from window system event queue if the next queued enter targets a window
360 // in the same window hierarchy (e.g. enter a child of this window). If so,
361 // remove the enter event from queue and handle both in single dispatch.
362 QWindowSystemInterfacePrivate::EnterEvent *systemEvent =
363 static_cast<QWindowSystemInterfacePrivate::EnterEvent *>
364 (QWindowSystemInterfacePrivate::peekWindowSystemEvent(t: QWindowSystemInterfacePrivate::Enter));
365 const QPointF globalPosF = systemEvent ? systemEvent->globalPos : QPointF(QGuiApplicationPrivate::lastCursorPosition);
366 if (systemEvent) {
367 if (QWidgetWindow *enterWindow = qobject_cast<QWidgetWindow *>(object: systemEvent->enter))
368 {
369 QWindow *thisParent = this;
370 QWindow *enterParent = enterWindow;
371 while (thisParent->parent())
372 thisParent = thisParent->parent();
373 while (enterParent->parent())
374 enterParent = enterParent->parent();
375 if (thisParent == enterParent) {
376 QGuiApplicationPrivate::currentMouseWindow = enterWindow;
377 enter = enterWindow->widget();
378 QWindowSystemInterfacePrivate::removeWindowSystemEvent(event: systemEvent);
379 }
380 }
381 }
382 // Enter-leave between sibling widgets is ignored when there is a mousegrabber - this makes
383 // both native and non-native widgets work similarly.
384 // When mousegrabbing, leaves are only generated if leaving the parent window.
385 if (!enter || !QWidget::mouseGrabber()) {
386 // Preferred leave target is the last mouse receiver, unless it has native window,
387 // in which case it is assumed to receive it's own leave event when relevant.
388 QWidget *leave = m_widget;
389 if (qt_last_mouse_receiver && !qt_last_mouse_receiver->internalWinId())
390 leave = qt_last_mouse_receiver.data();
391 QApplicationPrivate::dispatchEnterLeave(enter, leave, globalPosF);
392 qt_last_mouse_receiver = enter;
393 }
394 } else {
395 const QEnterEvent *ee = static_cast<QEnterEvent *>(event);
396 QWidget *child = m_widget->childAt(p: ee->position().toPoint());
397 QWidget *receiver = child ? child : m_widget.data();
398 QWidget *leave = nullptr;
399 if (QApplicationPrivate::inPopupMode() && receiver == m_widget
400 && qt_last_mouse_receiver != m_widget) {
401 // This allows to deliver the leave event to the native widget
402 // action on first-level menu.
403 leave = qt_last_mouse_receiver;
404 }
405 QApplicationPrivate::dispatchEnterLeave(enter: receiver, leave, globalPosF: ee->globalPosition());
406 qt_last_mouse_receiver = receiver;
407 }
408}
409
410QWidget *QWidgetWindow::getFocusWidget(FocusWidgets fw)
411{
412 QWidget *tlw = m_widget;
413 QWidget *w = tlw->nextInFocusChain();
414
415 QWidget *last = tlw;
416
417 uint focus_flag = qt_tab_all_widgets() ? Qt::TabFocus : Qt::StrongFocus;
418
419 while (w != tlw)
420 {
421 if (((w->focusPolicy() & focus_flag) == focus_flag)
422 && w->isVisibleTo(m_widget) && w->isEnabled())
423 {
424 last = w;
425 if (fw == FirstFocusWidget)
426 break;
427 }
428 w = w->nextInFocusChain();
429 }
430
431 return last;
432}
433
434void QWidgetWindow::handleFocusInEvent(QFocusEvent *e)
435{
436 QWidget *focusWidget = nullptr;
437 if (e->reason() == Qt::BacktabFocusReason)
438 focusWidget = getFocusWidget(fw: LastFocusWidget);
439 else if (e->reason() == Qt::TabFocusReason)
440 focusWidget = getFocusWidget(fw: FirstFocusWidget);
441
442 if (focusWidget != nullptr)
443 focusWidget->setFocus();
444}
445
446void QWidgetWindow::handleNonClientAreaMouseEvent(QMouseEvent *e)
447{
448 QApplication::forwardEvent(receiver: m_widget, event: e);
449}
450
451void QWidgetWindow::handleMouseEvent(QMouseEvent *event)
452{
453 static const QEvent::Type contextMenuTrigger =
454 QGuiApplicationPrivate::platformTheme()->themeHint(hint: QPlatformTheme::ContextMenuOnMouseRelease).toBool() ?
455 QEvent::MouseButtonRelease : QEvent::MouseButtonPress;
456 if (QApplicationPrivate::inPopupMode()) {
457 QPointer<QWidget> activePopupWidget = QApplication::activePopupWidget();
458 QPoint mapped = event->position().toPoint();
459 if (activePopupWidget != m_widget)
460 mapped = activePopupWidget->mapFromGlobal(event->globalPosition().toPoint());
461 bool releaseAfter = false;
462 QWidget *popupChild = activePopupWidget->childAt(p: mapped);
463
464 if (activePopupWidget != qt_popup_down) {
465 qt_button_down = nullptr;
466 qt_popup_down = nullptr;
467 }
468
469 switch (event->type()) {
470 case QEvent::MouseButtonPress:
471 case QEvent::MouseButtonDblClick:
472 qt_button_down = popupChild;
473 qt_popup_down = activePopupWidget;
474 qt_popup_down_closed = false;
475 break;
476 case QEvent::MouseButtonRelease:
477 releaseAfter = true;
478 break;
479 default:
480 break; // nothing for mouse move
481 }
482
483 int oldOpenPopupCount = openPopupCount;
484
485 if (activePopupWidget->isEnabled()) {
486 // deliver event
487 qt_replay_popup_mouse_event = false;
488 QPointer<QWidget> receiver = activePopupWidget;
489 QPoint widgetPos = mapped;
490 if (qt_button_down)
491 receiver = qt_button_down;
492 else if (popupChild)
493 receiver = popupChild;
494 if (receiver != activePopupWidget)
495 widgetPos = receiver->mapFromGlobal(event->globalPosition().toPoint());
496
497 const bool reallyUnderMouse = activePopupWidget->rect().contains(p: mapped);
498 const bool underMouse = activePopupWidget->underMouse();
499 if (underMouse != reallyUnderMouse) {
500 if (reallyUnderMouse) {
501 const QPoint receiverMapped = receiver->mapFromGlobal(event->globalPosition().toPoint());
502 // Prevent negative mouse position on enter event - this event
503 // should be properly handled in "handleEnterLeaveEvent()".
504 if (receiverMapped.x() >= 0 && receiverMapped.y() >= 0) {
505 QApplicationPrivate::dispatchEnterLeave(enter: receiver, leave: nullptr, globalPosF: event->globalPosition());
506 qt_last_mouse_receiver = receiver;
507 }
508 } else {
509 QApplicationPrivate::dispatchEnterLeave(enter: nullptr, leave: qt_last_mouse_receiver, globalPosF: event->globalPosition());
510 qt_last_mouse_receiver = receiver;
511 receiver = activePopupWidget;
512 }
513 }
514
515 if ((event->type() != QEvent::MouseButtonPress) || !(QMutableSinglePointEvent::from(e: event)->isDoubleClick())) {
516 // if the widget that was pressed is gone, then deliver move events without buttons
517 const auto buttons = event->type() == QEvent::MouseMove && qt_popup_down_closed
518 ? Qt::NoButton : event->buttons();
519 QMouseEvent e(event->type(), widgetPos, event->scenePosition(), event->globalPosition(),
520 event->button(), buttons, event->modifiers(),
521 event->source(), event->pointingDevice());
522 e.setTimestamp(event->timestamp());
523 QApplicationPrivate::sendMouseEvent(receiver, event: &e, alienWidget: receiver, native: receiver->window(), buttonDown: &qt_button_down, lastMouseReceiver&: qt_last_mouse_receiver);
524 qt_last_mouse_receiver = receiver;
525 }
526 } else {
527 // close disabled popups when a mouse button is pressed or released
528 switch (event->type()) {
529 case QEvent::MouseButtonPress:
530 case QEvent::MouseButtonDblClick:
531 case QEvent::MouseButtonRelease:
532 activePopupWidget->close();
533 break;
534 default:
535 break;
536 }
537 }
538
539 if (QApplication::activePopupWidget() != activePopupWidget
540 && qt_replay_popup_mouse_event
541 && QGuiApplicationPrivate::platformIntegration()->styleHint(hint: QPlatformIntegration::ReplayMousePressOutsidePopup).toBool()) {
542 if (m_widget->windowType() != Qt::Popup)
543 qt_button_down = nullptr;
544 if (event->type() == QEvent::MouseButtonPress) {
545 // the popup disappeared, replay the mouse press event
546 QWidget *w = QApplication::widgetAt(p: event->globalPosition().toPoint());
547 if (w && !QApplicationPrivate::isBlockedByModal(widget: w)) {
548 // activate window of the widget under mouse pointer
549 if (!w->isActiveWindow()) {
550 w->activateWindow();
551 w->window()->raise();
552 }
553
554 if (auto win = qt_widget_private(widget: w)->windowHandle(mode: QWidgetPrivate::WindowHandleMode::Closest)) {
555 const QRect globalGeometry = win->isTopLevel()
556 ? win->geometry()
557 : QRect(win->mapToGlobal(pos: QPoint(0, 0)), win->size());
558 if (globalGeometry.contains(p: event->globalPosition().toPoint())) {
559 // Use postEvent() to ensure the local QEventLoop terminates when called from QMenu::exec()
560 const QPoint localPos = win->mapFromGlobal(pos: event->globalPosition().toPoint());
561 QMouseEvent *e = new QMouseEvent(QEvent::MouseButtonPress, localPos, localPos, event->globalPosition().toPoint(),
562 event->button(), event->buttons(), event->modifiers(), event->source());
563 QCoreApplicationPrivate::setEventSpontaneous(e, spontaneous: true);
564 e->setTimestamp(event->timestamp());
565 QCoreApplication::postEvent(receiver: win, event: e);
566 }
567 }
568 }
569 }
570 qt_replay_popup_mouse_event = false;
571#ifndef QT_NO_CONTEXTMENU
572 } else if (event->type() == contextMenuTrigger
573 && event->button() == Qt::RightButton
574 && (openPopupCount == oldOpenPopupCount)) {
575 QWidget *receiver = activePopupWidget;
576 if (qt_button_down)
577 receiver = qt_button_down;
578 else if (popupChild)
579 receiver = popupChild;
580 const QPoint localPos = receiver->mapFromGlobal(event->globalPosition().toPoint());
581 QContextMenuEvent e(QContextMenuEvent::Mouse, localPos, event->globalPosition().toPoint(), event->modifiers());
582 QApplication::forwardEvent(receiver, event: &e, originatingEvent: event);
583 }
584#else
585 Q_UNUSED(contextMenuTrigger);
586 Q_UNUSED(oldOpenPopupCount);
587 }
588#endif
589
590 if (releaseAfter) {
591 qt_button_down = nullptr;
592 qt_popup_down_closed = false;
593 qt_popup_down = nullptr;
594 }
595 return;
596 }
597
598 qt_popup_down_closed = false;
599 // modal event handling
600 if (QApplicationPrivate::instance()->modalState() && !qt_try_modal(widget: m_widget, type: event->type()))
601 return;
602
603 // which child should have it?
604 QWidget *widget = m_widget->childAt(p: event->position().toPoint());
605 QPoint mapped = event->position().toPoint();
606
607 if (!widget)
608 widget = m_widget;
609
610 const bool initialPress = event->buttons() == event->button();
611 if (event->type() == QEvent::MouseButtonPress && initialPress)
612 qt_button_down = widget;
613
614 QWidget *receiver = QApplicationPrivate::pickMouseReceiver(candidate: m_widget, windowPos: event->scenePosition().toPoint(), pos: &mapped, type: event->type(), buttons: event->buttons(),
615 buttonDown: qt_button_down, alienWidget: widget);
616 if (!receiver)
617 return;
618
619 if ((event->type() != QEvent::MouseButtonPress) || !QMutableSinglePointEvent::from(e: event)->isDoubleClick()) {
620
621 // The preceding statement excludes MouseButtonPress events which caused
622 // creation of a MouseButtonDblClick event. QTBUG-25831
623 QMouseEvent translated(event->type(), mapped, event->scenePosition(), event->globalPosition(),
624 event->button(), event->buttons(), event->modifiers(),
625 event->source(), event->pointingDevice());
626 translated.setTimestamp(event->timestamp());
627 QApplicationPrivate::sendMouseEvent(receiver, event: &translated, alienWidget: widget, native: m_widget,
628 buttonDown: &qt_button_down, lastMouseReceiver&: qt_last_mouse_receiver);
629 event->setAccepted(translated.isAccepted());
630 }
631#ifndef QT_NO_CONTEXTMENU
632 if (event->type() == contextMenuTrigger && event->button() == Qt::RightButton
633 && m_widget->rect().contains(p: event->position().toPoint())) {
634 QContextMenuEvent e(QContextMenuEvent::Mouse, mapped, event->globalPosition().toPoint(), event->modifiers());
635 QGuiApplication::forwardEvent(receiver, event: &e, originatingEvent: event);
636 if (e.isAccepted())
637 event->accept();
638 }
639#endif
640}
641
642void QWidgetWindow::handleTouchEvent(QTouchEvent *event)
643{
644 if (event->type() == QEvent::TouchCancel) {
645 QApplicationPrivate::translateTouchCancel(device: event->pointingDevice(), timestamp: event->timestamp());
646 event->accept();
647 } else if (QApplicationPrivate::inPopupMode()) {
648 // Ignore touch events for popups. This will cause QGuiApplication to synthesise mouse
649 // events instead, which QWidgetWindow::handleMouseEvent will forward correctly:
650 event->ignore();
651 } else {
652 event->setAccepted(QApplicationPrivate::translateRawTouchEvent(widget: m_widget, touchEvent: event));
653 }
654}
655
656void QWidgetWindow::handleKeyEvent(QKeyEvent *event)
657{
658 if (QApplicationPrivate::instance()->modalState() && !qt_try_modal(widget: m_widget, type: event->type()))
659 return;
660
661 QObject *receiver = QWidget::keyboardGrabber();
662 if (!receiver && QApplicationPrivate::inPopupMode()) {
663 QWidget *popup = QApplication::activePopupWidget();
664 QWidget *popupFocusWidget = popup->focusWidget();
665 receiver = popupFocusWidget ? popupFocusWidget : popup;
666 }
667 if (!receiver)
668 receiver = focusObject();
669 QGuiApplication::forwardEvent(receiver, event);
670}
671
672bool QWidgetWindow::updateSize()
673{
674 bool changed = false;
675 if (m_widget->testAttribute(attribute: Qt::WA_OutsideWSRange))
676 return changed;
677 if (m_widget->testAttribute(attribute: Qt::WA_DontShowOnScreen))
678 return changed;
679
680 if (m_widget->data->crect.size() != geometry().size()) {
681 changed = true;
682 m_widget->data->crect.setSize(geometry().size());
683 }
684
685 updateMargins();
686 return changed;
687}
688
689void QWidgetWindow::updateMargins()
690{
691 // QTBUG-79147 (Windows): Bail out on resize events after closing a dialog
692 // and destroying the platform window which would clear the margins.
693 QTLWExtra *te = m_widget->d_func()->topData();
694 if (te->window == nullptr || te->window->handle() == nullptr)
695 return;
696 const QMargins margins = frameMargins();
697 te->posIncludesFrame= false;
698 te->frameStrut.setCoords(xp1: margins.left(), yp1: margins.top(), xp2: margins.right(), yp2: margins.bottom());
699 m_widget->data->fstrut_dirty = false;
700}
701
702static void sendChangeRecursively(QWidget *widget, QEvent::Type type)
703{
704 QEvent e(type);
705 QCoreApplication::sendEvent(receiver: widget, event: &e);
706 QWidgetPrivate *d = QWidgetPrivate::get(w: widget);
707 for (int i = 0; i < d->children.size(); ++i) {
708 QWidget *w = qobject_cast<QWidget *>(o: d->children.at(i));
709 if (w)
710 sendChangeRecursively(widget: w, type);
711 }
712}
713
714void QWidgetWindow::handleScreenChange()
715{
716 // Send an event recursively to the widget and its children.
717 sendChangeRecursively(widget: m_widget, type: QEvent::ScreenChangeInternal);
718
719 // Invalidate the backing store buffer and repaint immediately.
720 if (screen())
721 repaintWindow();
722}
723
724void QWidgetWindow::handleDevicePixelRatioChange()
725{
726 // Send an event recursively to the widget and its children.
727 sendChangeRecursively(widget: m_widget, type: QEvent::DevicePixelRatioChange);
728
729 // Invalidate the backing store buffer and repaint immediately.
730 if (screen())
731 repaintWindow();
732}
733
734void QWidgetWindow::repaintWindow()
735{
736 if (!m_widget->isVisible() || !m_widget->updatesEnabled() || !m_widget->rect().isValid())
737 return;
738
739 QTLWExtra *tlwExtra = m_widget->window()->d_func()->maybeTopData();
740 if (tlwExtra && tlwExtra->backingStore)
741 tlwExtra->repaintManager->markDirty(r: m_widget->rect(), widget: m_widget,
742 updateTime: QWidgetRepaintManager::UpdateNow, bufferState: QWidgetRepaintManager::BufferInvalid);
743}
744
745// Store normal geometry used for saving application settings.
746void QWidgetWindow::updateNormalGeometry()
747{
748 QTLWExtra *tle = m_widget->d_func()->maybeTopData();
749 if (!tle)
750 return;
751 // Ask platform window, default to widget geometry.
752 QRect normalGeometry;
753 if (const QPlatformWindow *pw = handle())
754 normalGeometry = QHighDpi::fromNativePixels(value: pw->normalGeometry(), context: this);
755 if (!normalGeometry.isValid() && !(m_widget->windowState() & ~Qt::WindowActive))
756 normalGeometry = m_widget->geometry();
757 if (normalGeometry.isValid())
758 tle->normalGeometry = normalGeometry;
759}
760
761void QWidgetWindow::handleMoveEvent(QMoveEvent *event)
762{
763 if (m_widget->testAttribute(attribute: Qt::WA_OutsideWSRange))
764 return;
765 if (m_widget->testAttribute(attribute: Qt::WA_DontShowOnScreen))
766 return;
767
768 auto oldPosition = m_widget->data->crect.topLeft();
769 auto newPosition = geometry().topLeft();
770
771 if (!m_widget->isWindow()) {
772 if (auto *nativeParent = m_widget->nativeParentWidget())
773 newPosition = m_widget->parentWidget()->mapFrom(nativeParent, newPosition);
774 }
775
776 bool changed = newPosition != oldPosition;
777
778 if (changed)
779 m_widget->data->crect.moveTopLeft(p: newPosition);
780
781 updateMargins(); // FIXME: Only do when changed?
782
783 if (changed) {
784 QMoveEvent widgetEvent(newPosition, oldPosition);
785 QGuiApplication::forwardEvent(receiver: m_widget, event: &widgetEvent, originatingEvent: event);
786 }
787}
788
789void QWidgetWindow::handleResizeEvent(QResizeEvent *event)
790{
791 auto oldRect = m_widget->rect();
792
793 if (updateSize()) {
794 QGuiApplication::forwardEvent(receiver: m_widget, event);
795
796 if (m_widget->d_func()->shouldPaintOnScreen()) {
797 QRegion dirtyRegion = m_widget->rect();
798 if (m_widget->testAttribute(attribute: Qt::WA_StaticContents))
799 dirtyRegion -= oldRect;
800 m_widget->d_func()->syncBackingStore(region: dirtyRegion);
801 } else {
802 m_widget->d_func()->syncBackingStore();
803 }
804 }
805}
806
807void QWidgetWindow::closeEvent(QCloseEvent *event)
808{
809 Q_D(QWidgetWindow);
810 bool accepted = m_widget->d_func()->handleClose(mode: d->inClose ? QWidgetPrivate::CloseWithEvent
811 : QWidgetPrivate::CloseWithSpontaneousEvent);
812 event->setAccepted(accepted);
813}
814
815bool QWidgetWindowPrivate::participatesInLastWindowClosed() const
816{
817 Q_Q(const QWidgetWindow);
818
819 // For historical reasons WA_QuitOnClose has been closely tied
820 // to the lastWindowClosed signal, since the default behavior
821 // is to quit the application after emitting lastWindowClosed.
822 // ### Qt 7: Rename this attribute, or decouple behavior.
823 if (!q->widget()->testAttribute(attribute: Qt::WA_QuitOnClose))
824 return false;
825
826 return QWindowPrivate::participatesInLastWindowClosed();
827}
828
829bool QWidgetWindowPrivate::treatAsVisible() const
830{
831 Q_Q(const QWidgetWindow);
832
833 // Widget windows may have Qt::WA_DontShowOnScreen, in which case the
834 // QQWidget will be visible, but the corresponding QWindow will not.
835 // Since the lastWindowClosed logic relies on checking whether the
836 // closed window was visible, and if there are any remaining visible
837 // windows, we need to reflect the QWidget state, not the QWindow one.
838 return q->widget()->isVisible();
839}
840
841#if QT_CONFIG(wheelevent)
842
843void QWidgetWindow::handleWheelEvent(QWheelEvent *event)
844{
845 if (QApplicationPrivate::instance()->modalState() && !qt_try_modal(widget: m_widget, type: event->type()))
846 return;
847
848 QWidget *rootWidget = m_widget;
849 QPointF pos = event->position();
850
851 // Use proper popup window for wheel event. Some QPA sends the wheel
852 // event to the root menu, so redirect it to the proper popup window.
853 QWidget *activePopupWidget = QApplication::activePopupWidget();
854 if (activePopupWidget && activePopupWidget != m_widget) {
855 rootWidget = activePopupWidget;
856 pos = rootWidget->mapFromGlobal(event->globalPosition());
857 }
858
859 // which child should have it?
860 QWidget *widget = rootWidget->childAt(p: pos.toPoint());
861
862 if (!widget)
863 widget = rootWidget;
864
865 QPointF mapped = widget->mapFrom(rootWidget, pos);
866
867 QWheelEvent translated(mapped, event->globalPosition(), event->pixelDelta(), event->angleDelta(),
868 event->buttons(), event->modifiers(), event->phase(), event->inverted(),
869 event->source(), event->pointingDevice());
870 translated.setTimestamp(event->timestamp());
871 QGuiApplication::forwardEvent(receiver: widget, event: &translated, originatingEvent: event);
872}
873
874#endif // QT_CONFIG(wheelevent)
875
876#if QT_CONFIG(draganddrop)
877
878static QWidget *findDnDTarget(QWidget *parent, const QPoint &pos)
879{
880 // Find a target widget under mouse that accepts drops (QTBUG-22987).
881 QWidget *widget = parent->childAt(p: pos);
882 if (!widget)
883 widget = parent;
884 for ( ; widget && !widget->isWindow() && !widget->acceptDrops(); widget = widget->parentWidget()) ;
885 if (widget && !widget->acceptDrops())
886 widget = nullptr;
887 return widget;
888}
889
890void QWidgetWindow::handleDragEnterEvent(QDragEnterEvent *event, QWidget *widget)
891{
892 Q_ASSERT(m_dragTarget == nullptr);
893 if (!widget)
894 widget = findDnDTarget(parent: m_widget, pos: event->position().toPoint());
895 if (!widget) {
896 event->ignore();
897 return;
898 }
899 m_dragTarget = widget;
900
901 const QPoint mapped = widget->mapFromGlobal(m_widget->mapToGlobal(event->position().toPoint()));
902 QDragEnterEvent translated(mapped, event->possibleActions(), event->mimeData(),
903 event->buttons(), event->modifiers());
904 QGuiApplication::forwardEvent(receiver: m_dragTarget, event: &translated, originatingEvent: event);
905 event->setAccepted(translated.isAccepted());
906 event->setDropAction(translated.dropAction());
907}
908
909void QWidgetWindow::handleDragMoveEvent(QDragMoveEvent *event)
910{
911 QPointer<QWidget> widget = findDnDTarget(parent: m_widget, pos: event->position().toPoint());
912 if (!widget) {
913 event->ignore();
914 if (m_dragTarget) { // Send DragLeave to previous
915 QDragLeaveEvent leaveEvent;
916 QWidget *dragTarget = m_dragTarget;
917 m_dragTarget = nullptr;
918 QGuiApplication::forwardEvent(receiver: dragTarget, event: &leaveEvent, originatingEvent: event);
919 }
920 } else {
921 const QPoint mapped = widget->mapFromGlobal(m_widget->mapToGlobal(event->position().toPoint()));
922 QDragMoveEvent translated(mapped, event->possibleActions(), event->mimeData(),
923 event->buttons(), event->modifiers());
924
925 if (widget == m_dragTarget) { // Target widget unchanged: Send DragMove
926 translated.setDropAction(event->dropAction());
927 translated.setAccepted(event->isAccepted());
928 QGuiApplication::forwardEvent(receiver: m_dragTarget, event: &translated, originatingEvent: event);
929 } else {
930 if (m_dragTarget) { // Send DragLeave to previous
931 QDragLeaveEvent leaveEvent;
932 QWidget *dragTarget = m_dragTarget;
933 m_dragTarget = nullptr;
934 QGuiApplication::forwardEvent(receiver: dragTarget, event: &leaveEvent, originatingEvent: event);
935 }
936 // widget might have been deleted when handling the leaveEvent
937 if (widget) {
938 // Send DragEnter to new widget.
939 handleDragEnterEvent(event: static_cast<QDragEnterEvent*>(event), widget);
940 // Handling 'DragEnter' should suffice for the application.
941 translated.setDropAction(event->dropAction());
942 translated.setAccepted(event->isAccepted());
943 // The drag enter event is always immediately followed by a drag move event,
944 // see QDragEnterEvent documentation.
945 if (m_dragTarget)
946 QGuiApplication::forwardEvent(receiver: m_dragTarget, event: &translated, originatingEvent: event);
947 }
948 }
949 event->setAccepted(translated.isAccepted());
950 event->setDropAction(translated.dropAction());
951 }
952}
953
954void QWidgetWindow::handleDragLeaveEvent(QDragLeaveEvent *event)
955{
956 if (m_dragTarget) {
957 QWidget *dragTarget = m_dragTarget;
958 m_dragTarget = nullptr;
959 QGuiApplication::forwardEvent(receiver: dragTarget, event);
960 }
961}
962
963void QWidgetWindow::handleDropEvent(QDropEvent *event)
964{
965 if (Q_UNLIKELY(m_dragTarget.isNull())) {
966 qWarning() << m_widget << ": No drag target set.";
967 event->ignore();
968 return;
969 }
970 const QPoint mapped = m_dragTarget->mapFromGlobal(m_widget->mapToGlobal(event->position().toPoint()));
971 QDropEvent translated(mapped, event->possibleActions(), event->mimeData(), event->buttons(), event->modifiers());
972 QWidget *dragTarget = m_dragTarget;
973 m_dragTarget = nullptr;
974 QGuiApplication::forwardEvent(receiver: dragTarget, event: &translated, originatingEvent: event);
975 event->setAccepted(translated.isAccepted());
976 event->setDropAction(translated.dropAction());
977}
978
979#endif // QT_CONFIG(draganddrop)
980
981void QWidgetWindow::handleExposeEvent(QExposeEvent *event)
982{
983 if (m_widget->testAttribute(attribute: Qt::WA_DontShowOnScreen))
984 return; // Ignore for widgets that fake exposure
985
986 QWidgetPrivate *wPriv = m_widget->d_func();
987 const bool exposed = isExposed();
988
989 if (wPriv->childrenHiddenByWState) {
990 // If widgets has been previously hidden by window state change event
991 // and they aren't yet shown...
992 if (exposed) {
993 // If the window becomes exposed...
994 if (!wPriv->childrenShownByExpose) {
995 // ... and they haven't been shown by this function yet - show it.
996 wPriv->showChildren(spontaneous: true);
997 QShowEvent showEvent;
998 QCoreApplication::forwardEvent(receiver: m_widget, event: &showEvent, originatingEvent: event);
999 wPriv->childrenShownByExpose = true;
1000 }
1001 } else {
1002 // If the window becomes not exposed...
1003 if (wPriv->childrenShownByExpose) {
1004 // ... and child widgets was previously shown by the expose event - hide widgets again.
1005 // This is a workaround, because sometimes when window is minimized programmatically,
1006 // the QPA can notify that the window is exposed after changing window state to minimized
1007 // and then, the QPA can send next expose event with null exposed region (not exposed).
1008 wPriv->hideChildren(spontaneous: true);
1009 QHideEvent hideEvent;
1010 QCoreApplication::forwardEvent(receiver: m_widget, event: &hideEvent, originatingEvent: event);
1011 wPriv->childrenShownByExpose = false;
1012 }
1013 }
1014 }
1015
1016 if (exposed) {
1017 // QTBUG-39220, QTBUG-58575: set all (potentially fully obscured parent widgets) mapped.
1018 m_widget->setAttribute(Qt::WA_Mapped);
1019 for (QWidget *p = m_widget->parentWidget(); p && !p->testAttribute(attribute: Qt::WA_Mapped); p = p->parentWidget())
1020 p->setAttribute(Qt::WA_Mapped);
1021 if (!event->m_region.isNull())
1022 wPriv->syncBackingStore(region: event->m_region);
1023 } else {
1024 m_widget->setAttribute(Qt::WA_Mapped, on: false);
1025 }
1026}
1027
1028void QWidgetWindow::handleWindowStateChangedEvent(QWindowStateChangeEvent *event)
1029{
1030 // QWindow does currently not know 'active'.
1031 Qt::WindowStates eventState = event->oldState();
1032 Qt::WindowStates widgetState = m_widget->windowState();
1033 Qt::WindowStates windowState = windowStates();
1034 if (widgetState & Qt::WindowActive)
1035 eventState |= Qt::WindowActive;
1036
1037 // Determine the new widget state, remember maximized/full screen
1038 // during minimized.
1039 if (windowState & Qt::WindowMinimized) {
1040 widgetState |= Qt::WindowMinimized;
1041 } else {
1042 widgetState = windowState | (widgetState & Qt::WindowActive);
1043 if (windowState) // Maximized or FullScreen
1044 updateNormalGeometry();
1045 }
1046
1047 // Sent event if the state changed (that is, it is not triggered by
1048 // QWidget::setWindowState(), which also sends an event to the widget).
1049 if (widgetState != Qt::WindowStates::Int(m_widget->data->window_state)) {
1050 m_widget->data->window_state = uint(widgetState);
1051 QWindowStateChangeEvent widgetEvent(eventState);
1052 QGuiApplication::forwardEvent(receiver: m_widget, event: &widgetEvent, originatingEvent: event);
1053 }
1054}
1055
1056bool QWidgetWindow::nativeEvent(const QByteArray &eventType, void *message, qintptr *result)
1057{
1058 return m_widget->nativeEvent(eventType, message, result);
1059}
1060
1061#if QT_CONFIG(tabletevent)
1062void QWidgetWindow::handleTabletEvent(QTabletEvent *event)
1063{
1064 static QPointer<QWidget> qt_tablet_target = nullptr;
1065
1066 QWidget *widget = qt_tablet_target;
1067
1068 if (!widget) {
1069 widget = m_widget->childAt(p: event->position().toPoint());
1070 if (!widget)
1071 widget = m_widget;
1072 if (event->type() == QEvent::TabletPress)
1073 qt_tablet_target = widget;
1074 }
1075
1076 if (widget) {
1077 QPointF delta = event->globalPosition() - event->globalPosition().toPoint();
1078 QPointF mapped = widget->mapFromGlobal(event->globalPosition().toPoint()) + delta;
1079 QTabletEvent ev(event->type(), event->pointingDevice(), mapped, event->globalPosition(),
1080 event->pressure(), event->xTilt(), event->yTilt(), event->tangentialPressure(),
1081 event->rotation(), event->z(), event->modifiers(), event->button(), event->buttons());
1082 ev.setTimestamp(event->timestamp());
1083 ev.setAccepted(false);
1084 QGuiApplication::forwardEvent(receiver: widget, event: &ev, originatingEvent: event);
1085 event->setAccepted(ev.isAccepted());
1086 }
1087
1088 if (event->type() == QEvent::TabletRelease && event->buttons() == Qt::NoButton)
1089 qt_tablet_target = nullptr;
1090}
1091#endif // QT_CONFIG(tabletevent)
1092
1093#ifndef QT_NO_GESTURES
1094void QWidgetWindow::handleGestureEvent(QNativeGestureEvent *e)
1095{
1096 // copy-pasted code to find correct widget follows:
1097 QObject *receiver = nullptr;
1098 if (QApplicationPrivate::inPopupMode()) {
1099 QWidget *popup = QApplication::activePopupWidget();
1100 QWidget *popupFocusWidget = popup->focusWidget();
1101 receiver = popupFocusWidget ? popupFocusWidget : popup;
1102 }
1103 if (!receiver)
1104 receiver = QApplication::widgetAt(p: e->globalPosition().toPoint());
1105 if (!receiver)
1106 receiver = m_widget; // last resort
1107
1108 QApplication::forwardEvent(receiver, event: e);
1109}
1110#endif // QT_NO_GESTURES
1111
1112#ifndef QT_NO_CONTEXTMENU
1113void QWidgetWindow::handleContextMenuEvent(QContextMenuEvent *e)
1114{
1115 // We are only interested in keyboard originating context menu events here,
1116 // mouse originated context menu events for widgets are generated in mouse handling methods.
1117 if (e->reason() != QContextMenuEvent::Keyboard)
1118 return;
1119
1120 QWidget *fw = QWidget::keyboardGrabber();
1121 if (!fw) {
1122 if (QApplication::activePopupWidget()) {
1123 fw = (QApplication::activePopupWidget()->focusWidget()
1124 ? QApplication::activePopupWidget()->focusWidget()
1125 : QApplication::activePopupWidget());
1126 } else if (QApplication::focusWidget()) {
1127 fw = QApplication::focusWidget();
1128 } else {
1129 fw = m_widget;
1130 }
1131 }
1132 if (fw && fw->isEnabled()) {
1133 QPoint pos = fw->inputMethodQuery(Qt::ImCursorRectangle).toRect().center();
1134 QContextMenuEvent widgetEvent(QContextMenuEvent::Keyboard, pos, fw->mapToGlobal(pos),
1135 e->modifiers());
1136 QGuiApplication::forwardEvent(receiver: fw, event: &widgetEvent, originatingEvent: e);
1137 }
1138}
1139#endif // QT_NO_CONTEXTMENU
1140
1141void QWidgetWindow::updateObjectName()
1142{
1143 QString name = m_widget->objectName();
1144 if (name.isEmpty())
1145 name = QString::fromUtf8(utf8: m_widget->metaObject()->className()) + "Class"_L1;
1146 name += "Window"_L1;
1147 setObjectName(name);
1148}
1149
1150QT_END_NAMESPACE
1151
1152#include "moc_qwidgetwindow_p.cpp"
1153

source code of qtbase/src/widgets/kernel/qwidgetwindow.cpp