From ac508832f2d23e6eeb62b0c5b2da83b7ddbb72bf Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tinja=20Paavosepp=C3=A4?= <tinja.paavoseppa@qt.io>
Date: Thu, 17 Mar 2022 09:08:50 +0200
Subject: [PATCH] Add GUI for configuring build. Still largely WIP.

Wizard UI for configuring the build.
TODO: icons for checkbox, create data containers for stuff like component
dependencies, error handling...
---
 CMakeLists.txt                  |  10 +-
 DirSelectionItem.qml            |  29 ++++++
 Footer.qml                      |  75 ++++++++++++++
 Header.qml                      |  31 ++++++
 PageBuilding.qml                |  28 ++++++
 PageModules.qml                 | 100 +++++++++++++++++++
 PageStart.qml                   | 168 ++++++++++++++++++++++++++++++++
 PageSummary.qml                 | 132 +++++++++++++++++++++++++
 buildparams.cpp                 | 109 +++++++++++++++++++++
 buildparams.h                   |  57 +++++++++++
 icons/qt_logo_green_64x64px.png | Bin 0 -> 2318 bytes
 imports/QtStyle/Button.qml      |  36 +++++++
 imports/QtStyle/CheckBox.qml    |  43 ++++++++
 imports/QtStyle/Label.qml       |  11 +++
 imports/QtStyle/ProgressBar.qml |  42 ++++++++
 imports/QtStyle/TextField.qml   |  30 ++++++
 imports/QtStyle/qmldir          |   7 ++
 imports/Theme/Theme.qml         |  16 +++
 imports/Theme/qmldir            |   2 +
 main.cpp                        |   1 +
 main.qml                        | 107 +++++++++++++++++++-
 qml.qrc                         |  22 +++++
 qtquickcontrols2.conf           |   6 ++
 23 files changed, 1056 insertions(+), 6 deletions(-)
 create mode 100644 DirSelectionItem.qml
 create mode 100644 Footer.qml
 create mode 100644 Header.qml
 create mode 100644 PageBuilding.qml
 create mode 100644 PageModules.qml
 create mode 100644 PageStart.qml
 create mode 100644 PageSummary.qml
 create mode 100644 buildparams.cpp
 create mode 100644 buildparams.h
 create mode 100644 icons/qt_logo_green_64x64px.png
 create mode 100644 imports/QtStyle/Button.qml
 create mode 100644 imports/QtStyle/CheckBox.qml
 create mode 100644 imports/QtStyle/Label.qml
 create mode 100644 imports/QtStyle/ProgressBar.qml
 create mode 100644 imports/QtStyle/TextField.qml
 create mode 100644 imports/QtStyle/qmldir
 create mode 100644 imports/Theme/Theme.qml
 create mode 100644 imports/Theme/qmldir
 create mode 100644 qml.qrc
 create mode 100644 qtquickcontrols2.conf

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 1ebe4a3..00df331 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -3,6 +3,7 @@ cmake_minimum_required(VERSION 3.16)
 project(UnixsocketPlayground2 VERSION 0.1 LANGUAGES CXX)
 
 set(CMAKE_AUTOMOC ON)
+set(CMAKE_AUTORCC ON)
 set(CMAKE_CXX_STANDARD_REQUIRED ON)
 
 find_package(Qt6 6.2 COMPONENTS Quick REQUIRED)
@@ -11,15 +12,20 @@ qt_add_executable(appUnixsocketPlayground2
     main.cpp
     qunixsockettotcpserver.h qunixsockettotcpserver.cpp
     qunixsockettotcpbridge.h qunixsockettotcpbridge.cpp
-    qhttprequest.h qhttprequest.cpp
+    qhttprequest.h qhttprequest.cpp buildparams.h buildparams.cpp
+    qml.qrc
 )
 
 qt_add_qml_module(appUnixsocketPlayground2
     URI UnixsocketPlayground2
     VERSION 1.0
-    QML_FILES main.qml 
+    QML_FILES main.qml DirSelectionItem.qml
+    PageStart.qml PageModules.qml Header.qml
+    PageBuilding.qml Footer.qml PageSummary.qml
 )
 
+set(QML_IMPORT_PATH ${CMAKE_SOURCE_DIR}/imports CACHE STRING "" FORCE)
+
 set_target_properties(appUnixsocketPlayground2 PROPERTIES
     MACOSX_BUNDLE_GUI_IDENTIFIER my.example.com
     MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
diff --git a/DirSelectionItem.qml b/DirSelectionItem.qml
new file mode 100644
index 0000000..3193ba1
--- /dev/null
+++ b/DirSelectionItem.qml
@@ -0,0 +1,29 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+
+RowLayout {
+    id: root
+
+    property alias text: label.text
+    property alias input: textField.text
+
+    signal buttonClicked()
+
+    spacing: 8
+
+    Label {
+        id: label
+        Layout.fillWidth: true
+    }
+
+    TextField {
+        id: textField
+    }
+
+    Button {
+        id: button
+        text: qsTr("Browse")
+        onClicked: root.buttonClicked()
+    }
+}
diff --git a/Footer.qml b/Footer.qml
new file mode 100644
index 0000000..30b1811
--- /dev/null
+++ b/Footer.qml
@@ -0,0 +1,75 @@
+import QtQuick
+import QtQuick.Controls
+import Theme
+
+Item {
+    id: root
+
+    property int currentIndex
+    property int pageCount
+
+    signal back()
+    signal next()
+    signal quit()
+    signal build()
+
+    Rectangle {
+        id: bottomDivider
+        anchors.left: parent.left
+        anchors.top: parent.top
+        anchors.right: parent.right
+        color: Theme.colorLight
+        height: 1
+    }
+
+    Row {
+        id: rowButtons
+        anchors.right: parent.right
+        anchors.bottom: parent.bottom
+        padding: 12
+        spacing: 8
+
+        Button {
+            id: buttonBack
+            text: qsTr("Back")
+            enabled: currentIndex > 0 && currentIndex < pageCount - 1
+            onClicked: back()
+        }
+
+        Button {
+            id: buttonNext
+            focus: true
+            enabled: currentIndex < pageCount - 1
+            onClicked: {
+                if (currentIndex === pageCount - 2) {
+                    build()
+                    buttonQuit.focus = true
+                }
+                next()
+            }
+
+            states: [
+                State {
+                    when: currentIndex < pageCount - 2
+                    PropertyChanges {
+                        target: buttonNext
+                        text: qsTr("Next")
+                    }
+                },
+                State {
+                    when: currentIndex >= pageCount - 2
+                    PropertyChanges {
+                        target: buttonNext
+                        text: qsTr("Build")
+                    }
+                }
+            ]
+        }
+
+        Button {
+            id: buttonQuit
+            text: qsTr("Quit")
+            onClicked: quit()
+        }
+    }
+}
diff --git a/Header.qml b/Header.qml
new file mode 100644
index 0000000..144e3a0
--- /dev/null
+++ b/Header.qml
@@ -0,0 +1,31 @@
+import QtQuick
+import QtQuick.Controls
+
+Item {
+    id: header
+    height: qtIcon.height + 24
+
+    Label {
+        text: "Let's build Qt!"
+        anchors.left: parent.left
+        anchors.verticalCenter: parent.verticalCenter
+        anchors.leftMargin: 12
+    }
+
+    Image {
+        id: qtIcon
+        source: "qrc:/icons/qt_logo_green_64x64px.png"
+        anchors.right: parent.right
+        anchors.verticalCenter: parent.verticalCenter
+        anchors.rightMargin: 12
+    }
+
+    Rectangle {
+        id: divider
+        anchors.bottom: parent.bottom
+        anchors.left: parent.left
+        anchors.right: parent.right
+        color: "#f3f3f4"
+        height: 1
+    }
+}
diff --git a/PageBuilding.qml b/PageBuilding.qml
new file mode 100644
index 0000000..24ae49a
--- /dev/null
+++ b/PageBuilding.qml
@@ -0,0 +1,28 @@
+import QtQuick
+import QtQuick.Controls
+
+Item {
+    id: pageBuilding
+
+    Label {
+        id: title
+        text: "Building..."
+    }
+
+    Label {
+        // TODO show details, errors etc
+        id: details
+        text: "Everything going great!"
+        anchors.top: title.bottom
+        anchors.topMargin: 24
+    }
+
+    ProgressBar {
+        id: progressBar
+        indeterminate: true
+        width: 500
+        anchors.top: details.bottom
+        anchors.left: parent.left
+        anchors.topMargin: 24
+    }
+}
diff --git a/PageModules.qml b/PageModules.qml
new file mode 100644
index 0000000..09bdbee
--- /dev/null
+++ b/PageModules.qml
@@ -0,0 +1,100 @@
+import QtQuick
+import QtQuick.Controls
+import UnixsocketPlayground2
+
+Item {
+    id: root
+
+    property var moduleNames: ["qtbase", "qtdeclarative", "qtremoteobjects", "qt5compat", "qttools",
+        "qtimageformats", "qtquicktimeline", "qtshadertools", "qtmultimedia"]
+    property var requiredModules: [] // TODO add required submodules to component
+
+    property BuildParams buildParams: null
+
+    onBuildParamsChanged: {
+        if (buildParams)
+            setModules()
+
+    }
+
+    Label {
+        id: label
+        text: qsTr("Choose what modules you would like to include.")
+              + qsTr(" Some modules may be selected due to being required by add-ons selected in the previous page")
+        width: parent.width
+    }
+
+    ScrollView {
+        id: scrollView
+        anchors.left: parent.left
+        anchors.top: label.bottom
+        anchors.topMargin: 28
+        anchors.right: parent.horizontalCenter
+        height: 400
+        ScrollBar.vertical.policy: ScrollBar.AlwaysOn
+
+        Column {
+            id: moduleCheckBoxes
+            spacing: 8
+
+            Repeater {
+                id: repeater
+                CheckBox {
+                    text: modelData
+                    enabled: !isModuleRequired(modelData)
+                    checked: !enabled
+                }
+            }
+        }
+    }
+
+    Label {
+        id: labelBranch
+        text: qsTr("Choose the branch to checkout:")
+        anchors.left: scrollView.right
+        anchors.leftMargin: 12
+        anchors.top: label.bottom
+        anchors.topMargin: 28
+    }
+
+    TextField {
+        id: fieldBranch
+        anchors.left: scrollView.right
+        anchors.leftMargin: 12
+        anchors.top: labelBranch.bottom
+        anchors.topMargin: 8
+        anchors.right: parent.right
+        text: "dev"
+    }
+
+    CheckBox {
+        id: chbSubmodulesUseSameBranch
+        anchors.left: scrollView.right
+        anchors.leftMargin: 12
+        anchors.top: fieldBranch.bottom
+        anchors.topMargin: 24
+        text: "Use the same branch for submodules"
+    }
+
+    function isModuleRequired(moduleName) {
+        for (var i = 0; i < requiredModules.length; i++) {
+            if (requiredModules[i] === moduleName) {
+                return true
+            }
+
+        }
+        return false
+    }
+
+    function setModules() {
+        repeater.model = []
+        requiredModules = []
+        requiredModules.push("qtbase")
+        if (buildParams.isComponentSelected("Qt Android Automotive")) {
+            for (var i = 1; i < moduleNames.length - 1; i++) {
+                requiredModules.push(moduleNames[i])
+            }
+        }
+        repeater.model = moduleNames
+    }
+}
diff --git a/PageStart.qml b/PageStart.qml
new file mode 100644
index 0000000..6ac1932
--- /dev/null
+++ b/PageStart.qml
@@ -0,0 +1,168 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import Qt.labs.platform
+import UnixsocketPlayground2
+
+Item {
+    id: root
+    property url qtDir: qtDirSelection.input
+    property url sdkDir: sdkSelection.input
+    property url ndkDir: ndkSelection.input
+
+    property BuildParams buildParams: null
+
+    onBuildParamsChanged: console.log("boo " + buildParams)
+
+    Label {
+        id: label
+        text: qsTr("Choose what to build and where to install.")
+        anchors.left: parent.left
+        anchors.right: parent.right
+    }
+
+    DirSelectionItem {
+        id: qtDirSelection
+        text: qsTr("Qt installation folder:")
+        anchors.left: parent.left
+        anchors.top: label.bottom
+        anchors.right: parent.right
+        anchors.topMargin: 28
+
+        onButtonClicked: {
+            folderDialog.currentItem = this
+            folderDialog.open()
+        }
+    }
+
+    Column {
+        id: qtVariantsCheckboxes
+        anchors.left: parent.left
+        anchors.top: qtDirSelection.bottom
+        anchors.topMargin: 28
+        anchors.right: parent.horizontalCenter
+        anchors.rightMargin: 12
+        height: 8 * 32
+        spacing: 8
+
+        Label {
+            text: qsTr("Choose which components you would like to build and install.")
+            width: parent.width
+        }
+
+        ButtonGroup {
+            id: childGroup
+            exclusive: false
+            checkState: chbAndroid.checkState
+        }
+
+        CheckBox {
+            text: "GCC 64"
+            onCheckedChanged: componenSelectedChanged(text, checked)
+        }
+
+        CheckBox {
+            id: chbAndroid
+            text: "Android"
+            checkState: childGroup.checkState
+        }
+
+        CheckBox {
+            text: "arm64 v8a"
+            leftPadding: 8
+            visible: chbAndroid.checkState === Qt.Checked
+                     || chbAndroid.checkState === Qt.PartiallyChecked
+            ButtonGroup.group: childGroup
+            onCheckedChanged: componenSelectedChanged(text, checked)
+        }
+
+        CheckBox {
+            text: "arm7"
+            leftPadding: 8
+            visible: chbAndroid.checkState === Qt.Checked
+                     || chbAndroid.checkState === Qt.PartiallyChecked
+            ButtonGroup.group: childGroup
+            onCheckedChanged: componenSelectedChanged(text, checked)
+        }
+
+        CheckBox {
+            text: "x86"
+            leftPadding: 8
+            visible: chbAndroid.checkState === Qt.Checked
+                     || chbAndroid.checkState === Qt.PartiallyChecked
+            ButtonGroup.group: childGroup
+            onCheckedChanged: componenSelectedChanged(text, checked)
+        }
+
+        CheckBox {
+            text: "x86 64"
+            leftPadding: 8
+            visible: chbAndroid.checkState === Qt.Checked
+                     || chbAndroid.checkState === Qt.PartiallyChecked
+            ButtonGroup.group: childGroup
+            onCheckedChanged: componenSelectedChanged(text, checked)
+        }
+
+        CheckBox {
+            text: "Qt Android Automotive"
+            enabled: chbAndroid.checkState === Qt.Checked
+                     || chbAndroid.checkState === Qt.PartiallyChecked
+            onCheckedChanged: componenSelectedChanged(text, checked)
+        }
+    }
+
+    Label {
+        id: labelAndroidDirs
+        anchors.top: qtVariantsCheckboxes.bottom
+        anchors.topMargin: 32
+        text: qsTr("Choose the location of Android SDK and NDK root folders.")
+        enabled: chbAndroid.checkState !== Qt.Unchecked
+    }
+
+    DirSelectionItem {
+        id: sdkSelection
+        text: qsTr("Android SDK root folder:")
+        anchors.left: parent.left
+        anchors.top: labelAndroidDirs.bottom
+        anchors.right: parent.right
+        anchors.topMargin: 24
+        enabled: chbAndroid.checkState !== Qt.Unchecked
+
+        onButtonClicked: {
+            folderDialog.currentItem = this
+            folderDialog.open()
+        }
+    }
+
+    DirSelectionItem {
+        id: ndkSelection
+        text: qsTr("Android NDK root folder:")
+        anchors.left: parent.left
+        anchors.top: sdkSelection.bottom
+        anchors.right: parent.right
+        anchors.topMargin: 18
+        enabled: chbAndroid.checkState !== Qt.Unchecked
+
+        onButtonClicked: {
+            folderDialog.currentItem = this
+            folderDialog.open()
+        }
+    }
+
+    function componenSelectedChanged(componentName, selected) {
+        if (selected)
+            buildParams.addComponent(componentName)
+        else
+            buildParams.removeComponent(componentName)
+    }
+
+    FolderDialog {
+        id: folderDialog
+
+        property DirSelectionItem currentItem
+
+        onAccepted: {
+            currentItem.input = folder
+        }
+    }
+}
diff --git a/PageSummary.qml b/PageSummary.qml
new file mode 100644
index 0000000..0dadd69
--- /dev/null
+++ b/PageSummary.qml
@@ -0,0 +1,132 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import QtQuick.Dialogs
+import Theme
+import UnixsocketPlayground2
+
+Item {
+    id: pageSummary
+
+    property var variantsToBuild: ["Qt GCC", "Qt for Android", "Qt Android Automotive"]
+    property var modulesToBuild: ["my bow", "my sword", "and my axe!"]
+
+    property BuildParams buildParams: null
+
+    onBuildParamsChanged: {
+        if (buildParams) {
+            if (buildParams.isComponentSelected("Qt Android Automotive")) {
+                gitCredentials.enabled = true
+            }
+        }
+    }
+
+    Item {
+        id: gitCredentials
+        anchors.top: parent.top
+        anchors.left: parent.left
+        anchors.right: parent.right
+        height: 160
+        enabled: true // TODO if contains QTAA
+        visible: enabled // TODO a Loader
+
+        Label {
+            id: labelGitCredentials
+            anchors.left: parent.left
+            anchors.top: parent.top
+            anchors.right: parent.right
+            text: qsTr("You have chosen to build some components that have restricted access. ") +
+                  qsTr("Please enter your credentials for checking out the repositories. ") +
+                  qsTr("Make sure you have set up your account according to these instructions: ") +
+                  qsTr("<a href=\"https://wiki.qt.io/Setting_up_Gerrit\">Setting up Gerrit</a>")
+            linkColor: Theme.colorHighlight
+            onLinkActivated: (link)=> Qt.openUrlExternally(link)
+        }
+
+        RowLayout {
+            id: rowUsername
+            anchors.left: parent.left
+            anchors.top: labelGitCredentials.bottom
+            anchors.topMargin: 24
+            anchors.right: parent.right
+            anchors.rightMargin: 104
+
+            Label {
+                id: labelUsername
+                text: "Gerrit username:"
+                Layout.fillWidth: true
+            }
+
+            TextField {
+                id: username
+            }
+        }
+
+        DirSelectionItem {
+            id: gitKey
+            anchors.left: parent.left
+            anchors.top: rowUsername.bottom
+            anchors.topMargin: 18
+            anchors.right: parent.right
+            text: qsTr("Git private key location:")
+
+            onButtonClicked: {
+                fileDialog.open()
+            }
+        }
+    }
+
+    Label {
+        id: label
+        anchors.left: parent.left
+        anchors.top: gitCredentials.bottom
+        anchors.topMargin: 28
+        anchors.right: parent.horizontalCenter
+        text: "You will be building the following Qt components:"
+    }
+
+    Column {
+        anchors.left: parent.left
+        anchors.top: label.bottom
+        anchors.topMargin: 12
+        anchors.right: parent.horizontalCenter
+        anchors.bottom: parent.bottom
+
+        Repeater {
+            model: buildParams ? buildParams.selectedComponents : []
+            Label {
+                text: modelData
+            }
+        }
+    }
+
+    Label {
+        id: labelModules
+        anchors.left: parent.horizontalCenter
+        anchors.leftMargin: 24
+        anchors.top: gitCredentials.bottom
+        anchors.topMargin: 28
+        anchors.right: parent.right
+        text: "Submodules:"
+    }
+
+    ListView {
+        anchors.left: parent.horizontalCenter
+        anchors.leftMargin: 24
+        anchors.top: label.bottom
+        anchors.topMargin: 12
+        anchors.right: parent.right
+        anchors.bottom: parent.bottom
+        model: modulesToBuild
+
+        delegate: Label {
+            text: modelData
+        }
+    }
+
+    FileDialog {
+        id: fileDialog
+
+        onAccepted: gitKey.input = selectedFile
+    }
+}
diff --git a/buildparams.cpp b/buildparams.cpp
new file mode 100644
index 0000000..fef7b92
--- /dev/null
+++ b/buildparams.cpp
@@ -0,0 +1,109 @@
+#include "buildparams.h"
+
+BuildParams::BuildParams(QObject *parent)
+    : QObject{parent}
+{
+}
+
+void BuildParams::addComponent(const QString &component)
+{
+    if (!m_selectedComponents.contains(component)) {
+        m_selectedComponents.append(component);
+        emit selectedComponentsChanged();
+    }
+}
+
+void BuildParams::addSubmodule(const QString &submodule)
+{
+    if (!m_selectedSubmodules.contains(submodule)) {
+        m_selectedSubmodules.append(submodule);
+        emit selectedSubmodulesChanged();
+    }
+}
+
+void BuildParams::removeComponent(const QString &component)
+{
+    if (m_selectedComponents.removeOne(component))
+        emit selectedComponentsChanged();
+}
+
+void BuildParams::removeSubmodule(const QString &submodule)
+{
+    if (m_selectedSubmodules.removeOne(submodule))
+        emit selectedSubmodulesChanged();
+}
+
+bool BuildParams::isComponentSelected(const QString &component)
+{
+    return m_selectedComponents.contains(component);
+}
+
+bool BuildParams::isSubmoduleSelected(const QString &submodule)
+{
+    return m_selectedSubmodules.contains(submodule);
+}
+
+QUrl BuildParams::qtDir() const
+{
+    return m_qtDir;
+}
+
+QUrl BuildParams::sdkDir() const
+{
+    return m_sdkDir;
+}
+
+QUrl BuildParams::ndkDir() const
+{
+    return m_ndkDir;
+}
+
+QString BuildParams::userName() const
+{
+    return m_userName;
+}
+
+QUrl BuildParams::keyLocation() const
+{
+    return m_keyLocation;
+}
+
+void BuildParams::setQtDir(const QUrl &url)
+{
+    if (url != m_qtDir) {
+        m_qtDir = url;
+        emit qtDirChanged();
+    }
+}
+
+void BuildParams::setSdkDir(const QUrl &url)
+{
+    if (url != m_sdkDir) {
+        m_sdkDir = url;
+        emit sdkDirChanged();
+    }
+}
+
+void BuildParams::setNdkDir(const QUrl &url)
+{
+    if (url != m_ndkDir) {
+        m_ndkDir = url;
+        emit ndkDirChanged();
+    }
+}
+
+void BuildParams::setUserName(const QString &userName)
+{
+    if (userName != m_userName) {
+        m_userName = userName;
+        emit userNameChanged();
+    }
+}
+
+void BuildParams::setKeyLocation(const QUrl &url)
+{
+    if (url != m_keyLocation) {
+        m_keyLocation = url;
+        emit keyLocationChanged();
+    }
+}
diff --git a/buildparams.h b/buildparams.h
new file mode 100644
index 0000000..be5c6ac
--- /dev/null
+++ b/buildparams.h
@@ -0,0 +1,57 @@
+#ifndef BUILDPARAMS_H
+#define BUILDPARAMS_H
+
+#include <QObject>
+#include <QQmlEngine>
+
+class BuildParams : public QObject
+{
+    Q_OBJECT
+    Q_PROPERTY(QUrl qtDir READ qtDir WRITE setQtDir NOTIFY qtDirChanged)
+    Q_PROPERTY(QUrl sdkDir READ sdkDir WRITE setSdkDir NOTIFY sdkDirChanged)
+    Q_PROPERTY(QUrl ndkDir READ ndkDir WRITE setNdkDir NOTIFY ndkDirChanged)
+    Q_PROPERTY(QStringList selectedComponents MEMBER m_selectedComponents NOTIFY selectedComponentsChanged)
+    Q_PROPERTY(QStringList selectedSubmodules MEMBER m_selectedSubmodules NOTIFY selectedSubmodulesChanged)
+    Q_PROPERTY(QString userName READ userName WRITE setUserName NOTIFY userNameChanged)
+    Q_PROPERTY(QUrl keyLocation READ keyLocation WRITE setKeyLocation NOTIFY keyLocationChanged)
+    QML_ELEMENT
+
+public:
+    explicit BuildParams(QObject *parent = nullptr);
+    Q_INVOKABLE void addComponent(const QString &component);
+    Q_INVOKABLE void addSubmodule(const QString &submodule);
+    Q_INVOKABLE void removeComponent(const QString &component);
+    Q_INVOKABLE void removeSubmodule(const QString &submodule);
+    Q_INVOKABLE bool isComponentSelected(const QString &component);
+    Q_INVOKABLE bool isSubmoduleSelected(const QString &submodule);
+    QUrl qtDir() const;
+    QUrl sdkDir() const;
+    QUrl ndkDir() const;
+    QString userName() const;
+    QUrl keyLocation() const;
+    void setQtDir(const QUrl &url);
+    void setSdkDir(const QUrl &url);
+    void setNdkDir(const QUrl &url);
+    void setUserName(const QString &userName);
+    void setKeyLocation(const QUrl &url);
+
+private:
+    QUrl m_qtDir;
+    QUrl m_sdkDir;
+    QUrl m_ndkDir;
+    QStringList m_selectedComponents;
+    QStringList m_selectedSubmodules;
+    QString m_userName;
+    QUrl m_keyLocation;
+
+signals:
+    void qtDirChanged();
+    void sdkDirChanged();
+    void ndkDirChanged();
+    void selectedComponentsChanged();
+    void selectedSubmodulesChanged();
+    void userNameChanged();
+    void keyLocationChanged();
+};
+
+#endif // BUILDPARAMS_H
diff --git a/icons/qt_logo_green_64x64px.png b/icons/qt_logo_green_64x64px.png
new file mode 100644
index 0000000000000000000000000000000000000000..9cb2e01d3895ed7d1a81e91ed5393b474d041671
GIT binary patch
literal 2318
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

literal 0
HcmV?d00001

diff --git a/imports/QtStyle/Button.qml b/imports/QtStyle/Button.qml
new file mode 100644
index 0000000..c2db259
--- /dev/null
+++ b/imports/QtStyle/Button.qml
@@ -0,0 +1,36 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Templates as T
+import Theme
+
+T.Button {
+    id: button
+
+    implicitWidth: background.implicitWidth
+    implicitHeight: background.implicitHeight
+
+    Keys.onEnterPressed: clicked()
+    Keys.onReturnPressed: clicked()
+
+    contentItem: Text {
+            text: button.text
+            font: button.font
+            opacity: enabled ? 1.0 : 0.3
+            color: button.hovered && button.enabled ? Theme.colorDark
+                                                    : button.focus ? Theme.colorLight : Theme.colorHighlight
+            horizontalAlignment: Text.AlignHCenter
+            verticalAlignment: Text.AlignVCenter
+            elide: Text.ElideRight
+    }
+
+    background: Rectangle {
+        color: button.hovered && button.enabled ? Theme.colorHighlight : "transparent"
+        border.width: 1
+        border.color: button.hovered && button.enabled  ? Theme.colorHighlight
+                                                        : button.focus ? Theme.colorLight : Theme.colorHighlight
+        implicitWidth: 96
+        implicitHeight: 28
+        opacity: enabled ? 1.0 : 0.3
+        radius: 2
+    }
+}
diff --git a/imports/QtStyle/CheckBox.qml b/imports/QtStyle/CheckBox.qml
new file mode 100644
index 0000000..59a7929
--- /dev/null
+++ b/imports/QtStyle/CheckBox.qml
@@ -0,0 +1,43 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Templates as T
+import Theme
+
+T.CheckBox {
+    id: checkBox
+
+    font: Theme.font
+    implicitHeight: indicator.implicitHeight
+    implicitWidth: contentItem.implicitWidth + indicator.implicitWidth
+    spacing: 8
+
+    indicator: Rectangle {
+        implicitWidth: 20
+        implicitHeight: 20
+        x: checkBox.leftPadding
+        y: parent.height / 2 - height / 2
+        radius: 2
+        color: (checkBox.checkState === Qt.Checked || checkBox.checkState === Qt.PartiallyChecked)
+               ? (checkBox.enabled ? (checkBox.hovered ? Theme.colorAccent : Theme.colorHighlight) : Theme.colorHighlightDark)
+               : "transparent"
+        border.width: 2
+        border.color: (checkBox.checkState === Qt.Checked || checkBox.checkState === Qt.PartiallyChecked)
+                      ? (checkBox.enabled ? (checkBox.hovered ? Theme.colorAccent : Theme.colorHighlight) : Theme.colorHighlightDark)
+                      : Theme.colorHighlight
+
+        Image {
+            anchors.centerIn: parent
+            visible: checkBox.checkState !== Qt.Unchecked
+            // TODO add icon
+        }
+    }
+
+    contentItem: Text {
+        text: checkBox.text
+        font: checkBox.font
+        opacity: enabled ? 1.0 : 0.3
+        color: Theme.colorLight
+        verticalAlignment: Text.AlignVCenter
+        leftPadding: checkBox.indicator.width + checkBox.spacing
+    }
+}
diff --git a/imports/QtStyle/Label.qml b/imports/QtStyle/Label.qml
new file mode 100644
index 0000000..09e78b1
--- /dev/null
+++ b/imports/QtStyle/Label.qml
@@ -0,0 +1,11 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Templates as T
+import Theme
+
+T.Label {
+    color: Theme.colorLight
+    wrapMode: Text.WordWrap
+    font: Theme.font
+    opacity: enabled ? 1.0 : 0.3
+}
diff --git a/imports/QtStyle/ProgressBar.qml b/imports/QtStyle/ProgressBar.qml
new file mode 100644
index 0000000..2668b03
--- /dev/null
+++ b/imports/QtStyle/ProgressBar.qml
@@ -0,0 +1,42 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Templates as T
+import Theme
+
+T.ProgressBar {
+    id: progressBar
+    implicitWidth: background.implicitWidth
+    implicitHeight: background.implicitHeight
+
+    background: Rectangle {
+        implicitWidth: 200
+        implicitHeight: 24
+        color: "transparent"
+        border.color: Theme.colorHighlight
+        border.width: 1
+        radius: 2
+        z: 2
+    }
+
+    contentItem: Item {
+        implicitWidth: progressBar.implicitWidth
+        implicitHeight: progressBar.implicitHeight
+        clip: true
+        z: 1
+
+        Rectangle {
+            width: parent.width > 0 ? parent.width / 2 : parent.implicitWidth / 2
+            height: parent.height > 0 ? parent.height : parent.implicitHeight
+            radius: 2
+            color: Theme.colorHighlightDark
+
+            XAnimator on x {
+                from: -progressBar.width
+                to: progressBar.width
+                duration: 1500
+                running: progressBar.indeterminate
+                loops: Animation.Infinite
+            }
+        }
+    }
+}
diff --git a/imports/QtStyle/TextField.qml b/imports/QtStyle/TextField.qml
new file mode 100644
index 0000000..ec38e34
--- /dev/null
+++ b/imports/QtStyle/TextField.qml
@@ -0,0 +1,30 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Templates as T
+import Theme
+
+T.TextField {
+    id: textField
+
+    implicitWidth: background.implicitWidth
+    implicitHeight: background.implicitHeight
+    color: Theme.colorHighlight
+    padding: 6
+    bottomPadding: 3
+    topPadding: 3
+    selectByMouse: true
+    selectionColor: Theme.colorHighlightDark
+    selectedTextColor: Theme.colorHighlight
+    verticalAlignment: TextInput.AlignVCenter
+    font: Theme.font
+    opacity: enabled ? 1.0 : 0.3
+
+    background: Rectangle {
+        color: "transparent"
+        border.width: 1
+        border.color: Theme.colorHighlight
+        implicitWidth: 400
+        implicitHeight: 28
+        radius: 2
+    }
+}
diff --git a/imports/QtStyle/qmldir b/imports/QtStyle/qmldir
new file mode 100644
index 0000000..37e35b7
--- /dev/null
+++ b/imports/QtStyle/qmldir
@@ -0,0 +1,7 @@
+module QtStyle
+
+Button 1.0 Button.qml
+CheckBox 1.0 CheckBox.qml
+Label 1.0 Label.qml
+ProgressBar 1.0 ProgressBar.qml
+TextField 1.0 TextField.qml
diff --git a/imports/Theme/Theme.qml b/imports/Theme/Theme.qml
new file mode 100644
index 0000000..87ae9b3
--- /dev/null
+++ b/imports/Theme/Theme.qml
@@ -0,0 +1,16 @@
+pragma Singleton
+
+import QtQuick
+
+QtObject {
+    readonly property color colorLight: "#f3f3f4"
+    readonly property color colorDark: "#09102b"
+    readonly property color colorHighlight: "#41cd52"
+    readonly property color colorHighlightDark: Qt.darker(colorHighlight, 2.0)
+    readonly property color colorAccent: "#ffe353"
+
+    property font font
+    font.pixelSize: 14
+    font.family: "Titillium"
+}
+
diff --git a/imports/Theme/qmldir b/imports/Theme/qmldir
new file mode 100644
index 0000000..4a58c13
--- /dev/null
+++ b/imports/Theme/qmldir
@@ -0,0 +1,2 @@
+module Theme
+singleton Theme 1.0 Theme.qml
diff --git a/main.cpp b/main.cpp
index 4d66376..a034f4d 100644
--- a/main.cpp
+++ b/main.cpp
@@ -11,6 +11,7 @@ int main(int argc, char *argv[])
 
 
     QQmlApplicationEngine engine;
+    engine.addImportPath(":/imports");
     const QUrl url(u"qrc:/UnixsocketPlayground2/main.qml"_qs);
     QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
                      &app, [url](QObject *obj, const QUrl &objUrl) {
diff --git a/main.qml b/main.qml
index 23eea85..8643a71 100644
--- a/main.qml
+++ b/main.qml
@@ -1,18 +1,117 @@
 import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import Qt.labs.platform
+import QtQuick.Dialogs
+import Theme
 import HttpRequest
+import UnixsocketPlayground2
 
 Window {
-    width: 640
-    height: 480
+    id: mainWindow
+    width: 1020
+    height: 720
     visible: true
-    title: qsTr("QtAA Docker build")
+    title: qsTr("You're a wizard, Harry")
+    color: Theme.colorDark
+
+    BuildParams {
+        id: buildParameters
+
+        qtDir: pageStart.qtDir
+        ndkDir: pageStart.ndkDir
+        sdkDir: pageStart.sdkDir
+    }
+
+    Header {
+        id: header
+        anchors.left: parent.left
+        anchors.right: parent.right
+        anchors.top: parent.top
+    }
+
+    Item {
+        id: content
+        anchors.left: parent.left
+        anchors.top: header.bottom
+        anchors.right: parent.right
+        anchors.bottom: footer.top
+
+        Column {
+            id: steps
+            anchors.left: parent.left
+            anchors.top: parent.top
+            anchors.bottom: parent.bottom
+            anchors.margins: 24
+            spacing: 12
+            width: 240
+
+            Repeater {
+                model: ["What to build", "Submodules", "Summary", "Building"]
+                Label {
+                    text: modelData
+                    color: index <= stackLayout.currentIndex ? Theme.colorLight : Theme.colorHighlight
+                    font.bold: index === stackLayout.currentIndex
+                }
+            }
+        }
+
+        StackLayout {
+            id: stackLayout
+            anchors.left: steps.right
+            anchors.top: parent.top
+            anchors.right: parent.right
+            anchors.bottom: parent.bottom
+            anchors.margins: 24
+            currentIndex: 0
+            clip: true
+
+            PageStart {
+                id: pageStart
+                buildParams:  buildParameters
+            }
+
+            PageModules {
+                id: pageModules
+                buildParams: StackLayout.isCurrentItem ? buildParameters : null // just a fast way to avoid page setting values before it's shown
+            }
+
+            PageSummary {
+                id: pageSummary
+                buildParams: StackLayout.isCurrentItem ? buildParameters : null
+            }
+
+            PageBuilding {
+                id: pageBuilding
+            }
+        }
+    }
+
+    Footer {
+        id: footer
+        anchors.left: parent.left
+        anchors.right: parent.right
+        anchors.bottom: parent.bottom
+
+        currentIndex: stackLayout.currentIndex
+        pageCount: stackLayout.count
+
+        onNext: stackLayout.currentIndex++
+        onBack: stackLayout.currentIndex--
+        onBuild: startBuild()
+        onQuit: Qt.quit()
+    }
 
     HttpRequest {
         url: "http://localhost:2323/info"
         running: true
 
         onResponseBodyChanged: function(responseBody) {
-            console.log(responseBodys)
+            console.log(responseBody)
         }
     }
+
+    function startBuild() {
+        console.log("building so hard")
+    }
 }
diff --git a/qml.qrc b/qml.qrc
new file mode 100644
index 0000000..be8a9f4
--- /dev/null
+++ b/qml.qrc
@@ -0,0 +1,22 @@
+<RCC>
+    <qresource prefix="/">
+        <file>qtquickcontrols2.conf</file>
+        <file>imports/QtStyle/qmldir</file>
+        <file>imports/QtStyle/Button.qml</file>
+        <file>main.qml</file>
+        <file>icons/qt_logo_green_64x64px.png</file>
+        <file>PageStart.qml</file>
+        <file>PageModules.qml</file>
+        <file>DirSelectionItem.qml</file>
+        <file>imports/QtStyle/Label.qml</file>
+        <file>imports/QtStyle/TextField.qml</file>
+        <file>imports/QtStyle/ProgressBar.qml</file>
+        <file>Header.qml</file>
+        <file>imports/Theme/qmldir</file>
+        <file>imports/Theme/Theme.qml</file>
+        <file>imports/QtStyle/CheckBox.qml</file>
+        <file>PageBuilding.qml</file>
+        <file>Footer.qml</file>
+        <file>PageSummary.qml</file>
+    </qresource>
+</RCC>
diff --git a/qtquickcontrols2.conf b/qtquickcontrols2.conf
new file mode 100644
index 0000000..c7cae21
--- /dev/null
+++ b/qtquickcontrols2.conf
@@ -0,0 +1,6 @@
+[Controls]
+Style=QtStyle
+
+[Default]
+Font\Family=Arial
+Font\PixelSize=20
-- 
GitLab