#include "FramelessDelegateWin.h" #include #include #include #include // Fixes error C2504: 'IUnknown' : base class undefined #include namespace Gdiplus { using std::min; using std::max; }; #include #include #include #include #include #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) #include #endif #include "FrameTitleBar.h" #pragma comment (lib,"Dwmapi.lib") // Adds missing library, fixes error LNK2019: unresolved external symbol __imp__DwmExtendFrameIntoClientArea #pragma comment (lib,"user32.lib") constexpr unsigned long BorderlessFlag = WS_POPUP | WS_THICKFRAME | WS_CAPTION | WS_SYSMENU | WS_MAXIMIZEBOX | WS_MINIMIZEBOX; inline bool IsCompositionEnabled() { #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) return QtWin::isCompositionEnabled(); #else BOOL enabled = FALSE; DwmIsCompositionEnabled(&enabled); return enabled; #endif } inline void ExtendFrameIntoClientArea(QWindow* window, int left, int top, int right, int bottom) { #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) QtWin::extendFrameIntoClientArea(window, left, top, right, bottom); #else MARGINS margins = { left, right, top, bottom }; DwmExtendFrameIntoClientArea(reinterpret_cast(window->winId()), &margins); #endif } inline std::optional GetScreenNativeWorkRect(HWND hwnd) { auto monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONULL); if (!monitor) return std::nullopt; MONITORINFO info; ::ZeroMemory(&info, sizeof(info)); info.cbSize = sizeof(info); GetMonitorInfo(monitor, &info); return QRect(info.rcWork.left, info.rcWork.top, info.rcWork.right - info.rcWork.left, info.rcWork.bottom - info.rcWork.top); } FramelessDelegate* FramelessDelegate::Create(QWidget* paranet) { return new FramelessDelegateWin(paranet); } FramelessDelegateWin::FramelessDelegateWin(QWidget* parent) : FramelessDelegate(parent){ } FramelessDelegateWin::~FramelessDelegateWin() = default; #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) bool FramelessDelegateWin::nativeEvent(const QByteArray& eventType, void* message, qintptr* result) { #else bool FramelessDelegateWin::nativeEvent(const QByteArray & eventType, void* message, long* result) { #endif if (nullptr == mainWidget_) { return false; } #if (QT_VERSION == QT_VERSION_CHECK(5, 11, 1)) MSG* msg = *reinterpret_cast(message); #else MSG* msg = reinterpret_cast(message); #endif switch (msg->message) { case WM_NCCALCSIZE: { if (msg->wParam) { if (IsZoomed(msg->hwnd)) { CheckMonitorChanged(); auto rc = workRect_; if (!rc) return false; if (auto ret = DefWindowProcW(msg->hwnd, WM_NCCALCSIZE, msg->wParam, msg->lParam); ret) { *result = ret; return true; } NCCALCSIZE_PARAMS* ncParam = reinterpret_cast(msg->lParam); ncParam->rgrc[0].top = rc->top(); ncParam->rgrc[0].bottom = rc->bottom() + 1; *result = 0; return true; } else { const auto clientRect = reinterpret_cast(msg->lParam); const auto before = *clientRect; if (auto ret = DefWindowProcW(msg->hwnd, WM_NCCALCSIZE, msg->wParam, msg->lParam); ret) { *result = ret; return true; } if (!IsCompositionEnabled()) { *clientRect = before; } else { clientRect->top = before.top; clientRect->left = before.left; clientRect->right = before.right; clientRect->bottom = before.bottom + 1; } } } *result = !msg->wParam ? 0 : WVR_REDRAW; return true; } case WM_NCPAINT: { if (!IsCompositionEnabled()) { *result = 0; return true; } else { break; } } case WM_SETFOCUS: { Qt::FocusReason reason; if (::GetKeyState(VK_LBUTTON) < 0 || ::GetKeyState(VK_RBUTTON) < 0) reason = Qt::MouseFocusReason; else if (::GetKeyState(VK_SHIFT) < 0) reason = Qt::BacktabFocusReason; else reason = Qt::TabFocusReason; QFocusEvent e(QEvent::FocusIn, reason); QApplication::sendEvent(mainWidget_, &e); } break; case WM_NCACTIVATE: { if (!IsCompositionEnabled()) { *result = 1; return true; } } break; case WM_NCHITTEST: { return OnNCTitTest(msg, result); } case WM_GETMINMAXINFO: { if (::IsZoomed(msg->hwnd)) { RECT frame = { 0, 0, 0, 0 }; AdjustWindowRectEx(&frame, WS_OVERLAPPEDWINDOW, FALSE, 0); double dpr = mainWidget_->devicePixelRatioF(); frames_.setLeft(abs(frame.left) / dpr + 0.5); frames_.setTop(abs(frame.bottom) / dpr + 0.5); frames_.setRight(abs(frame.right) / dpr + 0.5); frames_.setBottom(abs(frame.bottom) / dpr + 0.5); mainWidget_->setContentsMargins(frames_.left() + margins_.left(), \ frames_.top() + margins_.top(), \ frames_.right() + margins_.right(), \ frames_.bottom() + margins_.bottom()); justMaximized_ = true; } else { if (justMaximized_) { mainWidget_->setContentsMargins(margins_); frames_ = QMargins(); justMaximized_ = false; } } unsigned int flag = FrameTitleBar::FTB_MAX & titleBar_->GetSysteButton(); if (nullptr != titleBar_ && flag) { titleBar_->OnMaximized(justMaximized_); } return false; } case WM_NCLBUTTONDBLCLK: { if (mainWidget_->maximumSize() == QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX) && mainWidget_->windowFlags().testFlag(Qt::WindowMaximizeButtonHint)) return false; else return true; } case WM_NCRBUTTONUP: { ShowSystemMenu(QCursor::pos()); *result = 0; return true; } default: break; } return false; } void FramelessDelegateWin::OnShow() { if (isFirstShow_) { isFirstShow_ = false; SetNativeWindowLong(); } mainWidget_->setAttribute(Qt::WA_Resized); if (!isFirstShow_ && mainWidget_->isMaximized()) { QMetaObject::invokeMethod( this, [=]() { if (wndPlaceMent_.showCmd != SW_SHOWMINIMIZED) SetWindowPlacement((HWND)mainWidget_->winId(), &wndPlaceMent_); }, Qt::QueuedConnection ); } } void FramelessDelegateWin::OnHide() { wndPlaceMent_.length = sizeof(wndPlaceMent_); ::GetWindowPlacement(reinterpret_cast(mainWidget_->winId()), &wndPlaceMent_); } void FramelessDelegateWin::OnClose() { if (mainWidget_->windowFlags().testFlag(Qt::Dialog)) { wndPlaceMent_.length = sizeof(wndPlaceMent_); ::GetWindowPlacement(reinterpret_cast(mainWidget_->winId()), &wndPlaceMent_); } } void FramelessDelegateWin::OnScreenChangeInternaal() { if (mainWidget_->internalWinId()) { SetWindowPos(reinterpret_cast(mainWidget_->winId()), nullptr, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE); } } void FramelessDelegateWin::AddMoveBar(QWidget* moveWidget) { if (!IsCompositionEnabled() && qobject_cast(moveWidget)) { moveWidget->winId(); } if (!moveBars_.contains(moveWidget)) moveBars_.append(moveWidget); } double FramelessDelegateWin::DpiScale(double value) { if (nullptr == mainWidget_) { return 1.0; } return value / mainWidget_->devicePixelRatioF(); } double FramelessDelegateWin::UnDpiScale(double value) { if (nullptr == mainWidget_) { return 1.0; } return value * mainWidget_->devicePixelRatioF(); } void FramelessDelegateWin::CheckMonitorChanged() { if (nullptr == mainWidget_) { return; } auto hwnd = reinterpret_cast(mainWidget_->winId()); auto newMonitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONULL); if (newMonitor != monitor_) { monitor_ = newMonitor; auto ret = GetScreenNativeWorkRect(hwnd); if ( !!ret) workRect_ = ret; } } void FramelessDelegateWin::ShowSystemMenu(const QPoint& pos) { auto hwnd = (HWND)mainWidget_->winId(); HMENU menu = GetSystemMenu(hwnd, false); if (menu) { MENUITEMINFO mii; mii.cbSize = sizeof(MENUITEMINFO); mii.fMask = MIIM_STATE; mii.fType = 0; mii.fState = MF_ENABLED; SetMenuItemInfo(menu, SC_RESTORE, FALSE, &mii); SetMenuItemInfo(menu, SC_MAXIMIZE, FALSE, &mii); SetMenuItemInfo(menu, SC_MINIMIZE, FALSE, &mii); // update the options mii.fState = MF_DISABLED; if (mainWidget_->maximumSize() == mainWidget_->minimumSize() || mainWidget_->isMaximized()) SetMenuItemInfo(menu, SC_SIZE, FALSE, &mii); else SetMenuItemInfo(menu, SC_SIZE, TRUE, &mii); if (!mainWidget_->isMaximized()) SetMenuItemInfo(menu, SC_MOVE, TRUE, &mii); else SetMenuItemInfo(menu, SC_MOVE, FALSE, &mii); mii.fState = MF_GRAYED; WINDOWPLACEMENT wp; GetWindowPlacement(hwnd, &wp); switch (wp.showCmd) { case SW_SHOWMAXIMIZED: SetMenuItemInfo(menu, SC_MAXIMIZE, FALSE, &mii); SetMenuDefaultItem(menu, SC_CLOSE, FALSE); break; case SW_SHOWMINIMIZED: SetMenuItemInfo(menu, SC_MINIMIZE, FALSE, &mii); SetMenuDefaultItem(menu, SC_RESTORE, FALSE); break; case SW_SHOWNORMAL: { if (mainWidget_->maximumSize() == mainWidget_->minimumSize()) { SetMenuItemInfo(menu, SC_MAXIMIZE, FALSE, &mii); } if (!mainWidget_->windowFlags().testFlag(Qt::WindowMinimizeButtonHint)) { SetMenuItemInfo(menu, SC_MINIMIZE, FALSE, &mii); } SetMenuItemInfo(menu, SC_RESTORE, FALSE, &mii); SetMenuDefaultItem(menu, SC_CLOSE, FALSE); break; } } auto devicePixelRatio = qApp->devicePixelRatio(); auto globalPos = pos; globalPos.rx() *= devicePixelRatio; globalPos.ry() *= devicePixelRatio; BOOL cmd = TrackPopupMenuEx(menu, TPM_LEFTALIGN | TPM_TOPALIGN | TPM_RETURNCMD, globalPos.x(), globalPos.y(), hwnd, nullptr); if (cmd) PostMessage(hwnd, WM_SYSCOMMAND, cmd, 0); } } void FramelessDelegateWin::SetNativeWindowLong() { if (nullptr == mainWidget_) { return; } HWND hwnd = reinterpret_cast(mainWidget_->winId()); unsigned long style = BorderlessFlag; if (!mainWidget_->windowFlags().testFlag(Qt::WindowMinimizeButtonHint) || mainWidget_->maximumSize() != QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX)) { style &= ~WS_MINIMIZEBOX; } if (!mainWidget_->windowFlags().testFlag(Qt::WindowMaximizeButtonHint)) { style &= ~WS_MAXIMIZEBOX; } ::SetWindowLongPtr(hwnd, GWL_STYLE, style); if (IsCompositionEnabled()) { ExtendFrameIntoClientArea(mainWidget_->windowHandle(), 1, 0, 0, 0); } RECT rect; GetWindowRect(hwnd, &rect); SetWindowPos( hwnd, nullptr, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE); } #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) bool FramelessDelegateWin::OnNCTitTest(MSG* msg, long* result) { #else bool FramelessDelegateWin::OnNCTitTest(MSG* msg, qintptr* result) { #endif #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) auto [x, y] = QCursor::pos(); #else auto p = QCursor::pos(); auto x = p.x(); auto y = p.y(); #endif auto borderX = GetSystemMetrics(SM_CXPADDEDBORDER); auto borderY = GetSystemMetrics(SM_CXPADDEDBORDER); bool maxSized = mainWidget_->isMaximized() || mainWidget_->isFullScreen(); if (maxSized) { borderX = 0; borderY = 0; } if (mainWidget_->minimumSize() != mainWidget_->maximumSize()) { auto rect = mainWidget_->geometry(); if (!maxSized && x >= rect.left() && x <= rect.left() + borderX) { if (y >= rect.top() && y <= rect.top() + borderY) { *result = HTTOPLEFT; return true; } if (y > rect.top() + borderY && y < rect.bottom() - borderY) { *result = HTLEFT; return true; } if (y >= rect.bottom() - borderY && y <= rect.bottom()) { *result = HTBOTTOMLEFT; return true; } } else if (x > rect.left() + borderX && x < rect.right() - borderX) { if (!maxSized && y >= rect.top() && y <= rect.top() + borderY) { *result = HTTOP; return true; } if (y > rect.top() + borderY && y < rect.top() + borderY) { *result = HTCAPTION; return true; } if (!maxSized && y >= rect.bottom() - borderY && y <= rect.bottom()) { *result = HTBOTTOM; return true; } } else if (!maxSized && x >= rect.right() - borderX && x <= rect.right()) { if (y >= rect.top() && y <= rect.top() + borderY) { *result = HTTOPRIGHT; return true; } if (y > rect.top() + borderY && y < rect.bottom() - borderY) { *result = HTRIGHT; return true; } if (y >= rect.bottom() - borderY && y <= rect.bottom()) { *result = HTBOTTOMRIGHT; return true; } } else if (!maxSized) { *result = HTNOWHERE; return true; } } auto localPos = mainWidget_->mapFromGlobal(QPoint(x, y)); for (auto w : moveBars_) { auto child = mainWidget_->childAt(localPos); if (!child) continue; if (child == w) { auto pos = w->mapFrom(mainWidget_, localPos); auto title = qobject_cast(w); /*if (title) { if (title->iconIsVisible() && title->doIconRect().contains(pos)) { *result = HTSYSMENU; return true; } }*/ *result = HTCAPTION; return true; } else { auto it = std::find_if(w->children().begin(), w->children().end(), [&](const auto& obj) { if (obj->isWidgetType()) { return obj == child; } return false; }); if (it != w->children().end() && w->metaObject()->className() != QString("QWidget") && w->metaObject()->className() != QString("QLabel")) { // if ((*it)->property("qcustomui_maximumSizeButton").isValid()) //{ // *result = HTMAXBUTTON; // return false; // } //*result = HTCLIENT; //return false; } } } return false; }