Windows QPA: Fix RTL window title bars

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 <miikka.heikkinen@qt.io>
Reviewed-by: André de la Rocha <andre.rocha@qt.io>
This commit is contained in:
Friedemann Kleint 2018-03-28 11:22:09 +02:00
parent 26a0db4b44
commit f48aa008e9
7 changed files with 110 additions and 23 deletions

View File

@ -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<MINMAXINFO *>(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))

View File

@ -217,6 +217,8 @@ static inline unsigned parseOptions(const QStringList &paramList,
options |= QWindowsIntegration::NoNativeMenus;
} else if (param == QLatin1String("nowmpointer")) {
options |= QWindowsIntegration::DontUseWMPointer;
} else if (param == QLatin1String("reverse")) {
options |= QWindowsIntegration::RtlEnabled;
} else {
qWarning() << "Unknown option" << param;
}

View File

@ -69,7 +69,8 @@ public:
AlwaysUseNativeMenus = 0x100,
NoNativeMenus = 0x200,
DontUseWMPointer = 0x400,
DetectAltGrModifier = 0x800
DetectAltGrModifier = 0x800,
RtlEnabled = 0x1000
};
explicit QWindowsIntegration(const QStringList &paramList);

View File

@ -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) {

View File

@ -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;

View File

@ -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<QWindowSystemInterface::SynchronousDelivery>(w, creationContext->obtainedGeometry);
&& creationContext->requestedGeometryIn != obtainedGeometry) {
QWindowSystemInterface::handleGeometryChange<QWindowSystemInterface::SynchronousDelivery>(w, obtainedGeometry);
}
QPlatformScreen *obtainedScreen = screenForGeometry(creationContext->obtainedGeometry);
QPlatformScreen *obtainedScreen = screenForGeometry(obtainedGeometry);
if (obtainedScreen && screen() != obtainedScreen)
QWindowSystemInterface::handleWindowScreenChanged<QWindowSystemInterface::SynchronousDelivery>(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;
}

View File

@ -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);
}