12 #include "WidgetResizeHandler_p.h"
13 #include "FloatingWindow_p.h"
14 #include "TitleBar_p.h"
15 #include "DragController_p.h"
17 #include "Qt5Qt6Compat_p.h"
19 #include "DockRegistry_p.h"
20 #include "MDILayoutWidget_p.h"
23 #include <QMouseEvent>
25 #include <QGuiApplication>
28 #include <QScopedValueRollback>
31 #include <QtGui/private/qhighdpiscaling_p.h>
35 #if defined(Q_CC_MSVC)
36 #pragma comment(lib, "Dwmapi.lib")
37 #pragma comment(lib, "User32.lib")
43 bool WidgetResizeHandler::s_disableAllHandlers =
false;
44 WidgetResizeHandler::WidgetResizeHandler(EventFilterMode filterMode, WindowMode windowMode,
QWidgetOrQuick *target)
46 , m_usesGlobalEventFilter(filterMode == EventFilterMode::Global)
47 , m_isTopLevelWindowResizer(windowMode == WindowMode::TopLevel)
52 WidgetResizeHandler::~WidgetResizeHandler()
56 void WidgetResizeHandler::setAllowedResizeSides(CursorPositions sides)
58 mAllowedResizeSides = sides;
61 void WidgetResizeHandler::setResizeGap(
int gap)
66 bool WidgetResizeHandler::isMDI()
const
68 auto frame = qobject_cast<Frame *>(mTarget);
69 return frame && frame->isMDI();
72 bool WidgetResizeHandler::isResizing()
const
74 return m_resizingInProgress;
77 int WidgetResizeHandler::widgetResizeHandlerMargin()
84 if (s_disableAllHandlers)
87 auto widget = qobject_cast<QWidgetOrQuick *>(o);
91 auto me = mouseEvent(e);
95 if (m_isTopLevelWindowResizer) {
98 if (!widget->isTopLevel() || o != mTarget) {
99 if (m_usesGlobalEventFilter) {
103 if (!m_resizingInProgress) {
104 const QPoint globalPos = Qt5Qt6Compat::eventGlobalPos(me);
105 updateCursor(cursorPosition(globalPos));
113 }
else if (isMDI()) {
125 auto frame = firstParentOfType<Frame>(widget);
126 if (frame && frame->isMDIWrapper()) {
128 frame = frame->mdiFrame();
131 if (frame && frame != mTarget) {
132 const bool areSiblings = frame->QWidgetAdapter::parentWidget() == mTarget->parentWidget();
140 if (mTarget->isMaximized())
144 auto cursorPos = cursorPosition(Qt5Qt6Compat::eventGlobalPos(mouseEvent));
145 updateCursor(cursorPos);
149 const int m = widgetResizeHandlerMargin();
151 const QPoint cursorPoint = mTarget->mapFromGlobal(Qt5Qt6Compat::eventGlobalPos(mouseEvent));
155 m_resizingInProgress =
true;
157 Q_EMIT DockRegistry::self()->frameInMDIResizeChanged();
158 mNewPosition = Qt5Qt6Compat::eventGlobalPos(mouseEvent);
159 mCursorPos = cursorPos;
164 m_resizingInProgress =
false;
166 Q_EMIT DockRegistry::self()->frameInMDIResizeChanged();
167 auto frame =
static_cast<Frame *
>(mTarget);
171 frame->mdiLayoutWidget()->setDockWidgetGeometry(frame, frame->QWidgetAdapter::geometry());
176 if (mTarget->isMaximized() || !m_resizingInProgress || mouseEvent->button() !=
Qt::LeftButton)
179 mTarget->releaseMouse();
180 mTarget->releaseKeyboard();
186 if (mTarget->isMaximized())
190 const Frame *frameBeingResized = DockRegistry::self()->frameInMDIResize();
191 const bool otherFrameBeingResized = frameBeingResized && frameBeingResized != mTarget;
192 if (otherFrameBeingResized) {
199 m_resizingInProgress = m_resizingInProgress && (mouseEvent->buttons() &
Qt::LeftButton);
201 const bool consumed = mouseMoveEvent(mouseEvent);
210 bool WidgetResizeHandler::mouseMoveEvent(
QMouseEvent *e)
212 const QPoint globalPos = Qt5Qt6Compat::eventGlobalPos(e);
213 if (!m_resizingInProgress) {
219 const QRect oldGeometry = KDDockWidgets::globalGeometry(mTarget);
220 QRect newGeometry = oldGeometry;
222 QRect parentGeometry;
223 if (!mTarget->isTopLevel()) {
224 auto parent = KDDockWidgets::Private::parentWidget(mTarget);
225 parentGeometry = KDDockWidgets::globalGeometry(parent);
234 switch (mCursorPos) {
238 parentGeometry = parentGeometry.
adjusted(0, m_resizeGap, 0, 0);
239 deltaWidth = oldGeometry.
left() - globalPos.
x();
240 newWidth = qBound(minWidth, mTarget->width() + deltaWidth, maxWidth);
241 deltaWidth = newWidth - mTarget->width();
242 if (deltaWidth != 0) {
243 newGeometry.
setLeft(newGeometry.
left() - deltaWidth);
252 parentGeometry = parentGeometry.
adjusted(0, 0, -m_resizeGap, 0);
253 deltaWidth = globalPos.
x() - newGeometry.
right();
254 newWidth = qBound(minWidth, mTarget->width() + deltaWidth, maxWidth);
255 deltaWidth = newWidth - mTarget->width();
256 if (deltaWidth != 0) {
271 switch (mCursorPos) {
275 parentGeometry = parentGeometry.
adjusted(0, m_resizeGap, 0, 0);
276 deltaHeight = oldGeometry.
top() - globalPos.
y();
277 newHeight = qBound(minHeight, mTarget->height() + deltaHeight, maxHeight);
278 deltaHeight = newHeight - mTarget->height();
279 if (deltaHeight != 0) {
280 newGeometry.
setTop(newGeometry.
top() - deltaHeight);
289 parentGeometry = parentGeometry.
adjusted(0, 0, 0, -m_resizeGap);
290 deltaHeight = globalPos.
y() - newGeometry.
bottom();
291 newHeight = qBound(minHeight, mTarget->height() + deltaHeight, maxHeight);
292 deltaHeight = newHeight - mTarget->height();
293 if (deltaHeight != 0) {
303 if (newGeometry == mTarget->geometry()) {
308 if (!mTarget->isTopLevel()) {
311 newGeometry = newGeometry.
intersected(parentGeometry);
314 newGeometry.
moveTopLeft(mTarget->mapFromGlobal(newGeometry.
topLeft()) + mTarget->pos());
317 mTarget->setGeometry(newGeometry);
324 bool WidgetResizeHandler::handleWindowsNativeEvent(FloatingWindow *fw,
const QByteArray &eventType,
325 void *message, Qt5Qt6Compat::qintptr *result)
327 if (eventType !=
"windows_generic_MSG")
330 auto msg =
static_cast<MSG *
>(message);
331 if (msg->message == WM_NCHITTEST) {
332 if (DragController::instance()->isInClientDrag()) {
338 const QRect htCaptionRect = fw->dragRect();
339 const bool ret = handleWindowsNativeEvent(fw->windowHandle(), msg, result, htCaptionRect);
341 fw->setLastHitTest(*result);
343 }
else if (msg->message == WM_NCLBUTTONDBLCLK) {
344 if ((
Config::self().flags() & Config::Flag_DoubleClickMaximizes)) {
345 return handleWindowsNativeEvent(fw->windowHandle(), msg, result, {});
348 if (TitleBar *titleBar = fw->titleBar()) {
349 if (titleBar->isVisible()) {
350 titleBar->onDoubleClicked();
358 return handleWindowsNativeEvent(fw->windowHandle(), msg, result, {});
361 bool WidgetResizeHandler::handleWindowsNativeEvent(
QWindow *w, MSG *msg,
362 Qt5Qt6Compat::qintptr *result,
363 const NativeFeatures &features)
365 if (msg->message == WM_NCCALCSIZE && features.hasShadow()) {
368 }
else if (msg->message == WM_NCHITTEST && (features.hasResize() || features.hasDrag())) {
369 const int borderWidth = 8;
374 const int xPos = GET_X_LPARAM(msg->lParam);
375 const int yPos = GET_Y_LPARAM(msg->lParam);
377 GetWindowRect(
reinterpret_cast<HWND
>(w->
winId()), &rect);
379 if (xPos >= rect.left && xPos <= rect.left + borderWidth && yPos <= rect.bottom && yPos >= rect.bottom - borderWidth && features.hasResize()) {
380 *result = HTBOTTOMLEFT;
381 }
else if (xPos < rect.right && xPos >= rect.right - borderWidth && yPos <= rect.bottom && yPos >= rect.bottom - borderWidth && features.hasResize()) {
382 *result = HTBOTTOMRIGHT;
383 }
else if (xPos >= rect.left && xPos <= rect.left + borderWidth && yPos >= rect.top && yPos <= rect.top + borderWidth && features.hasResize()) {
385 }
else if (xPos <= rect.right && xPos >= rect.right - borderWidth && yPos >= rect.top && yPos < rect.top + borderWidth && features.hasResize()) {
386 *result = HTTOPRIGHT;
387 }
else if (!hasFixedWidth && xPos >= rect.left && xPos <= rect.left + borderWidth && features.hasResize()) {
389 }
else if (!hasFixedHeight && yPos >= rect.top && yPos <= rect.top + borderWidth && features.hasResize()) {
391 }
else if (!hasFixedHeight && yPos <= rect.bottom && yPos >= rect.bottom - borderWidth && features.hasResize()) {
393 }
else if (!hasFixedWidth && xPos <= rect.right && xPos >= rect.right - borderWidth && features.hasResize()) {
395 }
else if (features.hasDrag()) {
396 const QPoint globalPosQt = QHighDpi::fromNativePixels(
QPoint(xPos, yPos), w);
398 const QRect htCaptionRect = features.htCaptionRect;
399 if (globalPosQt.
y() >= htCaptionRect.
top() && globalPosQt.
y() <= htCaptionRect.
bottom() && globalPosQt.
x() >= htCaptionRect.
left() && globalPosQt.
x() <= htCaptionRect.
right()) {
400 if (!KDDockWidgets::inDisallowDragWidget(globalPosQt)) {
407 }
else if (msg->message == WM_NCLBUTTONDBLCLK && features.hasMaximize()) {
413 }
else if (msg->message == WM_GETMINMAXINFO) {
420 if (!screen || w->
screen() != screen) {
424 DefWindowProc(msg->hwnd, msg->message, msg->wParam, msg->lParam);
428 auto mmi =
reinterpret_cast<MINMAXINFO *
>(msg->lParam);
431 mmi->ptMaxSize.y = int(availableGeometry.
height() * dpr);
432 mmi->ptMaxSize.x = int(availableGeometry.
width() * dpr) - 1;
433 mmi->ptMaxPosition.x = availableGeometry.
x();
434 mmi->ptMaxPosition.y = availableGeometry.
y();
453 if (m_usesGlobalEventFilter) {
454 qApp->installEventFilter(
this);
456 mTarget->installEventFilter(
this);
459 qWarning() <<
"Target widget is null!";
465 #ifdef KDDOCKWIDGETS_QTWIDGETS
467 const QObjectList children = mTarget->children();
468 for (
int i = 0, total = children.size(); i < total; ++i) {
469 if (
auto child = qobject_cast<WidgetType *>(children.at(i))) {
496 restoreMouseCursor();
508 if (m_usesGlobalEventFilter)
509 qApp->setOverrideCursor(cursor);
511 mTarget->setCursor(cursor);
514 void WidgetResizeHandler::restoreMouseCursor()
516 if (m_usesGlobalEventFilter)
517 qApp->restoreOverrideCursor();
527 #ifdef KDDOCKWIDGETS_QTQUICK
531 return CursorPosition(mTarget->property(
"cursorPosition").toInt());
535 QPoint pos = mTarget->mapFromGlobal(globalPos);
537 const int x = pos.
x();
538 const int y = pos.
y();
539 const int margin = widgetResizeHandlerMargin();
543 if (y >= -margin && y <= mTarget->height() + margin) {
544 if (qAbs(x) <= margin)
546 else if (qAbs(x - (mTarget->width() - margin)) <= margin)
550 if (x >= -margin && x <= mTarget->width() + margin) {
551 if (qAbs(y) <= margin)
553 else if (qAbs(y - (mTarget->height() - margin)) <= margin)
558 result = result & mAllowedResizeSides;
564 void WidgetResizeHandler::setupWindow(
QWindow *window)
569 #if defined(Q_OS_WIN)
570 if (KDDockWidgets::usesAeroSnapWithCustomDecos()) {
571 const auto wid = HWND(window->
winId());
577 SetWindowPos(wid, 0, 0, 0, 0, 0,
578 SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
581 const bool usesTransparentFloatingWindow =
582 Config::self().internalFlags() & Config::InternalFlag_UseTransparentFloatingWindow;
583 if (!usesTransparentFloatingWindow) {
587 MARGINS margins = { 0, 0, 0, 1 };
588 DwmExtendFrameIntoClientArea(wid, &margins);
597 bool WidgetResizeHandler::isInterestingNativeEvent(
unsigned int nativeEvent)
599 switch (nativeEvent) {
602 case WM_NCLBUTTONDBLCLK:
603 case WM_GETMINMAXINFO:
611 #if defined(Q_OS_WIN) && defined(KDDOCKWIDGETS_QTWIDGETS)
612 bool NCHITTESTEventFilter::nativeEventFilter(
const QByteArray &eventType,
void *message,
613 Qt5Qt6Compat::qintptr *result)
616 if (eventType !=
"windows_generic_MSG" || !m_floatingWindow)
619 auto msg =
static_cast<MSG *
>(message);
620 if (msg->message != WM_NCHITTEST)
622 const WId wid = WId(msg->hwnd);
625 if (!child || child->
window() != m_floatingWindow)
627 const bool isThisWindow = child == m_floatingWindow;
630 *result = HTTRANSPARENT;
639 CustomFrameHelper::CustomFrameHelper(ShouldUseCustomFrame func,
QObject *parent)
642 , m_shouldUseCustomFrameFunc(func)
645 qApp->installNativeEventFilter(
this);
649 CustomFrameHelper::~CustomFrameHelper()
654 void CustomFrameHelper::applyCustomFrame(
QWindow *window)
657 WidgetResizeHandler::setupWindow(window);
660 qWarning() << Q_FUNC_INFO <<
"Not implemented on this platform";
664 bool CustomFrameHelper::nativeEventFilter(
const QByteArray &eventType,
void *message,
665 Qt5Qt6Compat::qintptr *result)
667 if (m_shouldUseCustomFrameFunc ==
nullptr || m_recursionGuard)
673 if (m_inDtor || !KDDockWidgets::usesAeroSnapWithCustomDecos())
676 if (eventType !=
"windows_generic_MSG")
679 auto msg =
static_cast<MSG *
>(message);
680 if (!WidgetResizeHandler::isInterestingNativeEvent(msg->message)) {
685 QWindow *window = DockRegistry::self()->windowForHandle(WId(msg->hwnd));
689 const WidgetResizeHandler::NativeFeatures features = m_shouldUseCustomFrameFunc(window);
690 if (!features.hasFeatures()) {
695 const char *propertyName =
"kddw_customframe_setup_ran";
699 WidgetResizeHandler::setupWindow(window);
703 return WidgetResizeHandler::handleWindowsNativeEvent(window, msg, result, features);