KDDockWidgets API Documentation 2.1
Loading...
Searching...
No Matches
WidgetResizeHandler.cpp
Go to the documentation of this file.
1/*
2 This file is part of KDDockWidgets.
3
4 SPDX-FileCopyrightText: 2019 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
5 Author: SĂ©rgio Martins <sergio.martins@kdab.com>
6
7 SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only
8
9 Contact KDAB at <info@kdab.com> for commercial licensing options.
10*/
11
12#include "WidgetResizeHandler_p.h"
13#include "DragController_p.h"
14#include "Config.h"
15#include "Utils_p.h"
16#include "View_p.h"
17#include "Logging_p.h"
18#include "DockRegistry_p.h"
19
20#include "kddockwidgets/core/DockRegistry.h"
21#include "kddockwidgets/core/MDILayout.h"
22#include "kddockwidgets/core/TitleBar.h"
23#include "kddockwidgets/core/FloatingWindow.h"
24#include "kddockwidgets/core/Platform.h"
25#include "core/ScopedValueRollback_p.h"
26
27#include <cstdlib>
28
29#if defined(Q_OS_WIN)
30#if defined(KDDW_FRONTEND_QTWIDGETS)
31#include "../qtcommon/Platform.h"
32#include <QWidget>
33#endif
34
35#if defined(KDDW_FRONTEND_QT)
36#include "../qtcommon/Window_p.h"
37#include <QGuiApplication>
38#include <QtGui/private/qhighdpiscaling_p.h>
39#endif
40
41#endif
42
43#if defined(Q_OS_WIN)
44
45#include <windowsx.h>
46#include <dwmapi.h>
47#if defined(Q_CC_MSVC)
48#pragma comment(lib, "Dwmapi.lib")
49#pragma comment(lib, "User32.lib")
50#endif
51#endif
52
53using namespace KDDockWidgets;
54using namespace KDDockWidgets::Core;
55
56#if defined(KDDW_FRONTEND_QTQUICK)
57#include "qtcommon/View.h"
58#endif
59
60bool WidgetResizeHandler::s_disableAllHandlers = false;
61WidgetResizeHandler::WidgetResizeHandler(EventFilterMode filterMode, WindowMode windowMode,
62 View *target)
63 : m_usesGlobalEventFilter(filterMode == EventFilterMode::Global)
64 , m_isTopLevelWindowResizer(windowMode == WindowMode::TopLevel)
65{
66 setTarget(target);
67}
68
69WidgetResizeHandler::~WidgetResizeHandler()
70{
71 if (m_usesGlobalEventFilter) {
73 } else if (mTargetGuard) {
74 mTarget->removeViewEventFilter(this);
75 }
76
77 restoreMouseCursor();
78}
79
80void WidgetResizeHandler::setAllowedResizeSides(CursorPositions sides)
81{
82 mAllowedResizeSides = sides;
83}
84
85void WidgetResizeHandler::setResizeGap(int gap)
86{
87 m_resizeGap = gap;
88}
89
90bool WidgetResizeHandler::isMDI() const
91{
92 Core::Group *group = mTarget->asGroupController();
93 return group && group->isMDI();
94}
95
96bool WidgetResizeHandler::isResizing() const
97{
98 return m_resizingInProgress;
99}
100
101int WidgetResizeHandler::widgetResizeHandlerMargin()
102{
103 return 4; // pixels
104}
105
106bool WidgetResizeHandler::onMouseEvent(View *widget, MouseEvent *e)
107{
108 if (s_disableAllHandlers || !widget || !mTargetGuard)
109 return false;
110
111 if (e->type() != Event::MouseButtonPress && e->type() != Event::MouseButtonRelease
112 && e->type() != Event::MouseMove)
113 return false;
114
115 auto me = mouseEvent(e);
116 if (!me)
117 return false;
118
119 if (m_isTopLevelWindowResizer) {
120 // Case #1.0: Resizing FloatingWindow
121
122 if (!widget->isRootView() || !widget->equals(mTarget)) {
123 if (m_usesGlobalEventFilter) {
124 // Case #1.1: FloatingWindows on EGLFS
125 // EGLFS doesn't support storing mouse cursor shape per window, so we need to use
126 // global filter do detect mouse leaving the window
127 if (!m_resizingInProgress) {
128 const Point globalPos = Qt5Qt6Compat::eventGlobalPos(me);
129 updateCursor(cursorPosition(globalPos));
130 }
131 }
132
133 // Case #1.2: FloatingWindows on all other platforms
134 // Not needed to mess with the cursor, it gets set when moving over another window.
135 return false;
136 }
137 } else if (isMDI()) {
138 // Case #2: Resizing an embedded MDI "Window"
139
140 // Each Group has a WidgetResizeHandler instance.
141 // mTarget is the Group we want to resize.
142 // but 'o' might not be mTarget, because we're using a global event filter.
143 // The global event filter is required because we allow the cursor to be outside the group,
144 // a few pixels so we have a nice resize margin. Here we deal with the case where our
145 // mTarget, let's say "Group 1" is on top of "Group 2" but cursor is near "Group 2"'s
146 // margins, and would show resize cursor. We only want to continue if the cursor is near the
147 // margins of our own group (mTarget)
148
149 auto f = widget->d->firstParentOfType(ViewType::Group);
150 auto group = f ? f->view()->asGroupController() : nullptr;
151 if (group && group->isMDIWrapper()) {
152 // We don't care about the inner Option_MDINestable helper group
153 group = group->mdiFrame();
154 }
155
156 if (group && !group->view()->equals(mTarget)) {
157 auto groupParent =
158 group->view()->d->aboutToBeDestroyed() ? nullptr : group->view()->parentView();
159 auto targetParent = mTarget->d->aboutToBeDestroyed() ? nullptr : mTarget->parentView();
160 const bool areSiblings = groupParent && groupParent->equals(targetParent);
161 if (areSiblings) {
162 if (cursorPosition(Qt5Qt6Compat::eventGlobalPos(e)) == CursorPosition_Undefined)
163 restoreMouseCursor();
164 return false;
165 }
166 }
167 }
168
169 switch (e->type()) {
170 case Event::MouseButtonPress: {
171 if (mTarget->isMaximized())
172 break;
173
174 CursorPosition cursorPos = cursorPosition(Qt5Qt6Compat::eventGlobalPos(e));
175 updateCursor(cursorPos);
176 if (cursorPos == CursorPosition_Undefined)
177 return false;
178
179 const int m = widgetResizeHandlerMargin();
180 const Rect widgetRect = mTarget->rect().marginsAdded(Margins(m, m, m, m));
181 const Point cursorPoint = mTarget->mapFromGlobal(Qt5Qt6Compat::eventGlobalPos(e));
182 if (!widgetRect.contains(cursorPoint) || e->button() != Qt::LeftButton)
183 return false;
184
185 m_resizingInProgress = true;
186 if (isMDI())
187 DockRegistry::self()->dptr()->groupInMDIResizeChanged.emit();
188 mNewPosition = Qt5Qt6Compat::eventGlobalPos(e);
189 mCursorPos = cursorPos;
190
191 return true;
192 }
193 case Event::MouseButtonRelease: {
194 m_resizingInProgress = false;
195 if (isMDI()) {
196 DockRegistry::self()->dptr()->groupInMDIResizeChanged.emit();
197 // Usually in KDDW all geometry changes are done in the layout items, which propagate to
198 // the widgets When resizing a MDI however, we're resizing the widget directly. So
199 // update the corresponding layout item when we're finished.
200 auto group = mTarget->asGroupController();
201 group->mdiLayout()->setDockWidgetGeometry(group, group->geometry());
202 }
203 updateCursor(CursorPosition_Undefined);
204 auto cursorPos = cursorPosition(Qt5Qt6Compat::eventGlobalPos(e));
205 updateCursor(cursorPos);
206 if (mTarget->isMaximized() || !m_resizingInProgress || e->button() != Qt::LeftButton)
207 break;
208
209 mTarget->releaseMouse();
210 mTarget->releaseKeyboard();
211 if (m_eventFilteringStartsManually)
213
214 return true;
215
216 break;
217 }
218 case Event::MouseMove: {
219 if (mTarget->isMaximized())
220 break;
221
222 if (isMDI()) {
223 const Core::Group *groupBeingResized = DockRegistry::self()->groupInMDIResize();
224 const bool otherGroupBeingResized =
225 groupBeingResized && groupBeingResized->view() != mTarget;
226 if (otherGroupBeingResized) {
227 // only one at a time!
228 return false;
229 }
230 }
231
232 m_resizingInProgress = m_resizingInProgress && (e->buttons() & Qt::LeftButton);
233 const bool consumed = mouseMoveEvent(e);
234 return consumed;
235 }
236 default:
237 break;
238 }
239 return false;
240}
241
242bool WidgetResizeHandler::mouseMoveEvent(MouseEvent *e)
243{
244 const Point globalPos = Qt5Qt6Compat::eventGlobalPos(e);
245 if (!m_resizingInProgress) {
246 const CursorPosition pos = cursorPosition(globalPos);
247 updateCursor(pos);
248 return pos != CursorPosition_Undefined;
249 }
250
251 const Rect oldGeometry = mTarget->d->globalGeometry();
252 Rect newGeometry = oldGeometry;
253
254 Rect parentGeometry;
255 if (!mTarget->isRootView()) {
256 auto parent = mTarget->parentView();
257 parentGeometry = parent->d->globalGeometry();
258 }
259
260 {
261 int deltaWidth = 0;
262 int newWidth = 0;
263 const int maxWidth = mTarget->maxSizeHint().width();
264 const int minWidth = mTarget->minSize().width();
265
266 switch (mCursorPos) {
270 parentGeometry = parentGeometry.adjusted(m_resizeGap, 0, 0, 0);
271 deltaWidth = oldGeometry.left() - globalPos.x();
272 newWidth = bound(minWidth, mTarget->width() + deltaWidth, maxWidth);
273 deltaWidth = newWidth - mTarget->width();
274 if (deltaWidth != 0) {
275 newGeometry.setLeft(newGeometry.left() - deltaWidth);
276 }
277
278 break;
279 }
280
284 parentGeometry = parentGeometry.adjusted(0, 0, -m_resizeGap, 0);
285 deltaWidth = globalPos.x() - newGeometry.right();
286 newWidth = bound(minWidth, mTarget->width() + deltaWidth, maxWidth);
287 deltaWidth = newWidth - mTarget->width();
288 if (deltaWidth != 0) {
289 newGeometry.setRight(oldGeometry.right() + deltaWidth);
290 }
291 break;
292 }
293 default:
294 break;
295 }
296 }
297
298 {
299 const int maxHeight = mTarget->maxSizeHint().height();
300 const int minHeight = mTarget->minSize().height();
301 int deltaHeight = 0;
302 int newHeight = 0;
303 switch (mCursorPos) {
307 parentGeometry = parentGeometry.adjusted(0, m_resizeGap, 0, 0);
308 deltaHeight = oldGeometry.top() - globalPos.y();
309 newHeight = bound(minHeight, mTarget->height() + deltaHeight, maxHeight);
310 deltaHeight = newHeight - mTarget->height();
311 if (deltaHeight != 0) {
312 newGeometry.setTop(newGeometry.top() - deltaHeight);
313 }
314
315 break;
316 }
317
321 parentGeometry = parentGeometry.adjusted(0, 0, 0, -m_resizeGap);
322 deltaHeight = globalPos.y() - newGeometry.bottom();
323 newHeight = bound(minHeight, mTarget->height() + deltaHeight, maxHeight);
324 deltaHeight = newHeight - mTarget->height();
325 if (deltaHeight != 0) {
326 newGeometry.setBottom(oldGeometry.bottom() + deltaHeight);
327 }
328 break;
329 }
330 default:
331 break;
332 }
333 }
334
335 if (newGeometry == mTarget->geometry()) {
336 // Nothing to do.
337 return true;
338 }
339
340 if (!mTarget->isRootView()) {
341
342 // Clip to parent's geometry.
343 newGeometry = newGeometry.intersected(parentGeometry);
344
345 // Back to local.
346 newGeometry.moveTopLeft(mTarget->mapFromGlobal(newGeometry.topLeft()) + mTarget->pos());
347 }
348
349 mTarget->setGeometry(newGeometry);
350 return true;
351}
352
353#ifdef KDDW_FRONTEND_QT_WINDOWS
354
355void WidgetResizeHandler::requestNCCALCSIZE(HWND winId)
356{
357 SetWindowPos(winId, 0, 0, 0, 0, 0,
358 SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
359}
360
362bool WidgetResizeHandler::handleWindowsNativeEvent(Core::FloatingWindow *fw,
363 const QByteArray &eventType, void *message,
364 Qt5Qt6Compat::qintptr *result)
365{
366 if (eventType != "windows_generic_MSG")
367 return false;
368
369 auto msg = static_cast<MSG *>(message);
370 if (msg->message == WM_NCHITTEST) {
371 if (DragController::instance()->isInClientDrag()) {
372 // There's a non-native drag going on.
373 *result = 0;
374 return false;
375 }
376
377 const Rect htCaptionRect = fw->dragRect();
378 const bool ret = handleWindowsNativeEvent(fw->view()->window(), msg, result, htCaptionRect);
379
380 fw->setLastHitTest(*result);
381 return ret;
382 } else if (msg->message == WM_NCLBUTTONDBLCLK) {
384 return handleWindowsNativeEvent(fw->view()->window(), msg, result, {});
385 } else {
386 // Let the title bar handle it. It will re-dock the window.
387 if (Core::TitleBar *titleBar = fw->titleBar()) {
388 if (titleBar->isVisible()) { // can't be invisible afaik
389 titleBar->onDoubleClicked();
390 }
391 }
392
393 return true;
394 }
395 }
396
397 return handleWindowsNativeEvent(fw->view()->window(), msg, result, {});
398}
399
400bool WidgetResizeHandler::handleWindowsNativeEvent(Core::Window::Ptr w, MSG *msg,
401 Qt5Qt6Compat::qintptr *result,
402 const NativeFeatures &features)
403{
404 if (msg->message == WM_NCCALCSIZE && features.hasShadow()) {
405 if (w->windowState() == WindowState::Minimized && w->hasBeenMinimizedDirectlyFromRestore()) {
406 // Qt is buggy with custom WM_NCCALCSIZE if window is minimized.
407 // Use full frame when minimized. We'll trigger WM_NCCALCSIZE when un-minimized.
408 return false;
409 }
410
411 *result = 0;
412 return true;
413 } else if (msg->message == WM_NCHITTEST && (features.hasResize() || features.hasDrag())) {
414 const int borderWidth = 8;
415 const bool hasFixedWidth = w->minWidth() == w->maxWidth();
416 const bool hasFixedHeight = w->minHeight() == w->maxHeight();
417 const bool hasFixedWidthOrHeight = hasFixedWidth || hasFixedHeight;
418
419 *result = 0;
420 const int xPos = GET_X_LPARAM(msg->lParam);
421 const int yPos = GET_Y_LPARAM(msg->lParam);
422 RECT rect;
423 GetWindowRect(reinterpret_cast<HWND>(w->handle()), &rect);
424
425 if (!hasFixedWidthOrHeight && xPos >= rect.left && xPos <= rect.left + borderWidth && yPos <= rect.bottom
426 && yPos >= rect.bottom - borderWidth && features.hasResize()) {
427 *result = HTBOTTOMLEFT;
428 } else if (!hasFixedWidthOrHeight && xPos < rect.right && xPos >= rect.right - borderWidth && yPos <= rect.bottom
429 && yPos >= rect.bottom - borderWidth && features.hasResize()) {
430 *result = HTBOTTOMRIGHT;
431 } else if (!hasFixedWidthOrHeight && xPos >= rect.left && xPos <= rect.left + borderWidth && yPos >= rect.top
432 && yPos <= rect.top + borderWidth && features.hasResize()) {
433 *result = HTTOPLEFT;
434 } else if (!hasFixedWidthOrHeight && xPos <= rect.right && xPos >= rect.right - borderWidth && yPos >= rect.top
435 && yPos < rect.top + borderWidth && features.hasResize()) {
436 *result = HTTOPRIGHT;
437 } else if (!hasFixedWidth && xPos >= rect.left && xPos <= rect.left + borderWidth
438 && features.hasResize()) {
439 *result = HTLEFT;
440 } else if (!hasFixedHeight && yPos >= rect.top && yPos <= rect.top + borderWidth
441 && features.hasResize()) {
442 *result = HTTOP;
443 } else if (!hasFixedHeight && yPos <= rect.bottom && yPos >= rect.bottom - borderWidth
444 && features.hasResize()) {
445 *result = HTBOTTOM;
446 } else if (!hasFixedWidth && xPos <= rect.right && xPos >= rect.right - borderWidth
447 && features.hasResize()) {
448 *result = HTRIGHT;
449 } else if (features.hasDrag()) {
450 const Point globalPosQt = w->fromNativePixels(Point(xPos, yPos));
451 // htCaptionRect is the rect on which we allow for Windows to do a native drag
452 const Rect htCaptionRect = features.htCaptionRect;
453 if (globalPosQt.y() >= htCaptionRect.top() && globalPosQt.y() <= htCaptionRect.bottom()
454 && globalPosQt.x() >= htCaptionRect.left()
455 && globalPosQt.x() <= htCaptionRect.right()) {
456 if (!Platform::instance()->inDisallowedDragView(
457 globalPosQt)) { // Just makes sure the mouse isn't over the close button, we
458 // don't allow drag in that case.
459 *result = HTCAPTION;
460 }
461 }
462 }
463
464 return *result != 0;
465 } else if (msg->message == WM_NCLBUTTONDBLCLK && features.hasMaximize()) {
466 // By returning false we accept Windows native action, a maximize.
467 // We could also call titleBar->onDoubleClicked(); here which will maximize if
468 // Flag_DoubleClickMaximizes is set, but there's a bug in QWidget::showMaximized() on
469 // Windows when we're covering the native title bar, the window is maximized with an offset.
470 // So instead, use a native maximize which works well
471 return false;
472 } else if (msg->message == WM_GETMINMAXINFO) {
473 // Qt doesn't work well with windows that don't have title bar but have native frames.
474 // When maximized they go out of bounds and the title bar is clipped, so catch
475 // WM_GETMINMAXINFO and patch the size
476
477 // According to microsoft docs it only works for the primary screen, but extrapolates for
478 // the others
479 auto screen = Platform::instance()->primaryScreen();
480 if (!screen || w->screen() != screen) {
481 return false;
482 }
483
484 DefWindowProc(msg->hwnd, msg->message, msg->wParam, msg->lParam);
485
486 const Rect availableGeometry = screen->availableGeometry();
487
488 auto mmi = reinterpret_cast<MINMAXINFO *>(msg->lParam);
489 const double dpr = screen->devicePixelRatio();
490
491 mmi->ptMaxSize.y = int(availableGeometry.height() * dpr);
492 mmi->ptMaxSize.x =
493 int(availableGeometry.width() * dpr) - 1; // -1 otherwise it gets bogus size
494 mmi->ptMaxPosition.x = availableGeometry.x();
495 mmi->ptMaxPosition.y = availableGeometry.y();
496
497 mmi->ptMinTrackSize.x = int(w->minWidth() * dpr);
498 mmi->ptMinTrackSize.y = int(w->minHeight() * dpr);
499
500 *result = 0;
501 return true;
502 }
503
504 return false;
505}
506
507#endif
508
509void WidgetResizeHandler::setTarget(View *w)
510{
511 if (w) {
512 mTarget = w;
513 mTargetGuard = w;
514 mTarget->setMouseTracking(true);
515 if (m_usesGlobalEventFilter) {
517 } else {
518 mTarget->installViewEventFilter(this);
519 }
520 } else {
521 KDDW_ERROR("Target widget is null!");
522 }
523}
524
525void WidgetResizeHandler::updateCursor(CursorPosition m)
526{
527 if (!m_handlesMouseCursor)
528 return;
529
530 if (!mTargetGuard) {
531 // Our target was destroyed while we're processing mouse events, it's fine.
532 restoreMouseCursor();
533 return;
534 }
535
536 if (Platform::instance()->isQtWidgets()) {
537 // Need for updating cursor when we change child widget
538 const auto childViews = mTarget->childViews();
539 for (const auto &child : childViews) {
540 if (!child->hasAttribute(Qt::WA_SetCursor)) {
541 child->setCursor(Qt::ArrowCursor);
542 }
543 }
544 }
545 switch (m) {
548 setMouseCursor(Qt::SizeFDiagCursor);
549 break;
552 setMouseCursor(Qt::SizeBDiagCursor);
553 break;
556 setMouseCursor(Qt::SizeVerCursor);
557 break;
560 setMouseCursor(Qt::SizeHorCursor);
561 break;
563 restoreMouseCursor();
564 break;
568 // Doesn't happen
569 break;
570 }
571}
572
573void WidgetResizeHandler::setMouseCursor(Qt::CursorShape cursor)
574{
575 if (m_usesGlobalEventFilter) {
576 Platform::instance()->setMouseCursor(cursor, /*discardLast=*/m_overrideCursorSet);
577 m_overrideCursorSet = true;
578 } else if (mTargetGuard) {
579 mTarget->setCursor(cursor);
580 }
581}
582
583void WidgetResizeHandler::restoreMouseCursor()
584{
585 if (!m_handlesMouseCursor)
586 return;
587
588 if (m_usesGlobalEventFilter) {
589 if (m_overrideCursorSet) {
591 m_overrideCursorSet = false;
592 }
593 } else if (mTargetGuard) {
594 mTarget->setCursor(Qt::ArrowCursor);
595 }
596}
597
598CursorPosition WidgetResizeHandler::cursorPosition_(Point globalPos) const
599{
600#ifdef KDDW_FRONTEND_QTQUICK
601 if (Platform::instance()->isQtQuick() && isMDI()) {
602 // Special case for QtQuick. The MouseAreas are driving it and know better what's the
603 // cursor position
604 auto qtview = static_cast<KDDockWidgets::QtCommon::View_qt *>(mTarget);
605 const QVariant v = qtview->viewProperty("cursorPosition");
606 if (v.isValid()) {
607 auto pos = CursorPosition(v.toInt());
608 return pos;
609 }
610 }
611#endif
612
613 Point pos = mTarget->mapFromGlobal(globalPos);
614
615 const int x = pos.x();
616 const int y = pos.y();
617 const int margin = widgetResizeHandlerMargin();
618
620 if (y >= -margin && y <= mTarget->height() + margin) {
621 if (std::abs(x) <= margin)
622 result |= CursorPosition_Left;
623 else if (std::abs(x - (mTarget->width() - margin)) <= margin)
624 result |= CursorPosition_Right;
625 }
626
627 if (x >= -margin && x <= mTarget->width() + margin) {
628 if (std::abs(y) <= margin)
629 result |= CursorPosition_Top;
630 else if (std::abs(y - (mTarget->height() - margin)) <= margin)
631 result |= CursorPosition_Bottom;
632 }
633
634 // Filter out sides we don't allow
635 result = result & mAllowedResizeSides;
636
637 return static_cast<CursorPosition>(result);
638}
639
640CursorPosition WidgetResizeHandler::cursorPosition(Point globalPos) const
641{
642 if (!mTargetGuard)
644
645 auto candidatePos = cursorPosition_(globalPos);
646
647 if (isMDI()) {
648 int result = int(candidatePos);
649
650 if (auto group = mTarget->asGroupController()) {
651 // Honour fixed size. Don't show mouse shape for resizing.
652 if (group->isFixedHeight())
653 result &= ~CursorPosition_Vertical;
654
655 if (group->isFixedWidth())
656 result &= ~CursorPosition_Horizontal;
657 } else {
658 KDDW_ERROR("WidgetResizeHandler::cursorPosition: Expected group");
659 }
660
661 return static_cast<CursorPosition>(result);
662 } else {
663 // For regular docking there's other mechanisms to enforce fixed size
664 return candidatePos;
665 }
666}
667
669void WidgetResizeHandler::setupWindow(Core::Window::Ptr window)
670{
671 // Does some minor setup on our QWindow.
672 // Like adding the drop shadow on Windows and two other workarounds.
673#ifdef KDDW_FRONTEND_QT_WINDOWS
674 if (KDDockWidgets::usesAeroSnapWithCustomDecos()) {
675 const auto wid = HWND(window->handle());
676 window->onScreenChanged(nullptr, [](QObject *, Window::Ptr win) {
677 // Qt honors our frame hijacking usually... but when screen changes we must give it a
678 // nudge. Otherwise what Qt thinks is the client area is not what Windows knows it is.
679 // SetWindowPos() will trigger an NCCALCSIZE message, which Qt will intercept and take
680 // note of the margins we're using.
681 requestNCCALCSIZE(HWND(win->handle()));
682 });
683
684 const bool usesTransparentFloatingWindow =
686 if (!usesTransparentFloatingWindow) {
687 // This enables the native drop shadow.
688 // Doesn't work well if the floating window has transparent round corners (shows weird
689 // white line).
690
691 MARGINS margins = { 0, 0, 0, 1 }; // arbitrary, just needs to be > 0 it seems
692 DwmExtendFrameIntoClientArea(wid, &margins);
693 }
694 }
695#else
696 KDDW_UNUSED(window);
697#endif // Q_OS_WIN
698}
699
700#ifdef KDDW_FRONTEND_QT_WINDOWS
701bool WidgetResizeHandler::isInterestingNativeEvent(unsigned int nativeEvent)
702{
703 switch (nativeEvent) {
704 case WM_NCHITTEST:
705 case WM_NCCALCSIZE:
706 case WM_NCLBUTTONDBLCLK:
707 case WM_GETMINMAXINFO:
708 return true;
709 default:
710 return false;
711 }
712}
713#endif
714
715#if defined(Q_OS_WIN) && defined(KDDW_FRONTEND_QTWIDGETS)
716bool NCHITTESTEventFilter::nativeEventFilter(const QByteArray &eventType, void *message,
717 Qt5Qt6Compat::qintptr *result)
718
719{
720 if (eventType != "windows_generic_MSG" || !m_guard)
721 return false;
722
723 auto msg = static_cast<MSG *>(message);
724 if (msg->message != WM_NCHITTEST)
725 return false;
726 const WId wid = WId(msg->hwnd);
727
729
730 if (!child || !m_floatingWindow->equals(child->rootView()))
731 return false;
732 const bool isThisWindow = m_floatingWindow->equals(child);
733
734 if (!isThisWindow) {
735 *result = HTTRANSPARENT;
736 return true;
737 }
738
739 return false;
740}
741#endif
742
743void WidgetResizeHandler::setEventFilterStartsManually()
744{
745 m_eventFilteringStartsManually = true;
747}
748
749void WidgetResizeHandler::setHandlesMouseCursor(bool handles)
750{
751 m_handlesMouseCursor = handles;
752}
Application-wide config to tune certain behaviours of the framework.
#define KDDW_UNUSED(name)
InternalFlags internalFlags() const
Definition Config.cpp:312
@ InternalFlag_UseTransparentFloatingWindow
Definition Config.h:164
static Config & self()
returns the singleton Config instance
Definition Config.cpp:88
View * view() const
Returns the view associated with this controller, if any.
Core::TitleBar * titleBar() const
Returns the title bar.
bool isMDI() const
Returns whether this group is in a MDI layout Usually no, unless you're using an MDI main window.
bool isMDIWrapper() const
Returns whether this group was created automatically just for the purpose of supporting DockWidgetOpt...
Group * mdiFrame() const
If this group is an MDI wrapper, returns the MDI group. That is the group you actually drag inside th...
MDILayout * mdiLayout() const
Returns the MDI layout. Or nullptr if this group isn't in a MDI layout.
void setDockWidgetGeometry(Core::Group *group, Rect)
sets the size and position of the dock widget group
virtual void setMouseCursor(Qt::CursorShape, bool discardLast=false)=0
Sets the mouse cursor to the specified shape, this has an application-wide effect Call restoreMouseCu...
virtual void restoreMouseCursor()=0
Undoes the call to setMouseCursor()
virtual std::shared_ptr< Screen > primaryScreen() const =0
static Platform * instance()
Returns the platform singleton.
void removeGlobalEventFilter(EventFilterInterface *)
Removes a global event filter.
void installGlobalEventFilter(EventFilterInterface *)
Installs a global event filter Events will be forwarded to the specified EventFilterInterface.
bool equals(const View *other) const
Returns whether this view represents the same GUI element as the other.
virtual void setMouseTracking(bool)=0
virtual bool isRootView() const =0
virtual std::shared_ptr< View > parentView() const =0
Returns the gui element's parent. Like QWidget::parentWidget()
Core::Group * asGroupController() const
virtual std::shared_ptr< Core::Window > window() const =0
Returns the window this view is inside For the Qt frontend, this wraps a QWindow. Like QWidget::windo...
Core::Group * groupInMDIResize() const
Returns the Group which is being resized in a MDI layout. nullptr if none.
static DockRegistry * self()
virtual std::shared_ptr< Core::View > qobjectAsView(QObject *) const =0
Returns the specified QObject casted to View Nullptr if it's not a view.
Class to abstract QAction, so code still works with QtQuick and Flutter.
T bound(T minVal, T value, T maxVal)
ArrowCursor
LeftButton
WA_SetCursor
bool isValid() const const
int toInt(bool *ok) const const
QWidget * find(WId id)

© Klarälvdalens Datakonsult AB (KDAB)
"The Qt, C++ and OpenGL Experts"
https://www.kdab.com/
KDDockWidgets
Advanced Dock Widget Framework for Qt
https://www.kdab.com/development-resources/qt-tools/kddockwidgets/
Generated by doxygen 1.9.8