Implement the New Project creation wizard for QDS

Task-number: QDS-4490
Change-Id: Ie8073e8838ec14a7f11ad972acc6fca4456adf58
Reviewed-by: Alessandro Portale <alessandro.portale@qt.io>
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
This commit is contained in:
Samuel Ghinet
2021-11-09 18:58:15 +02:00
parent e7d682e1b0
commit a850b1b866
44 changed files with 3164 additions and 9 deletions

View File

@@ -0,0 +1,192 @@
/****************************************************************************
**
** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
import QtQuick.Window
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import StudioTheme as StudioTheme
import StudioControls as SC
import NewProjectDialog
Item {
width: DialogValues.dialogWidth
height: DialogValues.dialogHeight
Rectangle { // the main dialog panel
anchors.fill: parent
color: DialogValues.darkPaneColor
ColumnLayout {
anchors.fill: parent
Layout.alignment: Qt.AlignHCenter
spacing: 0
Item { // Header Item
Layout.fillWidth: true
implicitHeight: 218
Column {
anchors.fill: parent
Item { width: parent.width; height: 74 } // spacer
Text {
text: qsTr("Welcome to Qt Design Studio. Let's Create Something Wonderful!")
font.pixelSize: 32
width: parent.width
height: 47
lineHeight: 49
lineHeightMode: Text.FixedHeight
color: DialogValues.textColor
horizontalAlignment: Text.AlignHCenter
}
Item { width: parent.width; height: 11 } // spacer
Text {
width: parent.width
text: qsTr("Get started by selecting from Presets or start from empty screen. You may also include your design file.")
color: DialogValues.textColor
font.pixelSize: DialogValues.paneTitlePixelSize
lineHeight: DialogValues.paneTitleLineHeight
lineHeightMode: Text.FixedHeight
horizontalAlignment: Text.AlignHCenter
}
}
} // Header Item
Item { // Content Item
Layout.fillWidth: true
Layout.fillHeight: true
RowLayout {
x: 35
width: parent.width - 70
height: parent.height
spacing: 0
Rectangle { // Left pane
color: DialogValues.lightPaneColor
Layout.fillWidth: true
Layout.fillHeight: true
Layout.minimumWidth: 379 // figured out this number visually
Layout.minimumHeight: 326 // figured out this number visually
Column {
x: DialogValues.defaultPadding // left padding
width: parent.width - DialogValues.defaultPadding * 2 // right padding
height: parent.height
Text {
text: qsTr("Presets")
width: parent.width
font.weight: Font.DemiBold
font.pixelSize: DialogValues.paneTitlePixelSize
lineHeight: DialogValues.paneTitleLineHeight
lineHeightMode: Text.FixedHeight
color: DialogValues.textColor
}
NewProjectView {
id: projectViewId
x: 10 // left padding
width: parent.width - 64 // right padding
height: DialogValues.projectViewHeight
loader: projectDetailsLoader
}
Item { height: 5; width: parent.width }
Text {
id: descriptionText
text: dialogBox.projectDescription
font.pixelSize: DialogValues.defaultPixelSize
lineHeight: DialogValues.defaultLineHeight
lineHeightMode: Text.FixedHeight
leftPadding: 14
width: projectViewId.width
color: DialogValues.textColor
wrapMode: Text.WordWrap
maximumLineCount: 4
elide: Text.ElideRight
}
}
} // Left pane
Loader {
id: projectDetailsLoader
// we need to specify width because the loaded item needs to use parent sizes
width: DialogValues.loadedPanesWidth
Layout.fillHeight: true
source: ""
}
} // RowLayout
} //Content Item
Item { // Footer
implicitHeight: DialogValues.footerHeight
implicitWidth: parent.width
RowLayout {
anchors.fill: parent
spacing: DialogValues.defaultPadding
Item { Layout.fillWidth: true }
SC.AbstractButton {
implicitWidth: DialogValues.dialogButtonWidth
width: DialogValues.dialogButtonWidth
visible: true
buttonIcon: qsTr("Cancel")
iconSize: DialogValues.defaultPixelSize
iconFont: StudioTheme.Constants.font
onClicked: {
dialogBox.reject();
}
}
SC.AbstractButton {
implicitWidth: DialogValues.dialogButtonWidth
width: DialogValues.dialogButtonWidth
visible: true
buttonIcon: qsTr("Create")
iconSize: DialogValues.defaultPixelSize
enabled: dialogBox.fieldsValid
iconFont: StudioTheme.Constants.font
onClicked: {
dialogBox.accept();
}
}
Item { implicitWidth: 35 - DialogValues.defaultPadding }
} // RowLayout
} // Footer
} // ColumnLayout
} // Rectangle
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

View File

@@ -0,0 +1,453 @@
/****************************************************************************
**
** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
import QtQuick.Window
import QtQuick.Controls
import QtQuick
import QtQuick.Layouts
import StudioControls as SC
import StudioTheme as StudioTheme
Item {
width: DialogValues.detailsPaneWidth
Component.onCompleted: {
dialogBox.detailsLoaded = true;
}
Component.onDestruction: {
dialogBox.detailsLoaded = false;
}
Rectangle {
color: DialogValues.darkPaneColor
anchors.fill: parent
Item {
x: DialogValues.detailsPanePadding // left padding
width: parent.width - DialogValues.detailsPanePadding * 2 // right padding
Column {
anchors.fill: parent
spacing: DialogValues.defaultPadding
Text {
text: qsTr("Details")
width: parent.width;
font.weight: Font.DemiBold
font.pixelSize: DialogValues.paneTitlePixelSize
lineHeight: DialogValues.paneTitleLineHeight
lineHeightMode: Text.FixedHeight
color: DialogValues.textColor
}
SC.TextField {
id: projectNameTextField
actionIndicatorVisible: false
translationIndicatorVisible: false
text: dialogBox.projectName
width: parent.width
color: DialogValues.textColor
selectByMouse: true
font.pixelSize: DialogValues.paneTitlePixelSize
}
Binding {
target: dialogBox
property: "projectName"
value: projectNameTextField.text
}
Item { width: parent.width; height: DialogValues.narrowSpacing(11) }
RowLayout { // Project location
width: parent.width
SC.TextField {
Layout.fillWidth: true
id: projectLocationTextField
actionIndicatorVisible: false
translationIndicatorVisible: false
text: dialogBox.projectLocation
color: DialogValues.textColor
selectByMouse: true
font.pixelSize: DialogValues.defaultPixelSize
}
Binding {
target: dialogBox
property: "projectLocation"
value: projectLocationTextField.text
}
SC.AbstractButton {
implicitWidth: 30
iconSize: 20
visible: true
buttonIcon: "…"
iconFont: StudioTheme.Constants.font
onClicked: {
var newLocation = dialogBox.chooseProjectLocation()
if (newLocation)
projectLocationTextField.text = newLocation
}
} // SC.AbstractButton
} // Project location RowLayout
Item { width: parent.width; height: DialogValues.narrowSpacing(7) }
RowLayout { // StatusMessage
width: parent.width
spacing: 0
Image {
id: statusIcon
asynchronous: false
}
Text {
id: statusMessage
text: dialogBox.statusMessage
font.pixelSize: DialogValues.defaultPixelSize
lineHeight: DialogValues.defaultLineHeight
lineHeightMode: Text.FixedHeight
color: DialogValues.textColor
wrapMode: Text.Wrap
elide: Text.ElideRight
maximumLineCount: 3
Layout.fillWidth: true
states: [
State {
name: "warning"
when: dialogBox.statusType === "warning"
PropertyChanges {
target: statusMessage
color: DialogValues.textWarning
}
PropertyChanges {
target: statusIcon
source: "image://newprojectdialog_library/status-warning"
}
},
State {
name: "error"
when: dialogBox.statusType === "error"
PropertyChanges {
target: statusMessage
color: DialogValues.textError
}
PropertyChanges {
target: statusIcon
source: "image://newprojectdialog_library/status-error"
}
}
]
} // Text
} // RowLayout
SC.CheckBox {
id: defaultLocationCheckbox
actionIndicatorVisible: false
text: qsTr("Use as default project location")
checked: false
font.pixelSize: DialogValues.defaultPixelSize
}
Binding {
target: dialogBox
property: "saveAsDefaultLocation"
value: defaultLocationCheckbox.checked
}
Rectangle { width: parent.width; height: 1; color: DialogValues.dividerlineColor }
SC.ComboBox { // Screen Size ComboBox
id: screenSizeComboBox
actionIndicatorVisible: false
currentIndex: 1
model: screenSizeModel
textRole: "display"
width: parent.width
font.pixelSize: DialogValues.defaultPixelSize
onActivated: (index) => {
// NOTE: item 0 is activated when the screenSizeModel is reset
dialogBox.setScreenSizeIndex(index);
var r = screenSizeModel.screenSizes(index);
widthTextField.text = r.width;
heightTextField.text = r.height;
}
Connections {
target: screenSizeModel
function onModelReset() {
screenSizeComboBox.activated(screenSizeComboBox.currentIndex)
}
}
} // Screen Size ComboBox
GridLayout { // orientation + width + height
width: parent.width
height: 85
columns: 4
rows: 2
columnSpacing: 10
rowSpacing: 10
// header items
Text {
text: qsTr("Width")
font.pixelSize: DialogValues.defaultPixelSize
lineHeight: DialogValues.defaultLineHeight
lineHeightMode: Text.FixedHeight
color: DialogValues.textColor
}
Text {
text: qsTr("Height")
font.pixelSize: DialogValues.defaultPixelSize
lineHeight: DialogValues.defaultLineHeight
lineHeightMode: Text.FixedHeight
color: DialogValues.textColor
}
Item { Layout.fillWidth: true }
Text {
text: qsTr("Orientation")
font.pixelSize: DialogValues.defaultPixelSize
lineHeight: DialogValues.defaultLineHeight
lineHeightMode: Text.FixedHeight
color: DialogValues.textColor
}
// content items
SC.TextField {
id: widthTextField
actionIndicatorVisible: false
translationIndicatorVisible: false
implicitWidth: 50
color: DialogValues.textColor
selectByMouse: true
validator: IntValidator { bottom: 1; top: 100000; }
font.pixelSize: DialogValues.defaultPixelSize
onTextChanged: {
var height = heightTextField.text ? parseInt(heightTextField.text) : 0
var width = text ? parseInt(text) : 0
if (width >= height)
orientationButton.setHorizontal()
else
orientationButton.setVertical()
}
} // Width Text Field
Binding {
target: dialogBox
property: "customWidth"
value: widthTextField.text
}
SC.TextField {
id: heightTextField
actionIndicatorVisible: false
translationIndicatorVisible: false
implicitWidth: 50
color: DialogValues.textColor
selectByMouse: true
validator: IntValidator { bottom: 1; top: 100000; }
font.pixelSize: DialogValues.defaultPixelSize
onTextChanged: {
var height = text ? parseInt(text) : 0
var width = widthTextField.text ? parseInt(widthTextField.text) : 0
if (width >= height)
orientationButton.setHorizontal()
else
orientationButton.setVertical()
}
} // Height Text Field
Binding {
target: dialogBox
property: "customHeight"
value: heightTextField.text
}
Item { Layout.fillWidth: true }
Button {
id: orientationButton
implicitWidth: 100
implicitHeight: 50
checked: false
hoverEnabled: false
background: Rectangle {
width: parent.width
height: parent.height
color: "transparent"
Row {
Item {
width: orientationButton.width / 2
height: orientationButton.height
Rectangle {
id: horizontalBar
color: "white"
width: parent.width
height: orientationButton.height / 2
anchors.verticalCenter: parent.verticalCenter
}
}
Item {
width: orientationButton.width / 4
height: orientationButton.height
}
Rectangle {
id: verticalBar
width: orientationButton.width / 4
height: orientationButton.height
color: "white"
}
}
}
onClicked: {
if (widthTextField.text && heightTextField.text) {
[widthTextField.text, heightTextField.text] = [heightTextField.text, widthTextField.text];
checked = !checked
}
}
function setHorizontal() {
checked = false
horizontalBar.color = DialogValues.textColorInteraction
verticalBar.color = "white"
}
function setVertical() {
checked = true
horizontalBar.color = "white"
verticalBar.color = DialogValues.textColorInteraction
}
} // Orientation button
} // GridLayout: orientation + width + height
Rectangle { width: parent.width; height: 1; color: DialogValues.dividerlineColor }
SC.Section {
width: parent.width
caption: qsTr("Advanced")
captionPixelSize: DialogValues.defaultPixelSize
captionColor: DialogValues.darkPaneColor
captionTextColor: DialogValues.textColor
leftPadding: 0
expanded: true
visible: dialogBox.haveVirtualKeyboard || dialogBox.haveTargetQtVersion
Column {
spacing: DialogValues.defaultPadding
width: parent.width
/* We need a spacer of -10 in order to have actual 18px spacing between
* section bottom and the checkbox. Otherwise, with Column spacing set to
* 18, without a spacer, the default space to the first item would be 10,
* for some reason. */
Item { width: parent.width; height: -10 }
SC.CheckBox {
id: useQtVirtualKeyboard
actionIndicatorVisible: false
text: qsTr("Use Qt Virtual Keyboard")
font.pixelSize: DialogValues.defaultPixelSize
checked: dialogBox.useVirtualKeyboard
visible: dialogBox.haveVirtualKeyboard
}
RowLayout { // Target Qt Version
width: parent.width
visible: dialogBox.haveTargetQtVersion
Text {
text: "Target Qt Version:"
font.pixelSize: DialogValues.defaultPixelSize
lineHeight: DialogValues.defaultLineHeight
lineHeightMode: Text.FixedHeight
color: DialogValues.textColor
}
SC.ComboBox { // Target Qt Version ComboBox
id: qtVersionComboBox
actionIndicatorVisible: false
implicitWidth: 70
Layout.alignment: Qt.AlignRight
currentIndex: 1
font.pixelSize: DialogValues.defaultPixelSize
model: ListModel {
ListElement {
name: "Qt 5"
}
ListElement {
name: "Qt 6"
}
}
width: parent.width
onActivated: (index) => {
dialogBox.setTargetQtVersion(index)
}
} // Target Qt Version ComboBox
} // RowLayout
} // Column
} // SC.Section
Binding {
target: dialogBox
property: "useVirtualKeyboard"
value: useQtVirtualKeyboard.checked
}
} // Column
} // Item
}
}

View File

@@ -0,0 +1,83 @@
/****************************************************************************
**
** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
pragma Singleton
import QtQml
import StudioTheme as StudioTheme
QtObject {
readonly property int dialogWidth: 1522
readonly property int dialogHeight: 994
readonly property int projectViewMinimumWidth: 600
readonly property int projectViewMinimumHeight: projectViewHeight
readonly property int dialogContentHeight: projectViewHeight + 300 // i.e. dialog without header and footer
readonly property int loadedPanesWidth: detailsPaneWidth + stylesPaneWidth
readonly property int detailsPaneWidth: 330 + detailsPanePadding * 2
readonly property int stylesPaneWidth: styleImageWidth + stylesPanePadding * 2 + styleImageBorderWidth * 2 // i.e. 240px
readonly property int detailsPanePadding: 18
readonly property int stylesPanePadding: 18
readonly property int defaultPadding: 18
readonly property int styleImageWidth: 200
readonly property int styleImageBorderWidth: 2
readonly property int footerHeight: 73
readonly property int projectItemWidth: 144
readonly property int projectItemHeight: 144
readonly property int projectViewHeight: projectItemHeight * 2 + projectViewHeaderHeight
readonly property int projectViewHeaderHeight: 38
readonly property int dialogButtonWidth: 100
readonly property int loadedPanesHeight: dialogContentHeight
readonly property int detailsPaneHeight: dialogContentHeight
readonly property string darkPaneColor: StudioTheme.Values.themeBackgroundColorNormal
readonly property string lightPaneColor: StudioTheme.Values.themeBackgroundColorAlternate
readonly property string textColor: StudioTheme.Values.themeTabInactiveText
readonly property string textColorInteraction: StudioTheme.Values.themeInteraction
readonly property string dividerlineColor: StudioTheme.Values.themeTextColorDisabled
readonly property string textError: StudioTheme.Values.themeError
readonly property string textWarning: StudioTheme.Values.themeWarning
readonly property real defaultPixelSize: 14
readonly property real defaultLineHeight: 21
readonly property real viewHeaderPixelSize: 16
readonly property real viewHeaderLineHeight: 24
readonly property real paneTitlePixelSize: 18
readonly property real paneTitleLineHeight: 27
// for a spacer item
function narrowSpacing(value, layoutSpacing = DialogValues.defaultPadding) {
/* e.g. if we want narrow spacing value = 11, then for the spacer item residing inside a
layout with spacing set to 18, we need to realize the fact that by adding the spacer
item, we already have 18 * 2 spacing added implicitly (i.e. spacing before the spacer
item and spacing after it). So we have to subtract 2 x layout spacing before setting
our own, narrower, spacing.
*/
return -layoutSpacing -layoutSpacing + value
}
}

View File

@@ -0,0 +1,198 @@
/****************************************************************************
**
** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
import QtQuick.Window
import QtQuick.Controls
import QtQuick
import QtQuick.Layouts
import StudioTheme as StudioTheme
GridView {
id: projectView
required property Item loader
header: TabBar {
id: tabBar
width: parent.width
height: DialogValues.projectViewHeaderHeight
background: Rectangle {
color: DialogValues.lightPaneColor
}
Repeater {
model: categoryModel
TabButton {
padding: 0
width: headerText.contentWidth + 36
background: Item { // TabButton background
Rectangle { // bottom strip
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
width: headerText.contentWidth
height: 6
radius: 10
color: tabBar.currentIndex === index ? DialogValues.textColorInteraction
: "transparent"
}
} // TabButton background
implicitHeight: headerText.height + DialogValues.defaultPadding - 7
contentItem: Item {
Column {
anchors.fill: parent
Text {
id: headerText
color: tabBar.currentIndex == index ? DialogValues.textColorInteraction
: DialogValues.textColor
text: name
width: parent.width
font.weight: Font.DemiBold
font.pixelSize: DialogValues.viewHeaderPixelSize
lineHeight: DialogValues.viewHeaderLineHeight
lineHeightMode: Text.FixedHeight
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
Item { width: parent.width; height: 11; }
} // Column
} // Item
onClicked: {
projectModel.setPage(index)
projectView.currentIndex = 0
projectView.currentIndexChanged()
}
} // TabButton
} // Repeater
} // Header - TabBar
cellWidth: DialogValues.projectItemWidth
cellHeight: DialogValues.projectItemHeight
boundsBehavior: Flickable.StopAtBounds
children: [
Rectangle {
color: DialogValues.darkPaneColor
anchors.fill: parent
z: -1
}
]
model: projectModel
// called by onModelReset and when user clicks on an item, or when the header item is changed.
onCurrentIndexChanged: {
dialogBox.selectedProject = projectView.currentIndex
var source = dialogBox.currentProjectQmlPath()
loader.source = source
}
Connections {
target: projectModel
// called when data is set (setWizardFactories)
function onModelReset() {
currentIndex = 0
currentIndexChanged()
}
}
delegate: ItemDelegate {
id: delegate
width: DialogValues.projectItemWidth
height: DialogValues.projectItemHeight
function fontIconCode(index) {
var code = projectModel.fontIconCode(index)
return code ? code : StudioTheme.Constants.wizardsUnknown
}
Column {
width: parent.width
height: parent.height
Label {
id: projectTypeIcon
text: fontIconCode(index)
color: DialogValues.textColor
width: parent.width
height: DialogValues.projectItemHeight / 2
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignBottom
renderType: Text.NativeRendering
font.pixelSize: 65
font.family: StudioTheme.Constants.iconFont.family
}
Text {
id: projectTypeLabel
color: DialogValues.textColor
text: name
font.pixelSize: DialogValues.defaultPixelSize
lineHeight: DialogValues.defaultLineHeight
lineHeightMode: Text.FixedHeight
width: parent.width
height: DialogValues.projectItemHeight / 2
wrapMode: Text.Wrap
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignTop
}
} // Column
MouseArea {
anchors.fill: parent
onClicked: {
delegate.GridView.view.currentIndex = index
}
}
states: [
State {
when: delegate.GridView.isCurrentItem
PropertyChanges {
target: projectTypeLabel
color: DialogValues.textColorInteraction
}
PropertyChanges {
target: projectTypeIcon
color: DialogValues.textColorInteraction
}
} // State
]
} // ItemDelegate
} // GridView

View File

@@ -0,0 +1,183 @@
/****************************************************************************
**
** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
import QtQuick.Window
import QtQuick.Controls
import QtQuick
import QtQuick.Layouts
import StudioControls as SC
Item {
width: DialogValues.stylesPaneWidth
Component.onCompleted: {
dialogBox.stylesLoaded = true;
/*
* TODO: roleNames is called before the backend model (in the proxy class StyleModel) is
* loaded, which may be buggy. But I found no way to force refresh the model, so as to
* reload the role names afterwards. setting styleModel.dynamicRoles doesn't appear to do
* anything.
*/
}
Component.onDestruction: {
dialogBox.stylesLoaded = false;
}
Rectangle {
color: DialogValues.lightPaneColor
anchors.fill: parent
Item {
x: DialogValues.stylesPanePadding // left padding
width: parent.width - DialogValues.stylesPanePadding * 2 // right padding
height: parent.height
ColumnLayout {
anchors.fill: parent
spacing: 5
Text {
id: styleTitleText
text: qsTr("Style")
width: parent.width;
font.weight: Font.DemiBold
font.pixelSize: DialogValues.paneTitlePixelSize
lineHeight: DialogValues.paneTitleLineHeight
lineHeightMode: Text.FixedHeight
color: DialogValues.textColor
function refresh() {
text = qsTr("Style") + " (" + styleModel.rowCount() + ")"
}
}
SC.ComboBox { // Style Filter ComboBox
actionIndicatorVisible: false
currentIndex: 0
textRole: "text"
valueRole: "value"
font.pixelSize: DialogValues.defaultPixelSize
model: ListModel {
ListElement { text: qsTr("All"); value: "all" }
ListElement { text: qsTr("Light"); value: "light" }
ListElement { text: qsTr("Dark"); value: "dark" }
}
implicitWidth: parent.width
onActivated: (index) => {
styleModel.filter(currentValue.toLowerCase());
styleTitleText.refresh();
}
} // Style Filter ComboBox
ListView {
id: stylesList
Layout.fillWidth: true
Layout.fillHeight: true
clip: true
model: styleModel
focus: true
boundsBehavior: Flickable.StopAtBounds
highlightFollowsCurrentItem: false
onCurrentIndexChanged: {
if (styleModel.rowCount() > 0)
dialogBox.styleIndex = stylesList.currentIndex;
}
delegate: ItemDelegate {
id: delegateId
height: styleImage.height + DialogValues.styleImageBorderWidth + styleText.height + 1
width: stylesList.width
Rectangle {
anchors.fill: parent
color: DialogValues.lightPaneColor
Column {
spacing: 0
anchors.fill: parent
Rectangle {
border.color: index == stylesList.currentIndex ? DialogValues.textColorInteraction : "transparent"
border.width: index == stylesList.currentIndex ? DialogValues.styleImageBorderWidth : 0
color: "transparent"
width: parent.width
height: parent.height - styleText.height
Image {
id: styleImage
asynchronous: false
source: "image://newprojectdialog_library/" + styleModel.iconId(model.index)
width: 200
height: 262
x: DialogValues.styleImageBorderWidth
y: DialogValues.styleImageBorderWidth
}
} // Rectangle
Text {
id: styleText
text: model.display
font.pixelSize: DialogValues.defaultPixelSize
lineHeight: DialogValues.defaultLineHeight
height: 18
lineHeightMode: Text.FixedHeight
horizontalAlignment: Text.AlignHCenter
width: parent.width
color: DialogValues.textColor
}
} // Column
} // Rectangle
MouseArea {
anchors.fill: parent
onClicked: {
stylesList.currentIndex = index
}
}
}
Connections {
target: styleModel
function onModelReset() {
stylesList.currentIndex = dialogBox.styleIndex;
stylesList.currentIndexChanged();
styleTitleText.refresh();
}
}
} // ListView
} // ColumnLayout
} // Parent Item
} // Rectangle
}

View File

@@ -0,0 +1,4 @@
singleton DialogValues 1.0 DialogValues.qml
Details 1.0 Details.qml
Styles 1.0 Styles.qml
NewProjectView 1.0 NewProjectView.qml

View File

@@ -0,0 +1,48 @@
/****************************************************************************
**
** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
import QtQuick.Window
import QtQuick.Controls
import QtQuick
import QtQuick.Layouts
import newprojectdialog
Item {
anchors.fill: parent
Row {
anchors.fill: parent
Details {
height: parent.height
}
Styles {
height: parent.height
}
}
}

View File

@@ -0,0 +1 @@
DefaultProject 1.0 DefaultProject.qml

View File

@@ -30,6 +30,9 @@ import StudioTheme 1.0 as StudioTheme
Item {
id: section
property alias caption: label.text
property alias captionPixelSize: label.font.pixelSize
property alias captionColor: header.color
property alias captionTextColor: label.color
property int leftPadding: 8
property int topPadding: 4
property int rightPadding: 0

View File

@@ -57,7 +57,6 @@ add_subdirectory(scxmleditor)
add_subdirectory(subversion)
add_subdirectory(compilationdatabaseprojectmanager)
add_subdirectory(languageclient)
add_subdirectory(studiowelcome)
# Level 6:
add_subdirectory(cmakeprojectmanager)
@@ -93,6 +92,7 @@ if (WIN32 AND CMAKE_CXX_COMPILER_ID MATCHES "Clang")
set(qmldesigner_builddir ${PROJECT_BINARY_DIR}/qmldsgnr)
endif()
add_subdirectory(qmldesigner ${qmldesigner_builddir})
add_subdirectory(studiowelcome)
add_subdirectory(qnx)
add_subdirectory(webassembly)
add_subdirectory(mcusupport)

View File

@@ -32,6 +32,7 @@
#include <extensionsystem/pluginmanager.h>
#include <utils/qtcassert.h>
#include <utils/algorithm.h>
#include <QApplication>
#include <QDebug>
@@ -165,9 +166,13 @@ namespace Core {
// The Core Singleton
static ICore *m_instance = nullptr;
static MainWindow *m_mainwindow = nullptr;
std::function<NewDialog *(QWidget *)> ICore::m_newDialogFactory = [](QWidget *parent) {
static NewDialog *defaultDialogFactory(QWidget *parent)
{
return new NewDialogWidget(parent);
};
}
static std::function<NewDialog *(QWidget *)> m_newDialogFactory = defaultDialogFactory;
/*!
Returns the pointer to the instance. Only use for connecting to signals.
@@ -253,7 +258,23 @@ void ICore::showNewItemDialog(const QString &title,
const QVariantMap &extraVariables)
{
QTC_ASSERT(!isNewItemDialogRunning(), return);
NewDialog *newDialog = ICore::m_newDialogFactory(dialogParent());
/* This is a workaround for QDS: In QDS, we currently have a "New Project" dialog box but we do
* not also have a "New file" dialog box (yet). Therefore, when requested to add a new file, we
* need to use QtCreator's dialog box. In QDS, if `factories` contains project wizard factories
* (even though it may contain file wizard factories as well), then we consider it to be a
* request for "New Project". Otherwise, if we only have file wizard factories, we defer to
* QtCreator's dialog and request "New File"
*/
auto dialogFactory = m_newDialogFactory;
bool haveProjectWizards = Utils::anyOf(factories, [](IWizardFactory *f) {
return f->kind() == IWizardFactory::ProjectWizard;
});
if (!haveProjectWizards)
dialogFactory = defaultDialogFactory;
NewDialog *newDialog = dialogFactory(dialogParent());
connect(newDialog->widget(), &QObject::destroyed, m_instance, &ICore::updateNewItemDialogState);
newDialog->setWizardFactories(factories, defaultLocation, extraVariables);
newDialog->setWindowTitle(title);

View File

@@ -180,8 +180,6 @@ public:
private:
static void updateNewItemDialogState();
static std::function<NewDialog *(QWidget *)> m_newDialogFactory;
};
} // namespace Core

View File

@@ -102,6 +102,14 @@ namespace Internal {
enum { debugMainWindow = 0 };
static bool isQtDesignStudio()
{
QSettings *settings = Core::ICore::settings();
const QString qdsStandaloneEntry = "QML/Designer/StandAloneMode"; //entry from qml settings
return settings->value(qdsStandaloneEntry, false).toBool();
}
MainWindow::MainWindow()
: AppMainWindow()
, m_coreImpl(new ICore(this))
@@ -519,7 +527,8 @@ void MainWindow::registerDefaultActions()
// New File Action
QIcon icon = QIcon::fromTheme(QLatin1String("document-new"), Utils::Icons::NEWFILE.icon());
m_newAction = new QAction(icon, tr("&New File or Project..."), this);
QString newActionText = isQtDesignStudio() ? tr("&New Project...") : tr("&New File or Project...");
m_newAction = new QAction(icon, newActionText, this);
cmd = ActionManager::registerAction(m_newAction, Constants::NEW);
cmd->setDefaultKeySequence(QKeySequence::New);
mfile->addAction(cmd, Constants::G_FILE_NEW);

View File

@@ -25,12 +25,13 @@
#pragma once
#include "../projectexplorer_export.h"
#include <utils/projectintropage.h>
namespace ProjectExplorer {
// Documentation inside.
class JsonProjectPage : public Utils::ProjectIntroPage
class PROJECTEXPLORER_EXPORT JsonProjectPage : public Utils::ProjectIntroPage
{
Q_OBJECT

View File

@@ -1,12 +1,20 @@
add_qtc_plugin(StudioWelcome
CONDITION TARGET Qt5::QuickWidgets
DEPENDS Qt5::QuickWidgets
PLUGIN_DEPENDS Core ProjectExplorer QtSupport
PLUGIN_DEPENDS Core ProjectExplorer QtSupport QmlDesigner
DEFINES STUDIO_QML_PATH="${CMAKE_CURRENT_SOURCE_DIR}/qml/"
SOURCES
studiowelcomeplugin.cpp studiowelcomeplugin.h
newprojectdialogimageprovider.cpp newprojectdialogimageprovider.h
newprojectmodel.cpp newprojectmodel.h
examplecheckout.cpp examplecheckout.h
studiowelcome_global.h
qdsnewdialog.cpp qdsnewdialog.h
wizardfactories.cpp wizardfactories.h
createproject.cpp createproject.h
wizardhandler.cpp wizardhandler.h
screensizemodel.h
stylemodel.h stylemodel.cpp
studiowelcome.qrc
"${PROJECT_SOURCE_DIR}/src/share/3rdparty/studiofonts/studiofonts.qrc"
EXTRA_TRANSLATIONS

View File

@@ -0,0 +1,77 @@
/****************************************************************************
**
** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "createproject.h"
#include <projectexplorer/jsonwizard/jsonprojectpage.h>
#include <projectexplorer/jsonwizard/jsonfieldpage.h>
#include <projectexplorer/jsonwizard/jsonfieldpage_p.h>
using namespace StudioWelcome;
void CreateProject::execute()
{
m_wizard.run([&](QWizardPage *page) {
if (auto *p = dynamic_cast<ProjectExplorer::JsonProjectPage *>(page))
processProjectPage(p);
else if (auto *p = dynamic_cast<ProjectExplorer::JsonFieldPage *>(page))
processFieldPage(p);
});
}
void CreateProject::processProjectPage(ProjectExplorer::JsonProjectPage *page)
{
page->setProjectName(m_projectName);
page->setFilePath(m_projectLocation);
page->setUseAsDefaultPath(m_saveAsDefaultLocation);
page->fieldsUpdated();
}
void CreateProject::processFieldPage(ProjectExplorer::JsonFieldPage *page)
{
if (page->jsonField("ScreenFactor"))
m_wizard.setScreenSizeIndex(m_screenSizeIndex);
if (page->jsonField("TargetQtVersion") && m_targetQtVersionIndex > -1)
m_wizard.setTargetQtVersionIndex(m_targetQtVersionIndex);
if (page->jsonField("ControlsStyle"))
m_wizard.setStyleIndex(m_styleIndex);
if (page->jsonField("UseVirtualKeyboard"))
m_wizard.setUseVirtualKeyboard(m_useVirtualKeyboard);
auto widthField = dynamic_cast<ProjectExplorer::LineEditField *>(page->jsonField("CustomScreenWidth"));
auto heightField = dynamic_cast<ProjectExplorer::LineEditField *>(page->jsonField("CustomScreenHeight"));
if (widthField && heightField) {
if (!m_customWidth.isEmpty() && !m_customHeight.isEmpty()) {
widthField->setText(m_customWidth);
heightField->setText(m_customHeight);
}
}
}

View File

@@ -0,0 +1,77 @@
/****************************************************************************
**
** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include "wizardhandler.h"
namespace ProjectExplorer {
class JsonProjectPage;
}
namespace StudioWelcome {
class CreateProject
{
public:
CreateProject(WizardHandler &wizard): m_wizard{wizard} {}
CreateProject &withName(const QString &name) { m_projectName = name; return *this; }
CreateProject &atLocation(const Utils::FilePath &location) { m_projectLocation = location; return *this; }
CreateProject &withScreenSizes(int screenSizeIndex, const QString &customWidth, const QString &customHeight)
{
m_screenSizeIndex = screenSizeIndex;
m_customWidth = customWidth;
m_customHeight = customHeight;
return *this;
}
CreateProject &withStyle(int styleIndex) { m_styleIndex = styleIndex; return *this; }
CreateProject &useQtVirtualKeyboard(bool value) { m_useVirtualKeyboard = value; return *this; }
CreateProject &saveAsDefaultLocation(bool value) { m_saveAsDefaultLocation = value; return *this; }
CreateProject &withTargetQtVersion(int targetQtVersionIndex)
{ m_targetQtVersionIndex = targetQtVersionIndex; return *this; }
void execute();
private:
void processProjectPage(ProjectExplorer::JsonProjectPage *page);
void processFieldPage(ProjectExplorer::JsonFieldPage *page);
private:
WizardHandler &m_wizard;
QString m_projectName;
Utils::FilePath m_projectLocation;
int m_screenSizeIndex = -1;
QString m_customWidth;
QString m_customHeight;
int m_styleIndex = -1;
bool m_useVirtualKeyboard = false;
bool m_saveAsDefaultLocation = false;
int m_targetQtVersionIndex = -1;
};
} // StudioWelcome

View File

@@ -0,0 +1,99 @@
/****************************************************************************
**
** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "newprojectdialogimageprovider.h"
#include <coreplugin/icore.h>
#include <utils/utilsicons.h>
#include <utils/stylehelper.h>
namespace StudioWelcome {
namespace Internal {
NewProjectDialogImageProvider::NewProjectDialogImageProvider()
: QQuickImageProvider(QQuickImageProvider::Pixmap)
{}
QPixmap NewProjectDialogImageProvider::invalidStyleIcon()
{
QString iconPath = Core::ICore::resourcePath("qmldesigner/newprojectdialog/image/style-error.png").toString();
QString file = Utils::StyleHelper::dpiSpecificImageFile(iconPath);
return QPixmap{file};
}
QPixmap NewProjectDialogImageProvider::requestStatusPixmap(const QString &id, QSize *size, const QSize &requestedSize)
{
QPixmap pixmap;
if (id == "status-warning") {
static const QPixmap warning = Utils::Icons::WARNING.pixmap();
pixmap = warning;
} else if (id == "status-error") {
static const QPixmap error = Utils::Icons::CRITICAL.pixmap();
pixmap = error;
}
if (requestedSize.isValid())
return pixmap.scaled(requestedSize);
return pixmap;
}
QPixmap NewProjectDialogImageProvider::requestStylePixmap(const QString &id, QSize *size, const QSize &requestedSize)
{
QString realPath = Core::ICore::resourcePath("qmldesigner/newprojectdialog/image/" + id).toString();
QPixmap pixmap{realPath};
if (size) {
size->setWidth(pixmap.width());
size->setHeight(pixmap.height());
}
if (pixmap.isNull())
pixmap = invalidStyleIcon();
if (requestedSize.isValid())
return pixmap.scaled(requestedSize);
return pixmap;
}
QPixmap NewProjectDialogImageProvider::requestPixmap(const QString &id, QSize *size, const QSize &requestedSize)
{
if (id.startsWith("style-"))
return requestStylePixmap(id, size, requestedSize);
if (id.startsWith("status-"))
return requestStatusPixmap(id, size, requestedSize);
return QPixmap{};
}
} // namespace Internal
} // namespace StudioWelcome

View File

@@ -0,0 +1,50 @@
/****************************************************************************
**
** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include <QQuickImageProvider>
namespace StudioWelcome {
namespace Internal {
class NewProjectDialogImageProvider : public QQuickImageProvider
{
public:
NewProjectDialogImageProvider();
QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) override;
private:
QPixmap requestStatusPixmap(const QString &id, QSize *size, const QSize &requestedSize);
QPixmap requestStylePixmap(const QString &id, QSize *size, const QSize &requestedSize);
static QPixmap invalidStyleIcon();
};
} // namespace Internal
} // namespace StudioWelcome

View File

@@ -0,0 +1,106 @@
/****************************************************************************
**
** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "newprojectmodel.h"
using namespace StudioWelcome;
/****************** BaseNewProjectModel ******************/
BaseNewProjectModel::BaseNewProjectModel(QObject *parent)
: QAbstractListModel(parent)
{}
QHash<int, QByteArray> BaseNewProjectModel::roleNames() const
{
QHash<int, QByteArray> roleNames;
roleNames[Qt::UserRole] = "name";
return roleNames;
}
void BaseNewProjectModel::setProjects(const ProjectsByCategory &projectsByCategory)
{
beginResetModel();
for (auto &[id, category] : projectsByCategory) {
m_categories.push_back(category.name);
m_projects.push_back(category.items);
}
endResetModel();
}
/****************** NewProjectCategoryModel ******************/
NewProjectCategoryModel::NewProjectCategoryModel(QObject *parent)
: BaseNewProjectModel(parent)
{}
int NewProjectCategoryModel::rowCount(const QModelIndex &) const
{
return static_cast<int>(categories().size());
}
QVariant NewProjectCategoryModel::data(const QModelIndex &index, int role) const
{
return categories().at(index.row());
}
/****************** NewProjectModel ******************/
NewProjectModel::NewProjectModel(QObject *parent)
: BaseNewProjectModel(parent)
{}
int NewProjectModel::rowCount(const QModelIndex &) const
{
if (projects().empty())
return 0;
return static_cast<int>(projectsOfCurrentCategory().size());
}
QVariant NewProjectModel::data(const QModelIndex &index, int role) const
{
return projectsOfCurrentCategory().at(index.row()).name;
}
void NewProjectModel::setPage(int index)
{
beginResetModel();
m_page = static_cast<size_t>(index);
endResetModel();
}
QString NewProjectModel::fontIconCode(int index) const
{
Utils::optional<ProjectItem> projectItem = project(index);
if (!projectItem)
return "";
return projectItem->fontIconCode;
}

View File

@@ -0,0 +1,145 @@
/****************************************************************************
**
** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include <QAbstractListModel>
#include <QUrl>
#include <utils/filepath.h>
#include <utils/optional.h>
namespace Utils {
class Wizard;
}
namespace StudioWelcome {
struct ProjectItem
{
QString name;
QString categoryId;
QString description;
QUrl qmlPath;
QString fontIconCode;
std::function<Utils::Wizard *(const Utils::FilePath &path)> create;
};
inline QDebug &operator<<(QDebug &d, const ProjectItem &item)
{
d << "name=" << item.name;
d << "; category = " << item.categoryId;
return d;
}
struct ProjectCategory
{
QString id;
QString name;
std::vector<ProjectItem> items;
};
inline QDebug &operator<<(QDebug &d, const ProjectCategory &cat)
{
d << "id=" << cat.id;
d << "; name=" << cat.name;
d << "; items=" << cat.items;
return d;
}
using ProjectsByCategory = std::map<QString, ProjectCategory>;
/****************** BaseNewProjectModel ******************/
class BaseNewProjectModel : public QAbstractListModel
{
using ProjectItems = std::vector<std::vector<ProjectItem>>;
using Categories = std::vector<QString>;
public:
explicit BaseNewProjectModel(QObject *parent = nullptr);
QHash<int, QByteArray> roleNames() const override;
void setProjects(const ProjectsByCategory &projects);
protected:
const ProjectItems &projects() const { return m_projects; }
const Categories &categories() const { return m_categories; }
private:
ProjectItems m_projects;
Categories m_categories;
};
/****************** NewProjectCategoryModel ******************/
class NewProjectCategoryModel : public BaseNewProjectModel
{
public:
explicit NewProjectCategoryModel(QObject *parent = nullptr);
int rowCount(const QModelIndex &parent) const override;
QVariant data(const QModelIndex &index, int role) const override;
};
/****************** NewProjectModel ******************/
class NewProjectModel : public BaseNewProjectModel
{
Q_OBJECT
public:
explicit NewProjectModel(QObject *parent = nullptr);
int rowCount(const QModelIndex &parent) const override;
QVariant data(const QModelIndex &index, int role) const override;
Q_INVOKABLE void setPage(int index); // called from QML when view's header item is clicked
Q_INVOKABLE QString fontIconCode(int index) const;
int page() const { return static_cast<int>(m_page); }
Utils::optional<ProjectItem> project(size_t selection) const
{
if (projects().empty())
return {};
if (m_page < projects().size()) {
const std::vector<ProjectItem> projectsOfCategory = projects().at(m_page);
if (selection < projectsOfCategory.size())
return projects().at(m_page).at(selection);
}
return {};
}
bool empty() const { return projects().empty(); }
private:
const std::vector<ProjectItem> projectsOfCurrentCategory() const
{ return projects().at(m_page); }
private:
size_t m_page = 0;
};
} // namespace StudioWelcome

View File

@@ -0,0 +1,343 @@
/****************************************************************************
**
** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include <QQmlContext>
#include <QMessageBox>
#include "qdsnewdialog.h"
#include <coreplugin/icore.h>
#include <coreplugin/iwizardfactory.h>
#include <utils/qtcassert.h>
#include <qmldesigner/components/componentcore/theme.h>
#include "createproject.h"
#include "wizardfactories.h"
#include "newprojectdialogimageprovider.h"
using namespace StudioWelcome;
namespace {
/*
* NOTE: copied from projectexplorer/jsonwizard/jsonprojectpage.h
*/
QString uniqueProjectName(const QString &path)
{
const QDir pathDir(path);
//: File path suggestion for a new project. If you choose
//: to translate it, make sure it is a valid path name without blanks
//: and using only ascii chars.
const QString prefix = QObject::tr("UntitledProject");
QString name = prefix;
int i = 0;
while (pathDir.exists(name))
name = prefix + QString::number(++i);
return name;
}
}
/***********************/
QdsNewDialog::QdsNewDialog(QWidget *parent)
: m_dialog{new QQuickWidget(parent)}
, m_categoryModel{new NewProjectCategoryModel(this)}
, m_projectModel{new NewProjectModel(this)}
, m_screenSizeModel{new ScreenSizeModel(this)}
, m_styleModel{new StyleModel(this)}
{
setParent(m_dialog);
m_dialog->rootContext()->setContextProperties(QVector<QQmlContext::PropertyPair>{
{{"categoryModel"}, QVariant::fromValue(m_categoryModel.data())},
{{"projectModel"}, QVariant::fromValue(m_projectModel.data())},
{{"screenSizeModel"}, QVariant::fromValue(m_screenSizeModel.data())},
{{"styleModel"}, QVariant::fromValue(m_styleModel.data())},
{{"dialogBox"}, QVariant::fromValue(this)},
});
m_dialog->setResizeMode(QQuickWidget::SizeRootObjectToView); // SizeViewToRootObject
m_dialog->engine()->addImageProvider(QStringLiteral("newprojectdialog_library"),
new Internal::NewProjectDialogImageProvider());
QmlDesigner::Theme::setupTheme(m_dialog->engine());
m_dialog->engine()->addImportPath(Core::ICore::resourcePath("qmldesigner/propertyEditorQmlSources/imports").toString());
m_dialog->engine()->addImportPath(Core::ICore::resourcePath("qmldesigner/newprojectdialog/imports").toString());
QString sourcesPath = qmlPath();
m_dialog->setSource(QUrl::fromLocalFile(sourcesPath));
m_dialog->setWindowModality(Qt::ApplicationModal);
m_dialog->setWindowFlags(Qt::Dialog);
m_dialog->setAttribute(Qt::WA_DeleteOnClose);
m_dialog->setMinimumSize(1155, 804);
QObject::connect(&m_wizard, &WizardHandler::deletingWizard, this, &QdsNewDialog::onDeletingWizard);
QObject::connect(&m_wizard, &WizardHandler::wizardCreated, this, &QdsNewDialog::onWizardCreated);
QObject::connect(&m_wizard, &WizardHandler::statusMessageChanged, this, &QdsNewDialog::onStatusMessageChanged);
QObject::connect(&m_wizard, &WizardHandler::projectCanBeCreated, this, &QdsNewDialog::onProjectCanBeCreatedChanged);
QObject::connect(&m_wizard, &WizardHandler::wizardCreationFailed, this, [this]() {
QMessageBox::critical(m_dialog, "New project", "Failed to initialize data");
reject();
delete this;
});
QObject::connect(m_styleModel.data(), &StyleModel::modelAboutToBeReset, this, [this]() {
this->m_qmlStyleIndex = -1;
});
}
void QdsNewDialog::onDeletingWizard()
{
m_screenSizeModel->setBackendModel(nullptr);
m_qmlScreenSizeIndex = -1;
m_screenSizeModel->reset();
m_styleModel->setBackendModel(nullptr);
m_qmlStyleIndex = -1;
}
void QdsNewDialog::setProjectName(const QString &name)
{
m_qmlProjectName = name;
m_wizard.setProjectName(name);
}
void QdsNewDialog::setProjectLocation(const QString &location)
{
m_qmlProjectLocation = Utils::FilePath::fromString(QDir::toNativeSeparators(location));
m_wizard.setProjectLocation(m_qmlProjectLocation);
}
void QdsNewDialog::onStatusMessageChanged(Utils::InfoLabel::InfoType type, const QString &message)
{
switch (type) {
case Utils::InfoLabel::Warning:
m_qmlStatusType = "warning";
break;
case Utils::InfoLabel::Error:
m_qmlStatusType = "error";
break;
default:
m_qmlStatusType = "normal";
break;
}
emit statusTypeChanged();
m_qmlStatusMessage = message;
emit statusMessageChanged();
}
void QdsNewDialog::onProjectCanBeCreatedChanged(bool value)
{
if (m_qmlFieldsValid == value)
return;
m_qmlFieldsValid = value;
emit fieldsValidChanged();
}
void QdsNewDialog::onWizardCreated(QStandardItemModel *screenSizeModel, QStandardItemModel *styleModel)
{
m_screenSizeModel->setBackendModel(screenSizeModel);
m_styleModel->setBackendModel(styleModel);
if (m_qmlDetailsLoaded) {
m_screenSizeModel->reset();
emit haveVirtualKeyboardChanged();
emit haveTargetQtVersionChanged();
setProjectName(m_qmlProjectName);
setProjectLocation(m_qmlProjectLocation.toString());
}
if (m_qmlStylesLoaded)
m_styleModel->reset();
}
QString QdsNewDialog::currentProjectQmlPath() const
{
if (!m_currentProject || m_currentProject->qmlPath.isEmpty())
return "";
return m_currentProject->qmlPath.toString();
}
void QdsNewDialog::setScreenSizeIndex(int index)
{
m_wizard.setScreenSizeIndex(index);
m_qmlScreenSizeIndex = index;
}
void QdsNewDialog::setTargetQtVersion(int index)
{
m_wizard.setTargetQtVersionIndex(index);
m_qmlTargetQtVersionIndex = index;
}
void QdsNewDialog::setStyleIndex(int index)
{
if (!m_qmlStylesLoaded)
return;
if (index == -1) {
m_qmlStyleIndex = index;
return;
}
m_qmlStyleIndex = index;
int actualIndex = m_styleModel->actualIndex(m_qmlStyleIndex);
QTC_ASSERT(actualIndex >= 0, return);
m_wizard.setStyleIndex(actualIndex);
}
int QdsNewDialog::getStyleIndex() const
{
/**
* m_wizard.styleIndex property is the wizard's (backend's) value of the style index.
* The initial value (saved in the wizard.json) is read from there. Any subsequent reads of
* the style index should use m_styleIndex, which is the QML's style index property. Setting
* the style index should update both the m_styleIndex and the backend. In this regard, the
* QdsNewDialog's m_styleIndex acts as some kind of cache.
*/
if (!m_qmlStylesLoaded)
return -1;
if (m_qmlStyleIndex == -1) {
int actualIndex = m_wizard.styleIndex();
// Not nice, get sets the property... m_qmlStyleIndex acts like a cache.
m_qmlStyleIndex = m_styleModel->filteredIndex(actualIndex);
return m_qmlStyleIndex;
}
return m_styleModel->actualIndex(m_qmlStyleIndex);
}
void QdsNewDialog::setWizardFactories(QList<Core::IWizardFactory *> factories_,
const Utils::FilePath &defaultLocation,
const QVariantMap &)
{
Utils::Id platform = Utils::Id::fromSetting("Desktop");
WizardFactories factories{factories_, m_dialog, platform};
m_categoryModel->setProjects(factories.projectsGroupedByCategory()); // calls model reset
m_projectModel->setProjects(factories.projectsGroupedByCategory()); // calls model reset
if (m_qmlSelectedProject > -1)
setSelectedProject(m_qmlSelectedProject);
if (factories.empty())
return; // TODO: some message box?
const Core::IWizardFactory *first = factories.front();
Utils::FilePath projectLocation = first->runPath(defaultLocation);
m_qmlProjectName = uniqueProjectName(projectLocation.toString());
emit projectNameChanged(); // So that QML knows to update the field
m_qmlProjectLocation = Utils::FilePath::fromString(QDir::toNativeSeparators(projectLocation.toString()));
emit projectLocationChanged(); // So that QML knows to update the field
if (m_qmlDetailsLoaded)
m_screenSizeModel->reset();
if (m_qmlStylesLoaded)
m_styleModel->reset();
}
QString QdsNewDialog::qmlPath() const
{
return Core::ICore::resourcePath("qmldesigner/newprojectdialog/NewProjectDialog.qml").toString();
}
void QdsNewDialog::showDialog()
{
m_dialog->show();
}
bool QdsNewDialog::getHaveVirtualKeyboard() const
{
return m_wizard.haveVirtualKeyboard();
}
bool QdsNewDialog::getHaveTargetQtVersion() const
{
return m_wizard.haveTargetQtVersion();
}
void QdsNewDialog::accept()
{
CreateProject create{m_wizard};
create.withName(m_qmlProjectName)
.atLocation(m_qmlProjectLocation)
.withScreenSizes(m_qmlScreenSizeIndex, m_qmlCustomWidth, m_qmlCustomHeight)
.withStyle(m_qmlStyleIndex)
.useQtVirtualKeyboard(m_qmlUseVirtualKeyboard)
.saveAsDefaultLocation(m_qmlSaveAsDefaultLocation)
.withTargetQtVersion(m_qmlTargetQtVersionIndex)
.execute();
m_dialog->close();
}
void QdsNewDialog::reject()
{
m_screenSizeModel->setBackendModel(nullptr);
m_styleModel->setBackendModel(nullptr);
m_wizard.destroyWizard();
m_dialog->close();
}
QString QdsNewDialog::chooseProjectLocation()
{
Utils::FilePath newPath = Utils::FileUtils::getExistingDirectory(m_dialog, tr("Choose Directory"),
m_qmlProjectLocation);
return QDir::toNativeSeparators(newPath.toString());
}
void QdsNewDialog::setSelectedProject(int selection)
{
if (m_qmlSelectedProject != selection || m_projectPage != m_projectModel->page()) {
m_qmlSelectedProject = selection;
m_currentProject = m_projectModel->project(m_qmlSelectedProject);
if (m_currentProject) {
setProjectDescription(m_currentProject->description);
m_projectPage = m_projectModel->page();
m_wizard.reset(m_currentProject.value(), m_qmlSelectedProject, m_qmlProjectLocation);
}
}
}

View File

@@ -0,0 +1,170 @@
/****************************************************************************
**
** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include <QQuickWidget>
#include <coreplugin/dialogs/newdialog.h>
#include <utils/infolabel.h>
#include <utils/optional.h>
#include "wizardhandler.h"
#include "newprojectmodel.h"
#include "screensizemodel.h"
#include "stylemodel.h"
QT_BEGIN_NAMESPACE
class QStandardItemModel;
QT_END_NAMESPACE
namespace StudioWelcome {
class QdsNewDialog : public QObject, public Core::NewDialog
{
Q_OBJECT
public:
Q_PROPERTY(int selectedProject MEMBER m_qmlSelectedProject WRITE setSelectedProject)
Q_PROPERTY(QString projectName MEMBER m_qmlProjectName WRITE setProjectName NOTIFY projectNameChanged)
Q_PROPERTY(QString projectLocation MEMBER m_qmlProjectLocation READ projectLocation WRITE setProjectLocation NOTIFY projectLocationChanged)
Q_PROPERTY(QString projectDescription MEMBER m_qmlProjectDescription READ projectDescription WRITE setProjectDescription NOTIFY projectDescriptionChanged)
Q_PROPERTY(QString customWidth MEMBER m_qmlCustomWidth)
Q_PROPERTY(QString customHeight MEMBER m_qmlCustomHeight)
Q_PROPERTY(int styleIndex MEMBER m_qmlStyleIndex READ getStyleIndex WRITE setStyleIndex)
Q_PROPERTY(bool useVirtualKeyboard MEMBER m_qmlUseVirtualKeyboard READ getUseVirtualKeyboard WRITE setUseVirtualKeyboard NOTIFY useVirtualKeyboardChanged)
Q_PROPERTY(bool haveVirtualKeyboard MEMBER m_qmlHaveVirtualKeyboard READ getHaveVirtualKeyboard NOTIFY haveVirtualKeyboardChanged)
Q_PROPERTY(bool haveTargetQtVersion MEMBER m_qmlHaveTargetQtVersion READ getHaveTargetQtVersion NOTIFY haveTargetQtVersionChanged)
Q_PROPERTY(bool saveAsDefaultLocation MEMBER m_qmlSaveAsDefaultLocation WRITE setSaveAsDefaultLocation)
Q_PROPERTY(QString statusMessage MEMBER m_qmlStatusMessage READ getStatusMessage NOTIFY statusMessageChanged)
Q_PROPERTY(QString statusType MEMBER m_qmlStatusType READ getStatusType NOTIFY statusTypeChanged)
Q_PROPERTY(bool fieldsValid MEMBER m_qmlFieldsValid READ getFieldsValid NOTIFY fieldsValidChanged)
Q_PROPERTY(bool detailsLoaded MEMBER m_qmlDetailsLoaded)
Q_PROPERTY(bool stylesLoaded MEMBER m_qmlStylesLoaded)
Q_INVOKABLE QString currentProjectQmlPath() const;
Q_INVOKABLE void setScreenSizeIndex(int index); // called when ComboBox item is "activated"
Q_INVOKABLE void setTargetQtVersion(int index);
Q_INVOKABLE QString chooseProjectLocation();
explicit QdsNewDialog(QWidget *parent);
QWidget *widget() override { return m_dialog; }
void setWizardFactories(QList<Core::IWizardFactory *> factories, const Utils::FilePath &defaultLocation,
const QVariantMap &extraVariables) override;
void setWindowTitle(const QString &title) override { m_dialog->setWindowTitle(title); }
void showDialog() override;
void setSelectedProject(int selection);
void setStyleIndex(int index);
int getStyleIndex() const;
void setUseVirtualKeyboard(bool value) { m_qmlUseVirtualKeyboard = value; }
bool getUseVirtualKeyboard() const { return m_qmlUseVirtualKeyboard; }
bool getFieldsValid() const { return m_qmlFieldsValid; }
bool getHaveVirtualKeyboard() const;
bool getHaveTargetQtVersion() const;
void setSaveAsDefaultLocation(bool value) { m_qmlSaveAsDefaultLocation = value; }
QString getStatusMessage() const { return m_qmlStatusMessage; }
QString getStatusType() const { return m_qmlStatusType; }
public slots:
void accept();
void reject();
signals:
void projectNameChanged();
void projectLocationChanged();
void projectDescriptionChanged();
void useVirtualKeyboardChanged();
void haveVirtualKeyboardChanged();
void haveTargetQtVersionChanged();
void statusMessageChanged();
void statusTypeChanged();
void fieldsValidChanged();
private slots:
void onStatusMessageChanged(Utils::InfoLabel::InfoType type, const QString &message);
void onProjectCanBeCreatedChanged(bool value);
private:
QString qmlPath() const;
void setProjectName(const QString &name);
void setProjectLocation(const QString &location);
QString projectLocation() const { return m_qmlProjectLocation.toString(); }
void setProjectDescription(const QString &description)
{
m_qmlProjectDescription = description;
emit projectDescriptionChanged();
}
QString projectDescription() const { return m_qmlProjectDescription; }
private slots:
void onDeletingWizard();
void onWizardCreated(QStandardItemModel *screenSizeModel, QStandardItemModel *styleModel);
private:
QQuickWidget *m_dialog = nullptr;
QPointer<NewProjectCategoryModel> m_categoryModel;
QPointer<NewProjectModel> m_projectModel;
QPointer<ScreenSizeModel> m_screenSizeModel;
QPointer<StyleModel> m_styleModel;
QString m_qmlProjectName;
Utils::FilePath m_qmlProjectLocation;
QString m_qmlProjectDescription;
int m_qmlSelectedProject = -1;
int m_qmlScreenSizeIndex = -1;
int m_qmlTargetQtVersionIndex = -1;
// m_qmlStyleIndex is like a cache, so it needs to be updated on get()
mutable int m_qmlStyleIndex = -1;
bool m_qmlUseVirtualKeyboard = false;
bool m_qmlHaveVirtualKeyboard = false;
bool m_qmlHaveTargetQtVersion = false;
bool m_qmlSaveAsDefaultLocation = false;
bool m_qmlFieldsValid = false;
QString m_qmlStatusMessage;
QString m_qmlStatusType;
int m_projectPage = -1; // i.e. the page in the Presets View
QString m_qmlCustomWidth;
QString m_qmlCustomHeight;
bool m_qmlDetailsLoaded = false;
bool m_qmlStylesLoaded = false;
Utils::optional<ProjectItem> m_currentProject;
WizardHandler m_wizard;
};
} //namespace StudioWelcome

View File

@@ -0,0 +1,111 @@
/****************************************************************************
**
** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include <QAbstractListModel>
#include <QStandardItemModel>
#include <QRegularExpression>
class ScreenSizeModel : public QAbstractListModel
{
Q_OBJECT
public:
explicit ScreenSizeModel(QObject *parent = nullptr)
: QAbstractListModel(parent)
{}
Q_INVOKABLE QSize screenSizes(int index) const
{
constexpr auto invalid = QSize{0, 0};
if (!m_backendModel)
return invalid;
auto *item = m_backendModel->item(index, 0);
// Matches strings like "1024 x 768" or "1080 x 1920 (FullHD)"
QRegularExpression re{R"__(^(\d+)\s*x\s*(\d+).*)__"};
if (!item)
return invalid;
auto m = re.match(item->text());
if (!m.hasMatch())
return invalid;
bool ok = false;
int width = m.captured(1).toInt(&ok);
if (!ok)
return invalid;
int height = m.captured(2).toInt(&ok);
if (!ok)
return invalid;
return QSize{width, height};
}
int rowCount(const QModelIndex &parent = QModelIndex()) const override
{
if (m_backendModel)
return m_backendModel->rowCount();
return 0;
}
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override
{
if (m_backendModel) {
auto *item = m_backendModel->item(index.row(), index.column());
return item->text();
}
return "";
}
QHash<int, QByteArray> roleNames() const override
{
if (m_backendModel)
return m_backendModel->roleNames();
QHash<int, QByteArray> roleNames;
roleNames[Qt::UserRole] = "name";
return roleNames;
}
void reset() {
beginResetModel();
endResetModel();
}
void setBackendModel(QStandardItemModel *model)
{
m_backendModel = model;
}
private:
QStandardItemModel *m_backendModel = nullptr;
};

View File

@@ -12,10 +12,20 @@ DEFINES += STUDIO_QML_PATH=\\\"$$PWD/qml/\\\"
HEADERS += \
studiowelcome_global.h \
studiowelcomeplugin.h \
newprojectdialogimageprovider.h \
qdsnewdialog.h \
wizardfactories.h \
createproject.h \
newprojectmodel.h \
examplecheckout.h
SOURCES += \
studiowelcomeplugin.cpp \
qdsnewdialog.cpp \
wizardfactories.cpp \
createproject.cpp \
newprojectdialogimageprovider.cpp \
newprojectmodel.cpp \
examplecheckout.cpp
OTHER_FILES += \

View File

@@ -17,8 +17,18 @@ QtcPlugin {
"studiowelcome_global.h",
"studiowelcomeplugin.h",
"studiowelcomeplugin.cpp",
"qdsnewdialog.cpp",
"qdsnewdialog.h",
"wizardfactories.cpp",
"wizardfactories.h",
"createproject.cpp",
"createproject.h",
"newprojectmodel.cpp",
"newprojectmodel.h",
"examplecheckout.h",
"examplecheckout.cpp",
"newprojectdialogimageprovider.h",
"newprojectdialogimageprovider.cpp",
"studiowelcome.qrc",
]

View File

@@ -26,6 +26,8 @@
#include "studiowelcomeplugin.h"
#include "examplecheckout.h"
#include "qdsnewdialog.h"
#include <coreplugin/coreconstants.h>
#include <coreplugin/dialogs/restartdialog.h>
#include <coreplugin/editormanager/editormanager.h>
@@ -368,6 +370,9 @@ void StudioWelcomePlugin::extensionsInitialized()
s_view->setSource(QUrl("qrc:/qml/splashscreen/main.qml"));
#endif
// disabled by default
Core::ICore::setNewDialogFactory([](QWidget *parent) { return new QdsNewDialog(parent); });
QTC_ASSERT(s_view->rootObject(),
qWarning() << "The StudioWelcomePlugin has a runtime depdendency on "
"qt/qtquicktimeline.";

View File

@@ -0,0 +1,130 @@
/****************************************************************************
**
** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "stylemodel.h"
#include "utils/algorithm.h"
#include "utils/qtcassert.h"
#include <QRegularExpression>
StyleModel::StyleModel(QObject *parent)
: QAbstractListModel(parent)
, m_backendModel(nullptr)
{}
QString StyleModel::iconId(int index) const
{
if (!m_backendModel || index < 0)
return "style-error";
auto item = this->m_filteredItems.at(index);
QString styleName = item->text();
QString id{"style-"};
id += styleName.toLower().replace(' ', '_') + ".png";
return id;
}
void StyleModel::filter(const QString &what)
{
if (what.toLower() == "all")
m_filteredItems = this->filterItems(m_items, "");
else if (what.toLower() == "light")
m_filteredItems = this->filterItems(m_items, "light");
else if (what.toLower() == "dark")
m_filteredItems = this->filterItems(m_items, "dark");
else
m_filteredItems.clear();
reset();
}
StyleModel::Items StyleModel::filterItems(const Items &items, const QString &kind)
{
if (kind.isEmpty())
return items;
return Utils::filtered(items, [&kind](auto *item) {
QString pattern{"\\S "};
pattern += kind;
QRegularExpression re{pattern, QRegularExpression::CaseInsensitiveOption};
return re.match(item->text()).hasMatch();
});
}
int StyleModel::filteredIndex(int actualIndex)
{
if (actualIndex < 0)
return actualIndex;
QStandardItem *item = m_items.at(actualIndex);
// TODO: perhaps should add this kind of find to utils/algorithm.h
auto it = std::find(std::cbegin(m_filteredItems), std::cend(m_filteredItems), item);
if (it == std::cend(m_filteredItems))
return -1;
return std::distance(std::cbegin(m_filteredItems), it);
}
int StyleModel::actualIndex(int filteredIndex)
{
if (filteredIndex < 0)
return filteredIndex;
QTC_ASSERT(filteredIndex < static_cast<int>(m_filteredItems.size()), return -1);
QStandardItem *item = m_filteredItems.at(filteredIndex);
auto it = std::find(std::cbegin(m_items), std::cend(m_items), item);
if (it == std::cend(m_items))
return -1;
auto result = std::distance(std::cbegin(m_items), it);
QTC_ASSERT(result >= 0, return -1);
QTC_ASSERT(result <= static_cast<int>(m_items.size()), return -1);
return result;
}
void StyleModel::setBackendModel(QStandardItemModel *model)
{
m_backendModel = model;
if (m_backendModel) {
m_count = model->rowCount();
m_roles = model->roleNames();
m_items.clear();
for (int i = 0; i < m_count; ++i)
m_items.push_back(model->item(i, 0));
m_filteredItems = filterItems(m_items, "");
} else {
m_count = 0;
m_items.clear();
m_filteredItems.clear();
}
}

View File

@@ -0,0 +1,96 @@
/****************************************************************************
**
** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include <QAbstractListModel>
#include <QStandardItemModel>
class StyleModel : public QAbstractListModel
{
Q_OBJECT
private:
using Items = std::vector<QStandardItem *>;
public:
explicit StyleModel(QObject *parent = nullptr);
Q_INVOKABLE QString iconId(int index) const;
Q_INVOKABLE void filter(const QString &what = "all");
int rowCount(const QModelIndex &parent = QModelIndex()) const override
{
if (m_backendModel)
return static_cast<int>(m_filteredItems.size());
return 0;
}
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override
{
if (m_backendModel) {
auto *item = m_filteredItems.at(index.row());
return item->text();
}
return "";
}
QHash<int, QByteArray> roleNames() const override
{
if (m_backendModel)
return m_roles;
/**
* TODO: roleNames is called before the backend model is loaded, which may be buggy. But I
* found no way to force refresh the model, so as to reload the role names afterwards.
*/
QHash<int, QByteArray> roleNames;
roleNames[Qt::UserRole] = "display";
return roleNames;
}
void reset()
{
beginResetModel();
endResetModel();
}
int filteredIndex(int actualIndex);
int actualIndex(int filteredIndex);
void setBackendModel(QStandardItemModel *model);
private:
static Items filterItems(const Items &items, const QString &kind);
private:
QStandardItemModel *m_backendModel;
Items m_items, m_filteredItems;
int m_count = -1;
QHash<int, QByteArray> m_roles;
};

View File

@@ -0,0 +1,113 @@
/****************************************************************************
**
** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include <coreplugin/icore.h>
#include <coreplugin/iwizardfactory.h>
#include <utils/algorithm.h>
#include "wizardfactories.h"
namespace {
// TODO: should be extern, check coreplugin/dialogs/newdialogwidget.cpp
const char BLACKLISTED_CATEGORIES_KEY[] = "Core/NewDialog/BlacklistedCategories";
}
using namespace StudioWelcome;
WizardFactories::WizardFactories(QList<Core::IWizardFactory *> &factories, QWidget *wizardParent, const Utils::Id &platform)
: m_wizardParent{wizardParent}
, m_platform{platform}
, m_factories{factories}
{
QVariant value = Core::ICore::settings()->value(BLACKLISTED_CATEGORIES_KEY);
m_blacklist = Utils::Id::fromStringList(value.toStringList());
sortByCategoryAndId();
filter();
m_projectItems = makeProjectItemsGroupedByCategory();
}
void WizardFactories::sortByCategoryAndId()
{
Utils::sort(m_factories, [](Core::IWizardFactory *lhs, Core::IWizardFactory *rhs) {
if (lhs->category() == rhs->category())
return lhs->id().toString() < rhs->id().toString();
else
return lhs->category() < rhs->category();
});
}
void WizardFactories::filter()
{
QList<Core::IWizardFactory *> acceptedFactories;
// TODO: perhaps I could use Utils::filtered here.
std::copy_if(std::begin(m_factories), std::end(m_factories), std::back_inserter(acceptedFactories),
[&](auto *wizard) {
return wizard->isAvailable(m_platform)
&& wizard->kind() == Core::IWizardFactory::ProjectWizard
&& !m_blacklist.contains(Utils::Id::fromString(wizard->category()));
});
m_factories = acceptedFactories;
}
ProjectItem WizardFactories::makeProjectItem(Core::IWizardFactory *f, QWidget *parent,
const Utils::Id &platform)
{
using namespace std::placeholders;
return {
/*.name =*/f->displayName(),
/*.categoryId =*/f->category(),
/*. description =*/f->description(),
/*.qmlPath =*/f->detailsPageQmlPath(),
/*.fontIconCode =*/f->fontIcondCode(),
/*.create =*/ std::bind(&Core::IWizardFactory::runWizard, f, _1, parent, platform,
QVariantMap(), false),
};
}
std::map<QString, ProjectCategory> WizardFactories::makeProjectItemsGroupedByCategory()
{
QMap<QString, ProjectCategory> categories;
for (auto *f : std::as_const(m_factories)) {
if (!categories.contains(f->category())) {
categories[f->category()] = {
/*.id =*/ f->category(),
/*.name =*/ f->displayCategory(),
/*.items = */
{
makeProjectItem(f, m_wizardParent, m_platform),
},
};
} else {
auto projectItem = makeProjectItem(f, m_wizardParent, m_platform);
categories[f->category()].items.push_back(projectItem);
}
}
return categories.toStdMap();
}

View File

@@ -0,0 +1,66 @@
/****************************************************************************
**
** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include "newprojectmodel.h"
#include <utils/id.h>
namespace Core {
class IWizardFactory;
}
namespace StudioWelcome {
class WizardFactories
{
public:
WizardFactories(QList<Core::IWizardFactory *> &factories, QWidget *wizardParent,
const Utils::Id &platform);
const Core::IWizardFactory *front() const { return m_factories.front(); }
const std::map<QString, ProjectCategory> &projectsGroupedByCategory() const
{ return m_projectItems; }
bool empty() const { return m_factories.empty(); }
private:
void sortByCategoryAndId();
void filter();
ProjectItem makeProjectItem(Core::IWizardFactory *f, QWidget *parent, const Utils::Id &platform);
std::map<QString, ProjectCategory> makeProjectItemsGroupedByCategory();
private:
QSet<Utils::Id> m_blacklist;
QWidget *m_wizardParent;
Utils::Id m_platform;
QList<Core::IWizardFactory *> m_factories;
std::map<QString, ProjectCategory> m_projectItems;
};
} // namespace StudioWelcome

View File

@@ -0,0 +1,249 @@
/****************************************************************************
**
** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include <QWizardPage>
#include <QMessageBox>
#include "wizardhandler.h"
#include <projectexplorer/jsonwizard/jsonfieldpage.h>
#include <projectexplorer/jsonwizard/jsonfieldpage_p.h>
#include <projectexplorer/jsonwizard/jsonprojectpage.h>
#include "utils/wizard.h"
#include <utils/qtcassert.h>
using namespace StudioWelcome;
void WizardHandler::reset(const ProjectItem &projectInfo, int projectSelection, const Utils::FilePath &location)
{
m_projectItem = projectInfo;
m_projectLocation = location;
m_selectedProject = projectSelection;
if (!m_wizard) {
setupWizard();
} else {
QObject::connect(m_wizard, &QObject::destroyed, this, &WizardHandler::onWizardResetting);
// DON'T SET `m_selectedProject = -1` --- we are switching now to a separate project.
emit deletingWizard();
m_wizard->deleteLater();
}
}
void WizardHandler::destroyWizard()
{
emit deletingWizard();
m_selectedProject = -1;
m_wizard->deleteLater();
m_wizard = nullptr;
}
void WizardHandler::setupWizard()
{
m_wizard = m_projectItem.create(m_projectLocation);
if (!m_wizard) {
emit wizardCreationFailed();
return;
}
initializeProjectPage(m_wizard->page(0));
initializeFieldsPage(m_wizard->page(1));
auto *screenFactorModel = getScreenFactorModel(m_detailsPage);
auto *styleModel = getStyleModel(m_detailsPage);
emit wizardCreated(screenFactorModel, styleModel);
}
void WizardHandler::setProjectName(const QString &name)
{
QTC_ASSERT(m_wizard, return);
QWizardPage *projectPage = m_wizard->page(0);
auto *jpp = dynamic_cast<ProjectExplorer::JsonProjectPage *>(projectPage);
QTC_ASSERT(jpp, return);
jpp->setProjectName(name);
}
void WizardHandler::setProjectLocation(const Utils::FilePath &location)
{
QTC_ASSERT(m_wizard, return);
QWizardPage *projectPage = m_wizard->page(0);
auto *jpp = dynamic_cast<ProjectExplorer::JsonProjectPage *>(projectPage);
QTC_ASSERT(jpp, return);
jpp->setFilePath(location);
}
void WizardHandler::initializeProjectPage(QWizardPage *page)
{
auto *jpp = dynamic_cast<ProjectExplorer::JsonProjectPage *>(page);
QTC_ASSERT(jpp, return);
QObject::connect(jpp, &ProjectExplorer::JsonProjectPage::statusMessageChanged, this, &WizardHandler::statusMessageChanged);
QObject::connect(jpp, &ProjectExplorer::JsonProjectPage::completeChanged, this, &WizardHandler::onProjectIntroCompleteChanged);
}
void WizardHandler::initializeFieldsPage(QWizardPage *page)
{
auto fieldsPage = dynamic_cast<ProjectExplorer::JsonFieldPage *>(page); // required for page->jsonField
QTC_ASSERT(fieldsPage, return);
m_detailsPage = fieldsPage;
fieldsPage->initializePage();
}
void WizardHandler::onProjectIntroCompleteChanged()
{
auto *page = dynamic_cast<ProjectExplorer::JsonProjectPage *>(QObject::sender());
QTC_ASSERT(page, return);
emit projectCanBeCreated(page->isComplete());
}
QStandardItemModel *WizardHandler::getScreenFactorModel(ProjectExplorer::JsonFieldPage *page)
{
auto *field = page->jsonField("ScreenFactor");
if (!field)
return nullptr;
auto *cbfield = dynamic_cast<ProjectExplorer::ComboBoxField *>(field);
QTC_ASSERT(cbfield, return nullptr);
return cbfield->model();
}
QStandardItemModel *WizardHandler::getStyleModel(ProjectExplorer::JsonFieldPage *page)
{
auto *field = page->jsonField("ControlsStyle");
if (!field)
return nullptr;
auto *cbfield = dynamic_cast<ProjectExplorer::ComboBoxField*>(field);
QTC_ASSERT(cbfield, return nullptr);
return cbfield->model();
}
void WizardHandler::onWizardResetting()
{
m_wizard = nullptr;
// if have a wizard request pending => create new wizard
// note: we always have a wizard request pending here, unless the dialogbox was requested to be destroyed.
// if m_selectedProject != -1 => the wizard was destroyed as a result of reset to a different project type
if (m_selectedProject > -1)
setupWizard();
}
void WizardHandler::setScreenSizeIndex(int index)
{
auto *field = m_detailsPage->jsonField("ScreenFactor");
auto *cbfield = dynamic_cast<ProjectExplorer::ComboBoxField *>(field);
QTC_ASSERT(cbfield, return);
cbfield->selectRow(index);
}
void WizardHandler::setTargetQtVersionIndex(int index)
{
auto *field = m_detailsPage->jsonField("TargetQtVersion");
auto *cbfield = dynamic_cast<ProjectExplorer::ComboBoxField *>(field);
QTC_ASSERT(cbfield, return);
cbfield->selectRow(index);
}
bool WizardHandler::haveTargetQtVersion() const
{
return m_wizard->hasField("TargetQtVersion");
}
void WizardHandler::setStyleIndex(int index)
{
auto *field = m_detailsPage->jsonField("ControlsStyle");
auto *cbfield = dynamic_cast<ProjectExplorer::ComboBoxField *>(field);
QTC_ASSERT(cbfield, return);
cbfield->selectRow(index);
}
int WizardHandler::styleIndex() const
{
auto *field = m_detailsPage->jsonField("ControlsStyle");
auto *cbfield = dynamic_cast<ProjectExplorer::ComboBoxField *>(field);
QTC_ASSERT(cbfield, return -1);
return cbfield->selectedRow();
}
void WizardHandler::setUseVirtualKeyboard(bool value)
{
auto *field = m_detailsPage->jsonField("UseVirtualKeyboard");
auto *cbfield = dynamic_cast<ProjectExplorer::CheckBoxField *>(field);
QTC_ASSERT(cbfield, return);
cbfield->setChecked(value);
}
bool WizardHandler::haveVirtualKeyboard() const
{
return m_wizard->hasField("UseVirtualKeyboard");
}
void WizardHandler::run(const std::function<void(QWizardPage *)> &processPage)
{
m_wizard->restart();
int nextId = 0;
do {
QWizardPage *page = m_wizard->currentPage();
QTC_ASSERT(page, return);
processPage(page);
if (!page->validatePage() || !page->isComplete()) {
QMessageBox::warning(m_wizard, "New project", "Could not create the project because fields are invalid");
return;
}
nextId = m_wizard->nextId();
m_wizard->next();
} while (-1 != nextId);
m_selectedProject = -1;
// Note: don't call `emit deletingWizard()` here.
// Note: QWizard::accept calls QObject::deleteLater on the wizard
m_wizard->accept();
}

View File

@@ -0,0 +1,96 @@
/****************************************************************************
**
** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include "newprojectmodel.h"
#include <utils/filepath.h>
#include <utils/infolabel.h>
QT_BEGIN_NAMESPACE
class QWizard;
class QWizardPage;
class QStandardItemModel;
QT_END_NAMESPACE
namespace ProjectExplorer {
class JsonFieldPage;
}
namespace StudioWelcome {
class WizardHandler: public QObject
{
Q_OBJECT
public:
//TODO: location should not be needed in reset() -- only when creating the project
void reset(const ProjectItem &projectInfo, int projectSelection, const Utils::FilePath &location);
void setScreenSizeIndex(int index);
void setTargetQtVersionIndex(int index);
bool haveTargetQtVersion() const;
void setStyleIndex(int index);
int styleIndex() const;
void destroyWizard();
void setUseVirtualKeyboard(bool value);
bool haveVirtualKeyboard() const;
void setProjectName(const QString &name);
void setProjectLocation(const Utils::FilePath &location);
void run(const std::function<void (QWizardPage *)> &processPage);
signals:
void deletingWizard();
void wizardCreated(QStandardItemModel *screenSizeModel, QStandardItemModel *styleModel);
void statusMessageChanged(Utils::InfoLabel::InfoType type, const QString &message);
void projectCanBeCreated(bool value);
void wizardCreationFailed();
private:
void setupWizard();
void initializeProjectPage(QWizardPage *page);
void initializeFieldsPage(QWizardPage *page);
QStandardItemModel *getScreenFactorModel(ProjectExplorer::JsonFieldPage *page);
QStandardItemModel *getStyleModel(ProjectExplorer::JsonFieldPage *page);
private slots:
void onWizardResetting();
void onProjectIntroCompleteChanged();
private:
Utils::Wizard *m_wizard = nullptr;
ProjectExplorer::JsonFieldPage *m_detailsPage = nullptr;
int m_selectedProject = -1;
ProjectItem m_projectItem;
Utils::FilePath m_projectLocation;
};
} // StudioWelcome