Transient QWindows centered; default-constructed geometry

Default-constructed geometry does not mean put the window at 0,0,
and it does not mean center the window on the screen: it means
let the window manager position the window.  If the window is
explicitly positioned at 0,0 though, that is a higher priority
than the transient hint; without this change, the transientFor property
had no effect.  On X11, transient means use center "gravity" to
make the transient window exactly centered.  But the user can still
override the geometry of a transient window, as with any window.
On OSX and Windows, neither transient window functionality nor smart
initial positioning are provided, so a window with no position set
will be centered on the screen, and a transient window will be put
at the center of its transientParent.

Change-Id: I4f5e37480eef5d105e45ffd60362a57f13ec55f5
Task-number: QTBUG-26903
Reviewed-by: Gunnar Sletta <gunnar.sletta@digia.com>
This commit is contained in:
Shawn Rutledge 2012-12-10 14:48:19 +01:00 committed by The Qt Project
parent d9ff510f02
commit 153d613353
12 changed files with 135 additions and 40 deletions

View File

@ -1559,6 +1559,8 @@ void QGuiApplicationPrivate::processActivatedEvent(QWindowSystemInterfacePrivate
}
QGuiApplicationPrivate::focus_window = newFocus;
if (!qApp)
return;
if (previous) {
QFocusEvent focusOut(QEvent::FocusOut);

View File

@ -41,11 +41,13 @@
#include "qplatformwindow.h"
#include "qplatformwindow_p.h"
#include "qplatformscreen.h"
#include <private/qguiapplication_p.h>
#include <qpa/qwindowsysteminterface.h>
#include <QtGui/qwindow.h>
#include <QtGui/qscreen.h>
#include <private/qwindow_p.h>
QT_BEGIN_NAMESPACE
@ -448,6 +450,43 @@ QString QPlatformWindow::formatWindowTitle(const QString &title, const QString &
return fullTitle;
}
/*!
Helper function to get initial geometry on windowing systems which do not
do smart positioning and also do not provide a means of centering a
transient window w.r.t. its parent. For example this is useful on Windows
and MacOS but not X11, because an X11 window manager typically tries to
layout new windows to optimize usage of the available desktop space.
However if the given window already has geometry which the application has
initialized, it takes priority.
*/
QRect QPlatformWindow::initialGeometry(const QWindow *w,
const QRect &initialGeometry, int defaultWidth, int defaultHeight)
{
QRect rect(initialGeometry);
if (rect.isNull()) {
QSize minimumSize = w->minimumSize();
if (minimumSize.width() > 0 || minimumSize.height() > 0) {
rect.setSize(minimumSize);
} else {
rect.setWidth(defaultWidth);
rect.setHeight(defaultHeight);
}
}
if (w->isTopLevel() && qt_window_private(const_cast<QWindow*>(w))->positionAutomatic) {
const QWindow *tp = w->transientParent();
if (tp) {
// A transient window should be centered w.r.t. its transient parent.
rect.moveCenter(tp->geometry().center());
} else {
// Center the window on the screen. (Only applicable on platforms
// which do not provide a better way.)
QPlatformScreen *scr = QPlatformScreen::platformScreenForWindow(w);
rect.moveCenter(scr->availableGeometry().center());
}
}
return rect;
}
/*!
\class QPlatformWindow
\since 4.8

View File

@ -130,6 +130,9 @@ public:
virtual void setFrameStrutEventsEnabled(bool enabled);
virtual bool frameStrutEventsEnabled() const;
static QRect initialGeometry(const QWindow *w,
const QRect &initialGeometry, int defaultWidth, int defaultHeight);
protected:
static QString formatWindowTitle(const QString &title, const QString &separator);

View File

@ -126,6 +126,18 @@ QT_BEGIN_NAMESPACE
and can keep rendering until it isExposed() returns false. To find out when
isExposed() changes, reimplement exposeEvent(). The window will always get
a resize event before the first expose event.
\section1 Initial geometry
If the window's width and height are left uninitialized, the window will
get a reasonable default geometry from the platform window. If the position
is left uninitialized, then the platform window will allow the windowing
system to position the window. For example on X11, the window manager
usually does some kind of smart positioning to try to avoid having new
windows completely obscure existing windows. However setGeometry()
initializes both the position and the size, so if you want a fixed size but
an automatic position, you should call resize() or setWidth() and
setHeight() instead.
*/
/*!
@ -962,7 +974,7 @@ void QWindow::setY(int arg)
void QWindow::setWidth(int arg)
{
if (width() != arg)
setGeometry(QRect(x(), y(), arg, height()));
resize(arg, height());
}
/*!
@ -972,7 +984,7 @@ void QWindow::setWidth(int arg)
void QWindow::setHeight(int arg)
{
if (height() != arg)
setGeometry(QRect(x(), y(), width(), arg));
resize(width(), arg);
}
/*!
@ -1095,6 +1107,7 @@ void QWindow::setGeometry(int posx, int posy, int w, int h)
void QWindow::setGeometry(const QRect &rect)
{
Q_D(QWindow);
d->positionAutomatic = false;
if (rect == geometry())
return;
QRect oldRect = geometry();

View File

@ -88,6 +88,7 @@ public:
, resizeEventPending(true)
, receivedExpose(false)
, positionPolicy(WindowFrameExclusive)
, positionAutomatic(true)
, contentOrientation(Qt::PrimaryOrientation)
, opacity(qreal(1.0))
, minimumSize(0, 0)
@ -137,6 +138,7 @@ public:
bool resizeEventPending;
bool receivedExpose;
PositionPolicy positionPolicy;
bool positionAutomatic;
Qt::ScreenOrientation contentOrientation;
qreal opacity;

View File

@ -58,6 +58,11 @@
#include <QDebug>
enum {
defaultWindowWidth = 160,
defaultWindowHeight = 160
};
static bool isMouseEvent(NSEvent *ev)
{
switch ([ev type]) {
@ -683,7 +688,8 @@ NSWindow * QCocoaWindow::createNSWindow()
{
QCocoaAutoReleasePool pool;
NSRect frame = qt_mac_flipRect(window()->geometry(), window());
QRect rect = initialGeometry(window(), window()->geometry(), defaultWindowWidth, defaultWindowHeight);
NSRect frame = qt_mac_flipRect(rect, window());
Qt::WindowType type = window()->type();
Qt::WindowFlags flags = window()->flags();

View File

@ -64,6 +64,11 @@
QT_BEGIN_NAMESPACE
enum {
defaultWindowWidth = 160,
defaultWindowHeight = 160
};
Q_GUI_EXPORT HICON qt_pixmapToWinHICON(const QPixmap &);
static QByteArray debugWinStyle(DWORD style)
@ -468,6 +473,8 @@ QWindowsWindow::WindowData
const QString windowClassName = QWindowsContext::instance()->registerWindowClass(w, isGL);
QRect rect = QPlatformWindow::initialGeometry(w, geometry, defaultWindowWidth, defaultWindowHeight);
if (title.isEmpty() && (result.flags & Qt::WindowTitleHint))
title = topLevel ? qAppName() : w->objectName();
@ -475,14 +482,14 @@ QWindowsWindow::WindowData
const wchar_t *classNameUtf16 = reinterpret_cast<const wchar_t *>(windowClassName.utf16());
// Capture events before CreateWindowEx() returns.
const QWindowCreationContextPtr context(new QWindowCreationContext(w, geometry, style, exStyle));
const QWindowCreationContextPtr context(new QWindowCreationContext(w, rect, style, exStyle));
QWindowsContext::instance()->setWindowCreationContext(context);
if (QWindowsContext::verboseWindows)
qDebug().nospace()
<< "CreateWindowEx: " << w << *this
<< " class=" <<windowClassName << " title=" << title
<< "\nrequested: " << geometry << ": "
<< "\nrequested: " << rect << ": "
<< context->frameWidth << 'x' << context->frameHeight
<< '+' << context->frameX << '+' << context->frameY;
@ -775,6 +782,7 @@ QWindowsWindow::QWindowsWindow(QWindow *aWindow, const WindowData &data) :
QWindowsWindow::~QWindowsWindow()
{
#ifndef Q_OS_WINCE
QWindowSystemInterface::flushWindowSystemEvents();
if (QWindowsContext::instance()->systemInfo() & QWindowsContext::SI_SupportsTouch)
QWindowsContext::user32dll.unregisterTouchWindow(m_data.hwnd);
#endif // !Q_OS_WINCE

View File

@ -113,6 +113,10 @@
#endif
#define XCOORD_MAX 16383
enum {
defaultWindowWidth = 160,
defaultWindowHeight = 160
};
//#ifdef NET_WM_STATE_DEBUG
@ -219,8 +223,16 @@ void QXcbWindow::create()
QRect rect = window()->geometry();
QPlatformWindow::setGeometry(rect);
rect.setWidth(qBound(1, rect.width(), XCOORD_MAX));
rect.setHeight(qBound(1, rect.height(), XCOORD_MAX));
QSize minimumSize = window()->minimumSize();
if (rect.width() > 0 || rect.height() > 0) {
rect.setWidth(qBound(1, rect.width(), XCOORD_MAX));
rect.setHeight(qBound(1, rect.height(), XCOORD_MAX));
} else if (minimumSize.width() > 0 || minimumSize.height() > 0) {
rect.setSize(minimumSize);
} else {
rect.setWidth(defaultWindowWidth);
rect.setHeight(defaultWindowHeight);
}
xcb_window_t xcb_parent_id = m_screen->root();
if (parent())
@ -436,15 +448,23 @@ void QXcbWindow::setGeometry(const QRect &rect)
propagateSizeHints();
const QRect wmGeometry = windowToWmGeometry(rect);
const quint32 mask = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT;
const qint32 values[] = {
qBound<qint32>(-XCOORD_MAX, wmGeometry.x(), XCOORD_MAX),
qBound<qint32>(-XCOORD_MAX, wmGeometry.y(), XCOORD_MAX),
qBound<qint32>(1, wmGeometry.width(), XCOORD_MAX),
qBound<qint32>(1, wmGeometry.height(), XCOORD_MAX),
};
Q_XCB_CALL(xcb_configure_window(xcb_connection(), m_window, mask, reinterpret_cast<const quint32*>(values)));
if (qt_window_private(window())->positionAutomatic) {
const quint32 mask = XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT;
const qint32 values[] = {
qBound<qint32>(1, wmGeometry.width(), XCOORD_MAX),
qBound<qint32>(1, wmGeometry.height(), XCOORD_MAX),
};
Q_XCB_CALL(xcb_configure_window(xcb_connection(), m_window, mask, reinterpret_cast<const quint32*>(values)));
} else {
const quint32 mask = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT;
const qint32 values[] = {
qBound<qint32>(-XCOORD_MAX, wmGeometry.x(), XCOORD_MAX),
qBound<qint32>(-XCOORD_MAX, wmGeometry.y(), XCOORD_MAX),
qBound<qint32>(1, wmGeometry.width(), XCOORD_MAX),
qBound<qint32>(1, wmGeometry.height(), XCOORD_MAX),
};
Q_XCB_CALL(xcb_configure_window(xcb_connection(), m_window, mask, reinterpret_cast<const quint32*>(values)));
}
xcb_flush(xcb_connection());
}
@ -563,6 +583,7 @@ void QXcbWindow::show()
if (!transientXcbParent)
transientXcbParent = static_cast<QXcbScreen *>(screen())->clientLeader();
if (transientXcbParent) { // ICCCM 4.1.2.6
m_gravity = XCB_GRAVITY_CENTER;
Q_XCB_CALL(xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, m_window,
XCB_ATOM_WM_TRANSIENT_FOR, XCB_ATOM_WINDOW, 32,
1, &transientXcbParent));
@ -1221,8 +1242,10 @@ void QXcbWindow::propagateSizeHints()
QWindow *win = window();
xcb_size_hints_set_position(&hints, true, rect.x(), rect.y());
xcb_size_hints_set_size(&hints, true, rect.width(), rect.height());
if (!qt_window_private(win)->positionAutomatic)
xcb_size_hints_set_position(&hints, true, rect.x(), rect.y());
if (rect.width() < QWINDOWSIZE_MAX || rect.height() < QWINDOWSIZE_MAX)
xcb_size_hints_set_size(&hints, true, rect.width(), rect.height());
xcb_size_hints_set_win_gravity(&hints, m_gravity);
QSize minimumSize = win->minimumSize();

View File

@ -109,7 +109,10 @@ void QWidgetPrivate::create_sys(WId window, bool initializeWindow, bool destroyO
win->setFlags(data.window_flags);
fixPosIncludesFrame();
win->setGeometry(q->geometry());
if (q->testAttribute(Qt::WA_Moved))
win->setGeometry(q->geometry());
else
win->resize(q->size());
win->setScreen(QGuiApplication::screens().value(topData()->screenIndex, 0));
if (q->testAttribute(Qt::WA_TranslucentBackground)) {
@ -466,19 +469,6 @@ void QWidget::activateWindow()
wnd->requestActivate();
}
// Position top level windows at the center, avoid showing
// Windows at the default 0,0 position excluding the frame.
static inline QRect positionTopLevelWindow(QRect geometry, const QScreen *screen)
{
if (screen && geometry.x() == 0 && geometry.y() == 0) {
const QRect availableGeometry = screen->availableGeometry();
if (availableGeometry.width() > geometry.width()
&& availableGeometry.height() > geometry.height())
geometry.moveCenter(availableGeometry.center());
}
return geometry;
}
// move() was invoked with Qt::WA_WState_Created not set (frame geometry
// unknown), that is, crect has a position including the frame.
// If we can determine the frame strut, fix that and clear the flag.
@ -529,16 +519,16 @@ void QWidgetPrivate::show_sys()
if (q->isWindow())
fixPosIncludesFrame();
QRect geomRect = q->geometry();
if (q->isWindow()) {
if (!q->testAttribute(Qt::WA_Moved))
geomRect = positionTopLevelWindow(geomRect, window->screen());
} else {
if (!q->isWindow()) {
QPoint topLeftOfWindow = q->mapTo(q->nativeParentWidget(),QPoint());
geomRect.moveTopLeft(topLeftOfWindow);
}
const QRect windowRect = window->geometry();
if (windowRect != geomRect) {
window->setGeometry(geomRect);
if (q->testAttribute(Qt::WA_Moved))
window->setGeometry(geomRect);
else
window->resize(geomRect.size());
}
if (QBackingStore *store = q->backingStore()) {

View File

@ -78,7 +78,6 @@ private slots:
void shareRegister();
void textureCleanup();
#endif
void graphicsViewClipping();
void partialGLWidgetUpdates_data();
void partialGLWidgetUpdates();
void glWidgetWithAlpha();
@ -98,6 +97,7 @@ private slots:
void destroyFBOAfterContext();
void threadImages();
void nullRectCrash();
void graphicsViewClipping();
};
tst_QGL::tst_QGL()
@ -865,7 +865,11 @@ void tst_QGL::graphicsViewClipping()
scene.setSceneRect(view.viewport()->rect());
QVERIFY(QTest::qWaitForWindowActive(&view));
QVERIFY(QTest::qWaitForWindowExposed(&view));
#ifdef Q_OS_MAC
// The black rectangle jumps from the center to the upper left for some reason.
QTest::qWait(100);
#endif
QImage image = viewport->grabFrameBuffer();
QImage expected = image;

View File

@ -5430,7 +5430,7 @@ void tst_QWidget::setToolTip()
QScopedPointer<QWidget> popup(new QWidget(0, Qt::Popup));
popup->setObjectName(QString::fromLatin1("tst_qwidget setToolTip #%1").arg(pass));
popup->setWindowTitle(popup->objectName());
popup->resize(150, 50);
popup->setGeometry(50, 50, 150, 50);
QFrame *frame = new QFrame(popup.data());
frame->setGeometry(0, 0, 50, 50);
frame->setFrameStyle(QFrame::Box | QFrame::Plain);

View File

@ -3902,6 +3902,11 @@ void tst_QLineEdit::inputMethod()
// removing focus allows input method to commit preedit
testWidget->setText("");
testWidget->activateWindow();
// TODO setFocus should not be necessary here, because activateWindow
// should focus it, and the window is the QLineEdit. But the test can fail
// on Windows if we don't do this. If each test had a unique QLineEdit
// instance, maybe such problems would go away.
testWidget->setFocus();
QTRY_VERIFY(testWidget->hasFocus());
QTRY_COMPARE(qApp->focusObject(), testWidget);