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(
bool isTopLevelResizer,
QWidgetOrQuick *target)
46 , m_isTopLevelWindowResizer(isTopLevelResizer)
51 WidgetResizeHandler::~WidgetResizeHandler()
55 void WidgetResizeHandler::setAllowedResizeSides(CursorPositions sides)
57 mAllowedResizeSides = sides;
60 void WidgetResizeHandler::setResizeGap(
int gap)
65 bool WidgetResizeHandler::isMDI()
const
67 auto frame = qobject_cast<Frame *>(mTarget);
68 return frame && frame->isMDI();
71 bool WidgetResizeHandler::isResizing()
const
73 return m_resizingInProgress;
76 int WidgetResizeHandler::widgetResizeHandlerMargin()
83 if (s_disableAllHandlers)
86 auto widget = qobject_cast<QWidgetOrQuick *>(o);
90 if (m_isTopLevelWindowResizer && (!widget->isTopLevel() || o != mTarget))
95 if (mTarget->isMaximized())
99 auto cursorPos = cursorPosition(Qt5Qt6Compat::eventGlobalPos(mouseEvent));
100 updateCursor(cursorPos);
104 const int m = widgetResizeHandlerMargin();
106 const QPoint cursorPoint = mTarget->mapFromGlobal(Qt5Qt6Compat::eventGlobalPos(mouseEvent));
110 m_resizingInProgress =
true;
112 Q_EMIT DockRegistry::self()->frameInMDIResizeChanged();
113 mNewPosition = Qt5Qt6Compat::eventGlobalPos(mouseEvent);
114 mCursorPos = cursorPos;
119 m_resizingInProgress =
false;
121 Q_EMIT DockRegistry::self()->frameInMDIResizeChanged();
122 auto frame =
static_cast<Frame *
>(mTarget);
126 frame->mdiLayoutWidget()->setDockWidgetGeometry(frame, frame->QWidgetAdapter::geometry());
131 if (mTarget->isMaximized() || !m_resizingInProgress || mouseEvent->button() !=
Qt::LeftButton)
134 mTarget->releaseMouse();
135 mTarget->releaseKeyboard();
141 if (mTarget->isMaximized())
145 const Frame *frameBeingResized = DockRegistry::self()->frameInMDIResize();
146 const bool otherFrameBeingResized = frameBeingResized && frameBeingResized != mTarget;
147 if (otherFrameBeingResized) {
154 m_resizingInProgress = m_resizingInProgress && (mouseEvent->buttons() &
Qt::LeftButton);
155 const bool state = m_resizingInProgress;
156 if (m_isTopLevelWindowResizer)
157 m_resizingInProgress = ((o == mTarget) && m_resizingInProgress);
158 const bool consumed = mouseMoveEvent(mouseEvent);
159 m_resizingInProgress = state;
168 bool WidgetResizeHandler::mouseMoveEvent(
QMouseEvent *e)
170 const QPoint globalPos = Qt5Qt6Compat::eventGlobalPos(e);
171 if (!m_resizingInProgress) {
177 const QRect oldGeometry = KDDockWidgets::globalGeometry(mTarget);
178 QRect newGeometry = oldGeometry;
180 QRect parentGeometry;
181 if (!mTarget->isTopLevel()) {
182 auto parent = KDDockWidgets::Private::parentWidget(mTarget);
183 parentGeometry = KDDockWidgets::globalGeometry(parent);
192 switch (mCursorPos) {
196 parentGeometry = parentGeometry.
adjusted(0, m_resizeGap, 0, 0);
197 deltaWidth = oldGeometry.
left() - globalPos.
x();
198 newWidth = qBound(minWidth, mTarget->width() + deltaWidth, maxWidth);
199 deltaWidth = newWidth - mTarget->width();
200 if (deltaWidth != 0) {
201 newGeometry.
setLeft(newGeometry.
left() - deltaWidth);
210 parentGeometry = parentGeometry.
adjusted(0, 0, -m_resizeGap, 0);
211 deltaWidth = globalPos.
x() - newGeometry.
right();
212 newWidth = qBound(minWidth, mTarget->width() + deltaWidth, maxWidth);
213 deltaWidth = newWidth - mTarget->width();
214 if (deltaWidth != 0) {
229 switch (mCursorPos) {
233 parentGeometry = parentGeometry.
adjusted(0, m_resizeGap, 0, 0);
234 deltaHeight = oldGeometry.
top() - globalPos.
y();
235 newHeight = qBound(minHeight, mTarget->height() + deltaHeight, maxHeight);
236 deltaHeight = newHeight - mTarget->height();
237 if (deltaHeight != 0) {
238 newGeometry.
setTop(newGeometry.
top() - deltaHeight);
247 parentGeometry = parentGeometry.
adjusted(0, 0, 0, -m_resizeGap);
248 deltaHeight = globalPos.
y() - newGeometry.
bottom();
249 newHeight = qBound(minHeight, mTarget->height() + deltaHeight, maxHeight);
250 deltaHeight = newHeight - mTarget->height();
251 if (deltaHeight != 0) {
261 if (newGeometry == mTarget->geometry()) {
266 if (!mTarget->isTopLevel()) {
269 newGeometry = newGeometry.
intersected(parentGeometry);
272 newGeometry.
moveTopLeft(mTarget->mapFromGlobal(newGeometry.
topLeft()) + mTarget->pos());
275 mTarget->setGeometry(newGeometry);
282 bool WidgetResizeHandler::handleWindowsNativeEvent(FloatingWindow *fw,
const QByteArray &eventType,
283 void *message, Qt5Qt6Compat::qintptr *result)
285 if (eventType !=
"windows_generic_MSG")
288 auto msg =
static_cast<MSG *
>(message);
289 if (msg->message == WM_NCHITTEST) {
290 if (DragController::instance()->isInClientDrag()) {
296 const QRect htCaptionRect = fw->dragRect();
297 const bool ret = handleWindowsNativeEvent(fw->windowHandle(), msg, result, htCaptionRect);
299 fw->setLastHitTest(*result);
301 }
else if (msg->message == WM_NCLBUTTONDBLCLK) {
302 if ((
Config::self().flags() & Config::Flag_DoubleClickMaximizes)) {
303 return handleWindowsNativeEvent(fw->windowHandle(), msg, result, {});
306 if (TitleBar *titleBar = fw->titleBar()) {
307 if (titleBar->isVisible()) {
308 titleBar->onDoubleClicked();
316 return handleWindowsNativeEvent(fw->windowHandle(), msg, result, {});
319 bool WidgetResizeHandler::handleWindowsNativeEvent(
QWindow *w, MSG *msg,
320 Qt5Qt6Compat::qintptr *result,
321 const NativeFeatures &features)
323 if (msg->message == WM_NCCALCSIZE && features.hasShadow()) {
326 }
else if (msg->message == WM_NCHITTEST && (features.hasResize() || features.hasDrag())) {
327 const int borderWidth = 8;
332 const int xPos = GET_X_LPARAM(msg->lParam);
333 const int yPos = GET_Y_LPARAM(msg->lParam);
335 GetWindowRect(
reinterpret_cast<HWND
>(w->
winId()), &rect);
337 if (xPos >= rect.left && xPos <= rect.left + borderWidth && yPos <= rect.bottom && yPos >= rect.bottom - borderWidth && features.hasResize()) {
338 *result = HTBOTTOMLEFT;
339 }
else if (xPos < rect.right && xPos >= rect.right - borderWidth && yPos <= rect.bottom && yPos >= rect.bottom - borderWidth && features.hasResize()) {
340 *result = HTBOTTOMRIGHT;
341 }
else if (xPos >= rect.left && xPos <= rect.left + borderWidth && yPos >= rect.top && yPos <= rect.top + borderWidth && features.hasResize()) {
343 }
else if (xPos <= rect.right && xPos >= rect.right - borderWidth && yPos >= rect.top && yPos < rect.top + borderWidth && features.hasResize()) {
344 *result = HTTOPRIGHT;
345 }
else if (!hasFixedWidth && xPos >= rect.left && xPos <= rect.left + borderWidth && features.hasResize()) {
347 }
else if (!hasFixedHeight && yPos >= rect.top && yPos <= rect.top + borderWidth && features.hasResize()) {
349 }
else if (!hasFixedHeight && yPos <= rect.bottom && yPos >= rect.bottom - borderWidth && features.hasResize()) {
351 }
else if (!hasFixedWidth && xPos <= rect.right && xPos >= rect.right - borderWidth && features.hasResize()) {
353 }
else if (features.hasDrag()) {
354 const QPoint globalPosQt = QHighDpi::fromNativePixels(
QPoint(xPos, yPos), w);
356 const QRect htCaptionRect = features.htCaptionRect;
357 if (globalPosQt.
y() >= htCaptionRect.
top() && globalPosQt.
y() <= htCaptionRect.
bottom() && globalPosQt.
x() >= htCaptionRect.
left() && globalPosQt.
x() <= htCaptionRect.
right()) {
358 if (!KDDockWidgets::inDisallowDragWidget(globalPosQt)) {
365 }
else if (msg->message == WM_NCLBUTTONDBLCLK && features.hasMaximize()) {
371 }
else if (msg->message == WM_GETMINMAXINFO) {
378 if (!screen || w->
screen() != screen) {
382 DefWindowProc(msg->hwnd, msg->message, msg->wParam, msg->lParam);
386 auto mmi =
reinterpret_cast<MINMAXINFO *
>(msg->lParam);
389 mmi->ptMaxSize.y = int(availableGeometry.
height() * dpr);
390 mmi->ptMaxSize.x = int(availableGeometry.
width() * dpr) - 1;
391 mmi->ptMaxPosition.x = availableGeometry.
x();
392 mmi->ptMaxPosition.y = availableGeometry.
y();
411 if (m_isTopLevelWindowResizer) {
412 mTarget->installEventFilter(
this);
414 qApp->installEventFilter(
this);
417 qWarning() <<
"Target widget is null!";
423 #ifdef KDDOCKWIDGETS_QTWIDGETS
425 const QObjectList children = mTarget->children();
426 for (
int i = 0, total = children.size(); i < total; ++i) {
427 if (
auto child = qobject_cast<WidgetType *>(children.at(i))) {
454 restoreMouseCursor();
466 if (m_isTopLevelWindowResizer)
467 mTarget->setCursor(cursor);
469 qApp->setOverrideCursor(cursor);
472 void WidgetResizeHandler::restoreMouseCursor()
474 if (m_isTopLevelWindowResizer)
477 qApp->restoreOverrideCursor();
485 #ifdef KDDOCKWIDGETS_QTQUICK
489 return CursorPosition(mTarget->property(
"cursorPosition").toInt());
493 QPoint pos = mTarget->mapFromGlobal(globalPos);
495 const int x = pos.
x();
496 const int y = pos.
y();
497 const int margin = widgetResizeHandlerMargin();
500 if (qAbs(x) <= margin)
502 else if (qAbs(x - (mTarget->width() - margin)) <= margin)
505 if (qAbs(y) <= margin)
507 else if (qAbs(y - (mTarget->height() - margin)) <= margin)
511 result = result & mAllowedResizeSides;
517 void WidgetResizeHandler::setupWindow(
QWindow *window)
522 #if defined(Q_OS_WIN)
523 if (KDDockWidgets::usesAeroSnapWithCustomDecos()) {
524 const auto wid = HWND(window->
winId());
530 SetWindowPos(wid, 0, 0, 0, 0, 0,
531 SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
534 const bool usesTransparentFloatingWindow =
535 Config::self().internalFlags() & Config::InternalFlag_UseTransparentFloatingWindow;
536 if (!usesTransparentFloatingWindow) {
540 MARGINS margins = { 0, 0, 0, 1 };
541 DwmExtendFrameIntoClientArea(wid, &margins);
550 bool WidgetResizeHandler::isInterestingNativeEvent(
unsigned int nativeEvent)
552 switch (nativeEvent) {
555 case WM_NCLBUTTONDBLCLK:
556 case WM_GETMINMAXINFO:
564 #if defined(Q_OS_WIN) && defined(KDDOCKWIDGETS_QTWIDGETS)
565 bool NCHITTESTEventFilter::nativeEventFilter(
const QByteArray &eventType,
void *message,
566 Qt5Qt6Compat::qintptr *result)
569 if (eventType !=
"windows_generic_MSG" || !m_floatingWindow)
572 auto msg =
static_cast<MSG *
>(message);
573 if (msg->message != WM_NCHITTEST)
575 const WId wid = WId(msg->hwnd);
578 if (!child || child->
window() != m_floatingWindow)
580 const bool isThisWindow = child == m_floatingWindow;
583 *result = HTTRANSPARENT;
592 CustomFrameHelper::CustomFrameHelper(ShouldUseCustomFrame func,
QObject *parent)
595 , m_shouldUseCustomFrameFunc(func)
598 qApp->installNativeEventFilter(
this);
602 CustomFrameHelper::~CustomFrameHelper()
607 void CustomFrameHelper::applyCustomFrame(
QWindow *window)
610 WidgetResizeHandler::setupWindow(window);
613 qWarning() << Q_FUNC_INFO <<
"Not implemented on this platform";
617 bool CustomFrameHelper::nativeEventFilter(
const QByteArray &eventType,
void *message,
618 Qt5Qt6Compat::qintptr *result)
620 if (m_shouldUseCustomFrameFunc ==
nullptr || m_recursionGuard)
626 if (m_inDtor || !KDDockWidgets::usesAeroSnapWithCustomDecos())
629 if (eventType !=
"windows_generic_MSG")
632 auto msg =
static_cast<MSG *
>(message);
633 if (!WidgetResizeHandler::isInterestingNativeEvent(msg->message)) {
638 QWindow *window = DockRegistry::self()->windowForHandle(WId(msg->hwnd));
642 const WidgetResizeHandler::NativeFeatures features = m_shouldUseCustomFrameFunc(window);
643 if (!features.hasFeatures()) {
648 const char *propertyName =
"kddw_customframe_setup_ran";
652 WidgetResizeHandler::setupWindow(window);
656 return WidgetResizeHandler::handleWindowsNativeEvent(window, msg, result, features);