#include "WindowsFramelessHelper.h" #include #include #include #include #include #include #include #include #include "../titlebar/TitleBar.h" QScopedPointer NativeWindowFilter::s_instance; QHash NativeWindowFilter::s_windows; QHash NativeWindowFilter::s_winIds; QHash NativeWindowFilter::s_helpers; void NativeWindowFilter::deliver(QWindow *window, WindowsFramelessHelper *helper) { Q_CHECK_PTR(window); if (!s_instance) { QCoreApplication *appc = QCoreApplication::instance(); if (appc) { auto filter = new NativeWindowFilter(); appc->installNativeEventFilter(filter); } } if (helper) { WId newId = window->winId(); WId oldId = s_windows.value(helper); if (newId != oldId) { s_helpers.insert(newId, helper); s_helpers.remove(oldId); s_windows.insert(helper, newId); s_winIds.insert(window, newId); } } else { WId oldId = s_winIds.take(window); WindowsFramelessHelper *helper = s_helpers.take(oldId); s_windows.remove(helper); } } bool NativeWindowFilter::nativeEventFilter(const QByteArray &eventType, void *message, long *result) { LPMSG msg = reinterpret_cast(message); if (auto h = s_helpers.value(reinterpret_cast(msg->hwnd))) return h->nativeEventFilter(eventType, msg, result); return false; } WindowsFramelessHelper::WindowsFramelessHelper(QWidget *widget) : QObject(widget) { Q_CHECK_PTR(widget); m_widget = widget; if (QOperatingSystemVersion::current().majorVersion() != QOperatingSystemVersion::Windows7.majorVersion()) { m_widget->setWindowFlags(m_widget->windowFlags() | Qt::FramelessWindowHint); } else if (m_widget->parentWidget()) { m_widget->setWindowFlags(m_widget->parentWidget()->windowFlags() | Qt::FramelessWindowHint | Qt::WindowMinMaxButtonsHint); } else { m_widget->setWindowFlags(Qt::FramelessWindowHint | Qt::WindowMinMaxButtonsHint); } m_widget->installEventFilter(this); m_titleBar = new TitleBar(m_widget); m_widget->resize(500, 500); m_titleBar->raise(); m_titleBarHeight = m_titleBar->height(); } WindowsFramelessHelper::~WindowsFramelessHelper() { if (m_window) NativeWindowFilter::deliver(m_window, nullptr); } void WindowsFramelessHelper::setMaximizedMargins(int left, int top, int right, int bottom) { m_maximizedMargins = QMargins(left, top, right, bottom); } void WindowsFramelessHelper::setMaximizedMargins(const QMargins &margins) { m_maximizedMargins = margins; } void WindowsFramelessHelper::setDraggableMargins(int left, int top, int right, int bottom) { m_draggableMargins = QMargins(left, top, right, bottom); } void WindowsFramelessHelper::setDraggableMargins(const QMargins &margins) { m_draggableMargins = margins; } void WindowsFramelessHelper::setTitleBar(TitleBar *titleBar) { if (titleBar != m_titleBar) { m_titleBar->deleteLater(); m_titleBar = titleBar; m_titleBar->setParent(m_widget); m_titleBar->raise(); m_titleBarHeight = m_titleBar->height(); } } void WindowsFramelessHelper::setResizeEnabled(bool isEnabled) { m_isResizeEnabled = isEnabled; } bool WindowsFramelessHelper::getResizeEnabled() const { return m_isResizeEnabled; } bool WindowsFramelessHelper::nativeEventFilter(const QByteArray &eventType, void *msg, long *result) { Q_CHECK_PTR(m_window); LPMSG lpMsg = reinterpret_cast(msg); WPARAM wParam = lpMsg->wParam; LPARAM lParam = lpMsg->lParam; if (WM_NCHITTEST == lpMsg->message) { *result = hitTest(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); return true; } else if (WM_NCACTIVATE == lpMsg->message) { if (!QtWin::isCompositionEnabled()) { if (result) *result = 1; return true; } } else if (WM_NCCALCSIZE == lpMsg->message) { if (TRUE == wParam) { if (isMaximized()) { NCCALCSIZE_PARAMS ¶ms = *reinterpret_cast(lParam); QRect g = availableGeometry(); QMargins m = maximizedMargins(); params.rgrc[0].top = g.top() - m.top(); params.rgrc[0].left = g.left() - m.left(); params.rgrc[0].right = g.right() + m.right() + 1; params.rgrc[0].bottom = g.bottom() + m.bottom() + 1; } if (result) *result = 0; return true; } } else if (WM_GETMINMAXINFO == lpMsg->message) { LPMINMAXINFO lpMinMaxInfo = reinterpret_cast(lParam); QRect g = availableGeometry(); QMargins m = maximizedMargins(); lpMinMaxInfo->ptMaxPosition.x = -m.left(); lpMinMaxInfo->ptMaxPosition.y = -m.top(); lpMinMaxInfo->ptMaxSize.x = g.right() - g.left() + 1 + m.left() + m.right(); lpMinMaxInfo->ptMaxSize.y = g.bottom() - g.top() + 1 + m.top() + m.bottom(); lpMinMaxInfo->ptMinTrackSize.x = m_window->minimumWidth(); lpMinMaxInfo->ptMinTrackSize.y = m_window->minimumHeight(); lpMinMaxInfo->ptMaxTrackSize.x = m_window->maximumWidth(); lpMinMaxInfo->ptMaxTrackSize.y = m_window->maximumHeight(); if (result) *result = 0; return true; } else if (WM_NCLBUTTONDBLCLK == lpMsg->message) { auto minimumSize = m_window->minimumSize(); auto maximumSize = m_window->maximumSize(); if ((minimumSize.width() >= maximumSize.width()) || (minimumSize.height() >= maximumSize.height())) { if (result) *result = 0; return true; } } else if (WM_MOVE == lpMsg->message) { RECT rcClient; GetWindowRect(lpMsg->hwnd, &rcClient); SetWindowPos(lpMsg->hwnd, NULL, rcClient.left, rcClient.top, rcClient.right - rcClient.left, rcClient.bottom - rcClient.top, SWP_FRAMECHANGED); } #ifdef Q_CC_MSVC else if (WM_DPICHANGED == lpMsg->message) { qreal old = m_scaleFactor; if (HIWORD(wParam) < 144) { m_scaleFactor = 1.0; } else { m_scaleFactor = 2.0; } if (!qFuzzyCompare(old, m_scaleFactor)) { emit scaleFactorChanged(m_scaleFactor); } auto hWnd = reinterpret_cast(m_window->winId()); auto rect = reinterpret_cast(lParam); SetWindowPos(hWnd, NULL, rect->left, rect->top, rect->right - rect->left, rect->bottom - rect->top, SWP_NOZORDER | SWP_NOACTIVATE); } #endif return false; } bool WindowsFramelessHelper::eventFilter(QObject *obj, QEvent *ev) { if (obj == m_widget && ev->type() == QEvent::WinIdChange) { initWindow(); } else if (obj == m_widget && ev->type() == QEvent::Resize) { m_titleBar->resize(m_widget->width(), m_titleBar->height()); m_titleBar->raise(); } if (m_window == obj && ev->type() == QEvent::WinIdChange) { updateWindowStyle(); } else if (m_window == obj && ev->type() == QEvent::Show) { updateWindowStyle(); } return QObject::eventFilter(obj, ev); } qreal WindowsFramelessHelper::scaleFactor() const { return m_scaleFactor; } void WindowsFramelessHelper::initWindow() { QWindow *window = m_widget->windowHandle(); Q_CHECK_PTR(window); m_window = window; if (m_window) { m_scaleFactor = m_window->screen()->devicePixelRatio(); if (m_window->flags() & Qt::FramelessWindowHint) { m_window->installEventFilter(this); updateWindowStyle(); } } } void WindowsFramelessHelper::updateWindowStyle() { Q_CHECK_PTR(m_window); HWND hWnd = reinterpret_cast(m_window->winId()); if (NULL == hWnd) return; else if (m_oldWindow == hWnd) { return; } m_oldWindow = hWnd; NativeWindowFilter::deliver(m_window, this); QOperatingSystemVersion currentVersion = QOperatingSystemVersion::current(); if (currentVersion < QOperatingSystemVersion::Windows8) { return; } LONG oldStyle = WS_OVERLAPPEDWINDOW | WS_THICKFRAME | WS_CAPTION | WS_SYSMENU | WS_MAXIMIZEBOX | WS_MINIMIZEBOX; ; LONG newStyle = WS_POPUP | WS_THICKFRAME; if (QtWin::isCompositionEnabled()) newStyle |= WS_CAPTION; if (m_window->flags() & Qt::CustomizeWindowHint) { if (m_window->flags() & Qt::WindowSystemMenuHint) newStyle |= WS_SYSMENU; if (m_window->flags() & Qt::WindowMinimizeButtonHint) newStyle |= WS_MINIMIZEBOX; if (m_window->flags() & Qt::WindowMaximizeButtonHint) newStyle |= WS_MAXIMIZEBOX; } else { newStyle |= WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX; } LONG currentStyle = GetWindowLong(hWnd, GWL_STYLE); SetWindowLong(hWnd, GWL_STYLE, (currentStyle & ~oldStyle) | newStyle); SetWindowPos(hWnd, NULL, 0, 0, 0, 0, SWP_NOOWNERZORDER | SWP_NOZORDER | SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE); if (QtWin::isCompositionEnabled()) QtWin::extendFrameIntoClientArea(m_window, 1, 1, 1, 1); } bool WindowsFramelessHelper::isMaximized() const { Q_CHECK_PTR(m_window); HWND hWnd = reinterpret_cast(m_window->winId()); if (NULL == hWnd) return false; WINDOWPLACEMENT windowPlacement; if (!GetWindowPlacement(hWnd, &windowPlacement)) return false; return (SW_MAXIMIZE == windowPlacement.showCmd); } QRect WindowsFramelessHelper::availableGeometry() const { MONITORINFO mi { 0 }; mi.cbSize = sizeof(MONITORINFO); auto hWnd = reinterpret_cast(m_window->winId()); auto hMonitor = MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST); if (!hMonitor || !GetMonitorInfoW(hMonitor, &mi)) { Q_ASSERT(NULL != hMonitor); return m_window->screen()->availableGeometry(); } return QRect(mi.rcWork.left, mi.rcWork.top, mi.rcWork.right - mi.rcWork.left, mi.rcWork.bottom - mi.rcWork.top); } int WindowsFramelessHelper::hitTest(int x, int y) const { Q_CHECK_PTR(m_window); x = x / m_window->devicePixelRatio(); y = y / m_window->devicePixelRatio(); enum RegionMask { Client = 0x0000, Top = 0x0001, Left = 0x0010, Right = 0x0100, Bottom = 0x1000, }; auto wfg = m_window->frameGeometry(); QMargins draggableMargins = this->draggableMargins(); int top = draggableMargins.top(); int left = draggableMargins.left(); int right = draggableMargins.right(); int bottom = draggableMargins.bottom(); if (top <= 0) top = GetSystemMetrics(SM_CYFRAME); if (left <= 0) left = GetSystemMetrics(SM_CXFRAME); if (right <= 0) right = GetSystemMetrics(SM_CXFRAME); if (bottom <= 0) bottom = GetSystemMetrics(SM_CYFRAME); auto result = (Top * (y < (wfg.top() + top))) | (Left * (x < (wfg.left() + left))) | (Right * (x > (wfg.right() - right))) | (Bottom * (y > (wfg.bottom() - bottom))); bool wResizable = m_isResizeEnabled ? m_window->minimumWidth() < m_window->maximumWidth() : false; bool hResizable = m_isResizeEnabled ? m_window->minimumHeight() < m_window->maximumHeight() : false; switch (result) { case Top | Left: return wResizable && hResizable ? HTTOPLEFT : HTCLIENT; case Top: return hResizable ? HTTOP : HTCLIENT; case Top | Right: return wResizable && hResizable ? HTTOPRIGHT : HTCLIENT; case Right: return wResizable ? HTRIGHT : HTCLIENT; case Bottom | Right: return wResizable && hResizable ? HTBOTTOMRIGHT : HTCLIENT; case Bottom: return hResizable ? HTBOTTOM : HTCLIENT; case Bottom | Left: return wResizable && hResizable ? HTBOTTOMLEFT : HTCLIENT; case Left: return wResizable ? HTLEFT : HTCLIENT; } auto pos = m_window->mapFromGlobal(QPoint(x, y)); return HTCLIENT; }