From f48aa008e992429647968a29ef30d412f1ae379a Mon Sep 17 00:00:00 2001 From: Friedemann Kleint Date: Wed, 28 Mar 2018 11:22:09 +0200 Subject: Windows QPA: Fix RTL window title bars MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a platform option to the plugin (-platform windows:reverse) that enables reverse mode. It sets WS_EX_LAYOUTRTL on RTL windows, forces normal orientation on all HDCs created for the window, fixes ClientToScreen()/ScreenToClient() accordingly and transforms mouse events. [ChangeLog][Platform Specific Changes][Windows] It is now possible to enable RTL mode by passing the option -platform windows:reverse. Fixes: QTBUG-28463 Change-Id: I4d70818b2fd315d4e8d5627eab11ae912c6e77be Reviewed-by: Miikka Heikkinen Reviewed-by: AndrĂ© de la Rocha --- src/plugins/platforms/windows/qwindowscontext.cpp | 12 ++-- .../platforms/windows/qwindowsintegration.cpp | 2 + .../platforms/windows/qwindowsintegration.h | 3 +- .../platforms/windows/qwindowsmousehandler.cpp | 10 +++- .../platforms/windows/qwindowspointerhandler.cpp | 8 ++- src/plugins/platforms/windows/qwindowswindow.cpp | 70 +++++++++++++++++++--- src/plugins/platforms/windows/qwindowswindow.h | 28 ++++++++- 7 files changed, 110 insertions(+), 23 deletions(-) (limited to 'src/plugins/platforms/windows') diff --git a/src/plugins/platforms/windows/qwindowscontext.cpp b/src/plugins/platforms/windows/qwindowscontext.cpp index 320b5b1a41..f93d196b20 100644 --- a/src/plugins/platforms/windows/qwindowscontext.cpp +++ b/src/plugins/platforms/windows/qwindowscontext.cpp @@ -713,7 +713,7 @@ static inline bool findPlatformWindowHelper(const POINT &screenPoint, unsigned c HWND *hwnd, QWindowsWindow **result) { POINT point = screenPoint; - ScreenToClient(*hwnd, &point); + screenToClient(*hwnd, &point); // Returns parent if inside & none matched. const HWND child = ChildWindowFromPointEx(*hwnd, point, cwexFlags); if (!child || child == *hwnd) @@ -1043,7 +1043,7 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message, // For non-client-area messages, these are screen coordinates (as expected // in the MSG structure), otherwise they are client coordinates. if (!(et & QtWindows::NonClientEventFlag)) { - ClientToScreen(msg.hwnd, &msg.pt); + clientToScreen(msg.hwnd, &msg.pt); } } else { GetCursorPos(&msg.pt); @@ -1134,13 +1134,11 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message, case QtWindows::QuerySizeHints: d->m_creationContext->applyToMinMaxInfo(reinterpret_cast(lParam)); return true; - case QtWindows::ResizeEvent: { - const QSize size(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) - d->m_creationContext->menuHeight); - d->m_creationContext->obtainedGeometry.setSize(size); - } + case QtWindows::ResizeEvent: + d->m_creationContext->obtainedSize = QSize(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); return true; case QtWindows::MoveEvent: - d->m_creationContext->obtainedGeometry.moveTo(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); + d->m_creationContext->obtainedPos = QPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); return true; case QtWindows::NonClientCreate: if (shouldHaveNonClientDpiScaling(d->m_creationContext->window)) diff --git a/src/plugins/platforms/windows/qwindowsintegration.cpp b/src/plugins/platforms/windows/qwindowsintegration.cpp index 4e319c2326..8dd3810463 100644 --- a/src/plugins/platforms/windows/qwindowsintegration.cpp +++ b/src/plugins/platforms/windows/qwindowsintegration.cpp @@ -217,6 +217,8 @@ static inline unsigned parseOptions(const QStringList ¶mList, options |= QWindowsIntegration::NoNativeMenus; } else if (param == QLatin1String("nowmpointer")) { options |= QWindowsIntegration::DontUseWMPointer; + } else if (param == QLatin1String("reverse")) { + options |= QWindowsIntegration::RtlEnabled; } else { qWarning() << "Unknown option" << param; } diff --git a/src/plugins/platforms/windows/qwindowsintegration.h b/src/plugins/platforms/windows/qwindowsintegration.h index e28b2c2fb3..015cf79b6c 100644 --- a/src/plugins/platforms/windows/qwindowsintegration.h +++ b/src/plugins/platforms/windows/qwindowsintegration.h @@ -69,7 +69,8 @@ public: AlwaysUseNativeMenus = 0x100, NoNativeMenus = 0x200, DontUseWMPointer = 0x400, - DetectAltGrModifier = 0x800 + DetectAltGrModifier = 0x800, + RtlEnabled = 0x1000 }; explicit QWindowsIntegration(const QStringList ¶mList); diff --git a/src/plugins/platforms/windows/qwindowsmousehandler.cpp b/src/plugins/platforms/windows/qwindowsmousehandler.cpp index c4d53855a5..5a52cd3ea8 100644 --- a/src/plugins/platforms/windows/qwindowsmousehandler.cpp +++ b/src/plugins/platforms/windows/qwindowsmousehandler.cpp @@ -106,7 +106,7 @@ static inline void compressMouseMove(MSG *msg) // Extract the x,y coordinates from the lParam as we do in the WndProc msg->pt.x = GET_X_LPARAM(mouseMsg.lParam); msg->pt.y = GET_Y_LPARAM(mouseMsg.lParam); - ClientToScreen(msg->hwnd, &(msg->pt)); + clientToScreen(msg->hwnd, &(msg->pt)); // Remove the mouse move message PeekMessage(&mouseMsg, msg->hwnd, WM_MOUSEMOVE, WM_MOUSEMOVE, PM_REMOVE); @@ -262,7 +262,13 @@ bool QWindowsMouseHandler::translateMouseEvent(QWindow *window, HWND hwnd, if (et == QtWindows::MouseWheelEvent) return translateMouseWheelEvent(window, hwnd, msg, result); - const QPoint winEventPosition(GET_X_LPARAM(msg.lParam), GET_Y_LPARAM(msg.lParam)); + QPoint winEventPosition(GET_X_LPARAM(msg.lParam), GET_Y_LPARAM(msg.lParam)); + if ((et & QtWindows::NonClientEventFlag) == 0 && QWindowsBaseWindow::isRtlLayout(hwnd)) { + RECT clientArea; + GetClientRect(hwnd, &clientArea); + winEventPosition.setX(clientArea.right - winEventPosition.x()); + } + QPoint clientPosition; QPoint globalPosition; if (et & QtWindows::NonClientEventFlag) { diff --git a/src/plugins/platforms/windows/qwindowspointerhandler.cpp b/src/plugins/platforms/windows/qwindowspointerhandler.cpp index b730dbcf3b..0e686bfb10 100644 --- a/src/plugins/platforms/windows/qwindowspointerhandler.cpp +++ b/src/plugins/platforms/windows/qwindowspointerhandler.cpp @@ -688,7 +688,13 @@ bool QWindowsPointerHandler::translateMouseEvent(QWindow *window, { *result = 0; - const QPoint eventPos(GET_X_LPARAM(msg.lParam), GET_Y_LPARAM(msg.lParam)); + QPoint eventPos(GET_X_LPARAM(msg.lParam), GET_Y_LPARAM(msg.lParam)); + if ((et & QtWindows::NonClientEventFlag) == 0 && QWindowsBaseWindow::isRtlLayout(hwnd)) { + RECT clientArea; + GetClientRect(hwnd, &clientArea); + eventPos.setX(clientArea.right - eventPos.x()); + } + QPoint localPos; QPoint globalPos; diff --git a/src/plugins/platforms/windows/qwindowswindow.cpp b/src/plugins/platforms/windows/qwindowswindow.cpp index d8273194fa..e700e6cff4 100644 --- a/src/plugins/platforms/windows/qwindowswindow.cpp +++ b/src/plugins/platforms/windows/qwindowswindow.cpp @@ -134,6 +134,10 @@ static QByteArray debugWinExStyle(DWORD exStyle) rc += " WS_EX_LAYERED"; if (exStyle & WS_EX_DLGMODALFRAME) rc += " WS_EX_DLGMODALFRAME"; + if (exStyle & WS_EX_LAYOUTRTL) + rc += " WS_EX_LAYOUTRTL"; + if (exStyle & WS_EX_NOINHERITLAYOUT) + rc += " WS_EX_NOINHERITLAYOUT"; return rc; } @@ -307,7 +311,7 @@ static inline QRect frameGeometry(HWND hwnd, bool topLevel) const int width = rect.right - rect.left; const int height = rect.bottom - rect.top; POINT leftTop = { rect.left, rect.top }; - ScreenToClient(parent, &leftTop); + screenToClient(parent, &leftTop); rect.left = leftTop.x; rect.top = leftTop.y; rect.right = leftTop.x + width; @@ -667,6 +671,17 @@ void WindowCreationData::fromWindow(const QWindow *w, const Qt::WindowFlags flag if ((flags & Qt::MSWindowsFixedSizeDialogHint)) dialog = true; + // This causes the title bar to drawn RTL and the close button + // to be left. Note that this causes: + // - All DCs created on the Window to have RTL layout (see SetLayout) + // - ClientToScreen() and ScreenToClient() to work in reverse as well. + // - Mouse event coordinates to be mirrored. + // - Positioning of child Windows. + if (QGuiApplication::layoutDirection() == Qt::RightToLeft + && (QWindowsIntegration::instance()->options() & QWindowsIntegration::RtlEnabled) != 0) { + exStyle |= WS_EX_LAYOUTRTL | WS_EX_NOINHERITLAYOUT; + } + // Parent: Use transient parent for top levels. if (popup) { flags |= Qt::WindowStaysOnTopHint; // a popup stays on top, no parent. @@ -772,6 +787,16 @@ QWindowsWindowData QPoint pos = calcPosition(w, context, invMargins); + // Mirror the position when creating on a parent in RTL mode, ditto for the obtained geometry. + int mirrorParentWidth = 0; + if (!w->isTopLevel() && QWindowsBaseWindow::isRtlLayout(parentHandle)) { + RECT rect; + GetClientRect(parentHandle, &rect); + mirrorParentWidth = rect.right; + } + if (mirrorParentWidth != 0 && pos.x() != CW_USEDEFAULT && context->frameWidth != CW_USEDEFAULT) + pos.setX(mirrorParentWidth - context->frameWidth - pos.x()); + result.hwnd = CreateWindowEx(exStyle, classNameUtf16, titleUtf16, style, pos.x(), pos.y(), @@ -779,14 +804,21 @@ QWindowsWindowData parentHandle, nullptr, appinst, nullptr); qCDebug(lcQpaWindows).nospace() << "CreateWindowEx: returns " << w << ' ' << result.hwnd << " obtained geometry: " - << context->obtainedGeometry << ' ' << context->margins; + << context->obtainedPos << context->obtainedSize << ' ' << context->margins; if (!result.hwnd) { qErrnoWarning("%s: CreateWindowEx failed", __FUNCTION__); return result; } - result.geometry = context->obtainedGeometry; + if (mirrorParentWidth != 0) { + context->obtainedPos.setX(mirrorParentWidth - context->obtainedSize.width() + - context->obtainedPos.x()); + } + + QRect obtainedGeometry(context->obtainedPos, context->obtainedSize); + + result.geometry = obtainedGeometry; result.fullFrameMargins = context->margins; result.embedded = embedded; result.hasFrame = hasFrame; @@ -1031,6 +1063,11 @@ bool QWindowsGeometryHint::positionIncludesFrame(const QWindow *w) \ingroup qt-lighthouse-win */ +bool QWindowsBaseWindow::isRtlLayout(HWND hwnd) +{ + return (GetWindowLongPtrW(hwnd, GWL_EXSTYLE) & WS_EX_LAYOUTRTL) != 0; +} + QWindowsBaseWindow *QWindowsBaseWindow::baseWindowOf(const QWindow *w) { if (w) { @@ -1193,7 +1230,9 @@ QWindowCreationContext::QWindowCreationContext(const QWindow *w, DWORD style, DWORD exStyle) : window(w), requestedGeometryIn(geometryIn), - requestedGeometry(geometry), obtainedGeometry(geometry), + requestedGeometry(geometry), + obtainedPos(geometryIn.topLeft()), + obtainedSize(geometryIn.size()), margins(QWindowsGeometryHint::frame(w, geometry, style, exStyle)), customMargins(cm) { @@ -1321,11 +1360,12 @@ void QWindowsWindow::initialize() // will send the message) and screen change signals of QWindow. if (w->type() != Qt::Desktop) { const Qt::WindowState state = w->windowState(); + const QRect obtainedGeometry(creationContext->obtainedPos, creationContext->obtainedSize); if (state != Qt::WindowMaximized && state != Qt::WindowFullScreen - && creationContext->requestedGeometryIn != creationContext->obtainedGeometry) { - QWindowSystemInterface::handleGeometryChange(w, creationContext->obtainedGeometry); + && creationContext->requestedGeometryIn != obtainedGeometry) { + QWindowSystemInterface::handleGeometryChange(w, obtainedGeometry); } - QPlatformScreen *obtainedScreen = screenForGeometry(creationContext->obtainedGeometry); + QPlatformScreen *obtainedScreen = screenForGeometry(obtainedGeometry); if (obtainedScreen && screen() != obtainedScreen) QWindowSystemInterface::handleWindowScreenChanged(w, obtainedScreen->screen()); } @@ -1942,7 +1982,16 @@ void QWindowsBaseWindow::setGeometry_sys(const QRect &rect) const windowPlacement.showCmd = windowPlacement.showCmd == SW_SHOWMINIMIZED ? SW_SHOWMINIMIZED : SW_HIDE; result = SetWindowPlacement(hwnd, &windowPlacement); } else { - result = MoveWindow(hwnd, frameGeometry.x(), frameGeometry.y(), + int x = frameGeometry.x(); + if (!window()->isTopLevel()) { + const HWND parentHandle = GetParent(hwnd); + if (isRtlLayout(parentHandle)) { + RECT rect; + GetClientRect(parentHandle, &rect); + x = rect.right - frameGeometry.width() - x; + } + } + result = MoveWindow(hwnd, x, frameGeometry.y(), frameGeometry.width(), frameGeometry.height(), true); } qCDebug(lcQpaWindows) << '<' << __FUNCTION__ << window() @@ -1958,8 +2007,11 @@ void QWindowsBaseWindow::setGeometry_sys(const QRect &rect) const HDC QWindowsWindow::getDC() { - if (!m_hdc) + if (!m_hdc) { m_hdc = GetDC(handle()); + if (QGuiApplication::layoutDirection() == Qt::RightToLeft) + SetLayout(m_hdc, 0); // Clear RTL layout + } return m_hdc; } diff --git a/src/plugins/platforms/windows/qwindowswindow.h b/src/plugins/platforms/windows/qwindowswindow.h index 958564aa86..1abe1e3531 100644 --- a/src/plugins/platforms/windows/qwindowswindow.h +++ b/src/plugins/platforms/windows/qwindowswindow.h @@ -89,7 +89,8 @@ struct QWindowCreationContext const QWindow *window; QRect requestedGeometryIn; // QWindow scaled QRect requestedGeometry; // after QPlatformWindow::initialGeometry() - QRect obtainedGeometry; + QPoint obtainedPos; + QSize obtainedSize; QMargins margins; QMargins customMargins; // User-defined, additional frame for WM_NCCALCSIZE int frameX = CW_USEDEFAULT; // Passed on to CreateWindowEx(), including frame. @@ -134,6 +135,7 @@ public: unsigned style() const { return GetWindowLongPtr(handle(), GWL_STYLE); } unsigned exStyle() const { return GetWindowLongPtr(handle(), GWL_EXSTYLE); } + static bool isRtlLayout(HWND hwnd); static QWindowsBaseWindow *baseWindowOf(const QWindow *w); static HWND handleOf(const QWindow *w); @@ -399,18 +401,38 @@ QDebug operator<<(QDebug d, const WINDOWPOS &); QDebug operator<<(QDebug d, const GUID &guid); #endif // !QT_NO_DEBUG_STREAM +static inline void clientToScreen(HWND hwnd, POINT *wP) +{ + if (QWindowsBaseWindow::isRtlLayout(hwnd)) { + RECT clientArea; + GetClientRect(hwnd, &clientArea); + wP->x = clientArea.right - wP->x; + } + ClientToScreen(hwnd, wP); +} + +static inline void screenToClient(HWND hwnd, POINT *wP) +{ + ScreenToClient(hwnd, wP); + if (QWindowsBaseWindow::isRtlLayout(hwnd)) { + RECT clientArea; + GetClientRect(hwnd, &clientArea); + wP->x = clientArea.right - wP->x; + } +} + // ---------- QWindowsGeometryHint inline functions. QPoint QWindowsGeometryHint::mapToGlobal(HWND hwnd, const QPoint &qp) { POINT p = { qp.x(), qp.y() }; - ClientToScreen(hwnd, &p); + clientToScreen(hwnd, &p); return QPoint(p.x, p.y); } QPoint QWindowsGeometryHint::mapFromGlobal(const HWND hwnd, const QPoint &qp) { POINT p = { qp.x(), qp.y() }; - ScreenToClient(hwnd, &p); + screenToClient(hwnd, &p); return QPoint(p.x, p.y); } -- cgit v1.2.3