Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Saving window state with QML

Is there a nice way to record the window state of a QtQuick application? The documentation gives the following method:

Settings {
    property alias x: mainWindow.x
    property alias y: mainWindow.y
    property alias width: mainWindow.width
    property alias height: mainWindow.height

However that has three flaws:

  1. It writes to the settings file continuously when you resize / move the window.
  2. It doesn't remember whether the window is maximised or not (Notepad++ also suffers from this annoying flaw btw).
  3. If you maximise the window it doesn't save the unmaximised geometry.

Does anyone have any better code?

like image 890
Timmmm Avatar asked Jul 15 '15 09:07

Timmmm


1 Answers

I managed to make something which works pretty well, as far as my limited testing shows. I did have to do one hacky bit which is because unfortunately Window.visibility is updated after Window.x/y/width/height, which means that if you are trying to record the windowed geometry, you can't just check the state of Window.visibility in onXChanged. Instead I had to record the two previous values and then discard the most recent one if the window is maximised.

Edit: This doesn't quite work perfectly. If you maximise the window, then close the app. Then open it then close it again. Then open it once more, when you unmaximise it won't go back to the correct windowed size. I think fixing that would be ugly enough QML that I'll probably implement this in C++ where it really belongs.

import QtQuick 2.3
import QtQuick.Window 2.2
import QtQuick.Controls 1.3
import Qt.labs.settings 1.0

Item {
    property Window window

    // Default properties for the application's first run.
    property int defaultX: 100
    property int defaultY: 100
    property int defaultWidth: 500
    property int defaultHeight: 500
    property bool defaultMaximised: false

    Settings {
        id: windowStateSettings
        category: "WindowState"
        property int x
        property int y
        property int width
        property int height
        property bool maximised
    }

    Component.onCompleted: {
        if (windowStateSettings.width === 0 || windowStateSettings.height === 0)
        {
            // First run, or width/height are screwed up.
            curX = defaultX;
            curY = defaultY;
            curWidth = defaultWidth;
            curHeight = defaultHeight;
            curMaximised = defaultMaximised
        }
        else
        {
            curX = windowStateSettings.x;
            curY = windowStateSettings.y;
            curWidth = windowStateSettings.width;
            curHeight = windowStateSettings.height;
            curMaximised = windowStateSettings.maximised
        }
        window.x = prevX = curX;
        window.y = prevY = curY;
        window.width = prevWidth = curWidth;
        window.height = prevHeight = curHeight;

        if (curMaximised)
            window.visibility = Window.Maximized;
    }

    // Remember the windowed geometry, and whether it is maximised or not.
    // Internal use only.
    property int curX
    property int curY
    property int curWidth
    property int curHeight
    property bool curMaximised

    // We also have to save the previous values of X/Y/Width/Height so they can be restored if we maximise, since we
    // can't tell that the updated X,Y values are because of maximisation until *after* the maximisation.
    property int prevX
    property int prevY
    property int prevWidth
    property int prevHeight

    Connections {
        target: window
        onVisibilityChanged: {
            if (window.visibility === Window.Maximized)
            {
                curMaximised = true;
                // Ignore the latest X/Y/width/height values.
                curX = prevX;
                curY = prevY;
                curWidth = prevWidth;
                curHeight = prevHeight;
            }
            else if (window.visibility === Window.Windowed)
            {
                curMaximised = false;
            }
            else if (window.visibility === Window.Hidden)
            {
                // Save settings.
                windowStateSettings.x = curX;
                windowStateSettings.y = curY;
                windowStateSettings.width = curWidth;
                windowStateSettings.height = curHeight;
                windowStateSettings.maximised = curMaximised;
            }
        }

        // We can't use window.visibility here to ignore the maximised geometry because it changes after the geometry.
        // Instead we cache the two previous values and revert them if maximised.
        onXChanged: {
            prevX = curX;
            curX = window.x;
        }
        onYChanged: {
            prevY = curY;
            curY = window.y;
        }
        onWidthChanged: {
            prevWidth = curWidth;
            curWidth = window.width;
        }
        onHeightChanged: {
            prevHeight = curHeight;
            curHeight = window.height;
        }
    }

}

Use it like this:

ApplicationWindow {
    id: mainWindow

    WindowStateSaver {
        window: mainWindow
        defaultWidth: 1000 // Or whatever. You can also specify the defaultX/Y if you want.
        defaultHeight: 700
    }
like image 65
Timmmm Avatar answered Nov 17 '22 06:11

Timmmm