Positioning multiple windows in Qt

Submitted by mimec on 2013-05-28

It's been a while since the last post, but I've been quite busy with WebIssues. Now that the first beta of version 1.1 is released, it's time to catch up with other things. Actually I started writing this post some time ago, but then my HDD crashed, so I had to do it once again.

QWidget provides the saveGeometry/restoreGeometry pair of methods for a convenient way of storing and restoring the position of a window. This works fine when our application has just one top level window. But what if the application can open multiple windows of a given type? Consider a chat or email application, where each conversation or message can be opened in a separate window. It should be possible to store the position and size of the windows, but we also want to make sure that when the user opens multiple windows, they are displayed at slightly different positions, so that they don't cover one another entirely.

Doing this manually is not an easy task. Besides handling the maximized state of the window, restoreGeometry also ensures that the window is not displayed off screen when a display device is disconnected or its resolution has changed since the geometry was saved. Instead of implementing a custom mechanism, we can take advantage of saveGeometry/restoreGeometry, with a slight modification which makes multiple windows behave correctly.

The idea is that in addition to the geometry (which is serialized as a QByteArray) we also store a boolean flag called "offset". Whenever a new window is opened, we remember its position and set the offset to true, meaning that when another window is opened, an offset should be added to the stored position. When a window is closed, we remember its final position and set the offset to false. Another window will be then opened at the exact position of the old one. One way of doing this is handling the show and hide events:

void MyWindow::showEvent( QShowEvent* e )
{
    if ( !e->spontaneous() )
        storeGeometry( true );
}

void MyWindow::hideEvent( QHideEvent* e )
{
    if ( !e->spontaneous() )
        storeGeometry( false );
}

void MyWindow::storeGeometry( bool offset )
{
    QSettings settings;
    settings.setValue( "MyWindowGeometry", saveGeometry() );
    settings.setValue( "MyWindowOffset", offset );
}

Note that we ignore spontaneous events, which are fired when the window is minimized or restored by the user. All that's left is restoring the window's geometry. This can be done, for example, in the constructor of the window's class:

QSettings settings;
restoreGeometry( settings.value( "MyWindowGeometry" ).toByteArray() );
if ( settings.value( "MyWindowOffset" ).toBool() ) {
    QPoint position = pos() + QPoint( 40, 40 );
    QRect available = QApplication::desktop()->availableGeometry( this );
    QRect frame = frameGeometry();
    if ( position.x() + frame.width() > available.right() )
        position.rx() = available.left();
    if ( position.y() + frame.height() > available.bottom() - 20 )
        position.ry() = available.top();
    move( position );
}

We add an arbitrary offset of 40 pixels to the window's X and Y position. Then we ensure that the window doesn't span outside the right and bottom edge of the screen and otherwise move it to the left or top edge, respectively.

Note that this is not a perfect solution. If the user opens and closes the windows in certain order, they will overlap (I leave it as an excercise to the reader to come up with such scenario). But it nicely handles the two most common scenarios: reopening a closed window in the same location and ensuring that multiple windows, opened one after another, have slightly different positions.