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:
parent
26a0db4b44
commit
f48aa008e9
@ -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))
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -69,7 +69,8 @@ public:
|
||||
AlwaysUseNativeMenus = 0x100,
|
||||
NoNativeMenus = 0x200,
|
||||
DontUseWMPointer = 0x400,
|
||||
DetectAltGrModifier = 0x800
|
||||
DetectAltGrModifier = 0x800,
|
||||
RtlEnabled = 0x1000
|
||||
};
|
||||
|
||||
explicit QWindowsIntegration(const QStringList ¶mList);
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user