diff --git a/share/qtcreator/qmldesigner/newprojectdialog/NewProjectDialog.qml b/share/qtcreator/qmldesigner/newprojectdialog/NewProjectDialog.qml new file mode 100644 index 00000000000..86234bf5bae --- /dev/null +++ b/share/qtcreator/qmldesigner/newprojectdialog/NewProjectDialog.qml @@ -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 +} diff --git a/share/qtcreator/qmldesigner/newprojectdialog/image/style-basic.png b/share/qtcreator/qmldesigner/newprojectdialog/image/style-basic.png new file mode 100644 index 00000000000..53d03e476b4 Binary files /dev/null and b/share/qtcreator/qmldesigner/newprojectdialog/image/style-basic.png differ diff --git a/share/qtcreator/qmldesigner/newprojectdialog/image/style-default.png b/share/qtcreator/qmldesigner/newprojectdialog/image/style-default.png new file mode 100644 index 00000000000..c3f05108f9e Binary files /dev/null and b/share/qtcreator/qmldesigner/newprojectdialog/image/style-default.png differ diff --git a/share/qtcreator/qmldesigner/newprojectdialog/image/style-error.png b/share/qtcreator/qmldesigner/newprojectdialog/image/style-error.png new file mode 100644 index 00000000000..aeb03a574ed Binary files /dev/null and b/share/qtcreator/qmldesigner/newprojectdialog/image/style-error.png differ diff --git a/share/qtcreator/qmldesigner/newprojectdialog/image/style-fusion.png b/share/qtcreator/qmldesigner/newprojectdialog/image/style-fusion.png new file mode 100644 index 00000000000..882256b30d4 Binary files /dev/null and b/share/qtcreator/qmldesigner/newprojectdialog/image/style-fusion.png differ diff --git a/share/qtcreator/qmldesigner/newprojectdialog/image/style-imagine.png b/share/qtcreator/qmldesigner/newprojectdialog/image/style-imagine.png new file mode 100644 index 00000000000..47d66317b28 Binary files /dev/null and b/share/qtcreator/qmldesigner/newprojectdialog/image/style-imagine.png differ diff --git a/share/qtcreator/qmldesigner/newprojectdialog/image/style-macos.png b/share/qtcreator/qmldesigner/newprojectdialog/image/style-macos.png new file mode 100644 index 00000000000..323f4d51b16 Binary files /dev/null and b/share/qtcreator/qmldesigner/newprojectdialog/image/style-macos.png differ diff --git a/share/qtcreator/qmldesigner/newprojectdialog/image/style-material_dark.png b/share/qtcreator/qmldesigner/newprojectdialog/image/style-material_dark.png new file mode 100644 index 00000000000..53247a422fd Binary files /dev/null and b/share/qtcreator/qmldesigner/newprojectdialog/image/style-material_dark.png differ diff --git a/share/qtcreator/qmldesigner/newprojectdialog/image/style-material_light.png b/share/qtcreator/qmldesigner/newprojectdialog/image/style-material_light.png new file mode 100644 index 00000000000..63c6b7b79f0 Binary files /dev/null and b/share/qtcreator/qmldesigner/newprojectdialog/image/style-material_light.png differ diff --git a/share/qtcreator/qmldesigner/newprojectdialog/image/style-universal_dark.png b/share/qtcreator/qmldesigner/newprojectdialog/image/style-universal_dark.png new file mode 100644 index 00000000000..fc21300336e Binary files /dev/null and b/share/qtcreator/qmldesigner/newprojectdialog/image/style-universal_dark.png differ diff --git a/share/qtcreator/qmldesigner/newprojectdialog/image/style-universal_light.png b/share/qtcreator/qmldesigner/newprojectdialog/image/style-universal_light.png new file mode 100644 index 00000000000..735219f0bb6 Binary files /dev/null and b/share/qtcreator/qmldesigner/newprojectdialog/image/style-universal_light.png differ diff --git a/share/qtcreator/qmldesigner/newprojectdialog/image/style-universal_system.png b/share/qtcreator/qmldesigner/newprojectdialog/image/style-universal_system.png new file mode 100644 index 00000000000..fc21300336e Binary files /dev/null and b/share/qtcreator/qmldesigner/newprojectdialog/image/style-universal_system.png differ diff --git a/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/Details.qml b/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/Details.qml new file mode 100644 index 00000000000..c7ed3ec8d64 --- /dev/null +++ b/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/Details.qml @@ -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 + } +} diff --git a/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/DialogValues.qml b/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/DialogValues.qml new file mode 100644 index 00000000000..5bcfadaa785 --- /dev/null +++ b/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/DialogValues.qml @@ -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 + } +} diff --git a/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/NewProjectView.qml b/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/NewProjectView.qml new file mode 100644 index 00000000000..049b955420c --- /dev/null +++ b/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/NewProjectView.qml @@ -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 diff --git a/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/Styles.qml b/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/Styles.qml new file mode 100644 index 00000000000..a1ef6696f3a --- /dev/null +++ b/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/Styles.qml @@ -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 +} diff --git a/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/qmldir b/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/qmldir new file mode 100644 index 00000000000..d7c1562164f --- /dev/null +++ b/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/qmldir @@ -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 diff --git a/share/qtcreator/qmldesigner/newprojectdialog/imports/ProjectType/DefaultProject.qml b/share/qtcreator/qmldesigner/newprojectdialog/imports/ProjectType/DefaultProject.qml new file mode 100644 index 00000000000..05bc5fff042 --- /dev/null +++ b/share/qtcreator/qmldesigner/newprojectdialog/imports/ProjectType/DefaultProject.qml @@ -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 + } + } +} diff --git a/share/qtcreator/qmldesigner/newprojectdialog/imports/ProjectType/qmldir b/share/qtcreator/qmldesigner/newprojectdialog/imports/ProjectType/qmldir new file mode 100644 index 00000000000..6f3c69e9d42 --- /dev/null +++ b/share/qtcreator/qmldesigner/newprojectdialog/imports/ProjectType/qmldir @@ -0,0 +1 @@ +DefaultProject 1.0 DefaultProject.qml diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/Section.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/Section.qml index 23ee5615700..a6dbcfe11da 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/Section.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/Section.qml @@ -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 diff --git a/src/plugins/CMakeLists.txt b/src/plugins/CMakeLists.txt index e61b8ab4338..800cd3b775d 100644 --- a/src/plugins/CMakeLists.txt +++ b/src/plugins/CMakeLists.txt @@ -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) diff --git a/src/plugins/coreplugin/icore.cpp b/src/plugins/coreplugin/icore.cpp index c118533426a..82a715f59e2 100644 --- a/src/plugins/coreplugin/icore.cpp +++ b/src/plugins/coreplugin/icore.cpp @@ -32,6 +32,7 @@ #include #include +#include #include #include @@ -165,9 +166,13 @@ namespace Core { // The Core Singleton static ICore *m_instance = nullptr; static MainWindow *m_mainwindow = nullptr; -std::function ICore::m_newDialogFactory = [](QWidget *parent) { + +static NewDialog *defaultDialogFactory(QWidget *parent) +{ return new NewDialogWidget(parent); -}; +} + +static std::function 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); diff --git a/src/plugins/coreplugin/icore.h b/src/plugins/coreplugin/icore.h index 589cf49765e..93cf5c71f17 100644 --- a/src/plugins/coreplugin/icore.h +++ b/src/plugins/coreplugin/icore.h @@ -180,8 +180,6 @@ public: private: static void updateNewItemDialogState(); - - static std::function m_newDialogFactory; }; } // namespace Core diff --git a/src/plugins/coreplugin/mainwindow.cpp b/src/plugins/coreplugin/mainwindow.cpp index 59ba9b08abc..83d5a257a57 100644 --- a/src/plugins/coreplugin/mainwindow.cpp +++ b/src/plugins/coreplugin/mainwindow.cpp @@ -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); diff --git a/src/plugins/projectexplorer/jsonwizard/jsonprojectpage.h b/src/plugins/projectexplorer/jsonwizard/jsonprojectpage.h index b362b988945..ced13950aac 100644 --- a/src/plugins/projectexplorer/jsonwizard/jsonprojectpage.h +++ b/src/plugins/projectexplorer/jsonwizard/jsonprojectpage.h @@ -25,12 +25,13 @@ #pragma once +#include "../projectexplorer_export.h" #include namespace ProjectExplorer { // Documentation inside. -class JsonProjectPage : public Utils::ProjectIntroPage +class PROJECTEXPLORER_EXPORT JsonProjectPage : public Utils::ProjectIntroPage { Q_OBJECT diff --git a/src/plugins/studiowelcome/CMakeLists.txt b/src/plugins/studiowelcome/CMakeLists.txt index 2ae3bdcc9a0..3d290d6486b 100644 --- a/src/plugins/studiowelcome/CMakeLists.txt +++ b/src/plugins/studiowelcome/CMakeLists.txt @@ -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 diff --git a/src/plugins/studiowelcome/createproject.cpp b/src/plugins/studiowelcome/createproject.cpp new file mode 100644 index 00000000000..1b6dd93e9d7 --- /dev/null +++ b/src/plugins/studiowelcome/createproject.cpp @@ -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 + +#include +#include + +using namespace StudioWelcome; + +void CreateProject::execute() +{ + m_wizard.run([&](QWizardPage *page) { + if (auto *p = dynamic_cast(page)) + processProjectPage(p); + else if (auto *p = dynamic_cast(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(page->jsonField("CustomScreenWidth")); + auto heightField = dynamic_cast(page->jsonField("CustomScreenHeight")); + + if (widthField && heightField) { + if (!m_customWidth.isEmpty() && !m_customHeight.isEmpty()) { + widthField->setText(m_customWidth); + heightField->setText(m_customHeight); + } + } +} diff --git a/src/plugins/studiowelcome/createproject.h b/src/plugins/studiowelcome/createproject.h new file mode 100644 index 00000000000..9709bbee30e --- /dev/null +++ b/src/plugins/studiowelcome/createproject.h @@ -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 diff --git a/src/plugins/studiowelcome/newprojectdialogimageprovider.cpp b/src/plugins/studiowelcome/newprojectdialogimageprovider.cpp new file mode 100644 index 00000000000..2c7e454f5b0 --- /dev/null +++ b/src/plugins/studiowelcome/newprojectdialogimageprovider.cpp @@ -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 +#include +#include + +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 + diff --git a/src/plugins/studiowelcome/newprojectdialogimageprovider.h b/src/plugins/studiowelcome/newprojectdialogimageprovider.h new file mode 100644 index 00000000000..f149d082c21 --- /dev/null +++ b/src/plugins/studiowelcome/newprojectdialogimageprovider.h @@ -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 + +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 diff --git a/src/plugins/studiowelcome/newprojectmodel.cpp b/src/plugins/studiowelcome/newprojectmodel.cpp new file mode 100644 index 00000000000..07589a4c041 --- /dev/null +++ b/src/plugins/studiowelcome/newprojectmodel.cpp @@ -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 BaseNewProjectModel::roleNames() const +{ + QHash 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(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(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(index); + + endResetModel(); +} + +QString NewProjectModel::fontIconCode(int index) const +{ + Utils::optional projectItem = project(index); + if (!projectItem) + return ""; + + return projectItem->fontIconCode; +} diff --git a/src/plugins/studiowelcome/newprojectmodel.h b/src/plugins/studiowelcome/newprojectmodel.h new file mode 100644 index 00000000000..9e6cdd157b9 --- /dev/null +++ b/src/plugins/studiowelcome/newprojectmodel.h @@ -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 +#include + +#include +#include + +namespace Utils { +class Wizard; +} + +namespace StudioWelcome { + +struct ProjectItem +{ + QString name; + QString categoryId; + QString description; + QUrl qmlPath; + QString fontIconCode; + std::function 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 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; + +/****************** BaseNewProjectModel ******************/ + +class BaseNewProjectModel : public QAbstractListModel +{ + using ProjectItems = std::vector>; + using Categories = std::vector; + +public: + explicit BaseNewProjectModel(QObject *parent = nullptr); + QHash 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(m_page); } + + Utils::optional project(size_t selection) const + { + if (projects().empty()) + return {}; + + if (m_page < projects().size()) { + const std::vector 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 projectsOfCurrentCategory() const + { return projects().at(m_page); } + +private: + size_t m_page = 0; +}; + +} // namespace StudioWelcome diff --git a/src/plugins/studiowelcome/qdsnewdialog.cpp b/src/plugins/studiowelcome/qdsnewdialog.cpp new file mode 100644 index 00000000000..b031a083bcf --- /dev/null +++ b/src/plugins/studiowelcome/qdsnewdialog.cpp @@ -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 +#include + +#include "qdsnewdialog.h" + +#include +#include +#include +#include + +#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{ + {{"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 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); + } + } +} diff --git a/src/plugins/studiowelcome/qdsnewdialog.h b/src/plugins/studiowelcome/qdsnewdialog.h new file mode 100644 index 00000000000..ede50bc97a7 --- /dev/null +++ b/src/plugins/studiowelcome/qdsnewdialog.h @@ -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 + +#include +#include +#include + +#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 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 m_categoryModel; + QPointer m_projectModel; + QPointer m_screenSizeModel; + QPointer 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 m_currentProject; + + WizardHandler m_wizard; +}; + +} //namespace StudioWelcome diff --git a/src/plugins/studiowelcome/screensizemodel.h b/src/plugins/studiowelcome/screensizemodel.h new file mode 100644 index 00000000000..e01f17295c5 --- /dev/null +++ b/src/plugins/studiowelcome/screensizemodel.h @@ -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 +#include +#include + +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 roleNames() const override + { + if (m_backendModel) + return m_backendModel->roleNames(); + + QHash roleNames; + roleNames[Qt::UserRole] = "name"; + return roleNames; + } + + void reset() { + beginResetModel(); + endResetModel(); + } + + void setBackendModel(QStandardItemModel *model) + { + m_backendModel = model; + } + +private: + QStandardItemModel *m_backendModel = nullptr; +}; + diff --git a/src/plugins/studiowelcome/studiowelcome.pro b/src/plugins/studiowelcome/studiowelcome.pro index e2ccee8576f..3cdee86f789 100644 --- a/src/plugins/studiowelcome/studiowelcome.pro +++ b/src/plugins/studiowelcome/studiowelcome.pro @@ -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 += \ diff --git a/src/plugins/studiowelcome/studiowelcome.qbs b/src/plugins/studiowelcome/studiowelcome.qbs index f2bec88af22..c630d83f01c 100644 --- a/src/plugins/studiowelcome/studiowelcome.qbs +++ b/src/plugins/studiowelcome/studiowelcome.qbs @@ -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", ] diff --git a/src/plugins/studiowelcome/studiowelcomeplugin.cpp b/src/plugins/studiowelcome/studiowelcomeplugin.cpp index d1195675624..56ef7302842 100644 --- a/src/plugins/studiowelcome/studiowelcomeplugin.cpp +++ b/src/plugins/studiowelcome/studiowelcomeplugin.cpp @@ -26,6 +26,8 @@ #include "studiowelcomeplugin.h" #include "examplecheckout.h" +#include "qdsnewdialog.h" + #include #include #include @@ -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."; diff --git a/src/plugins/studiowelcome/stylemodel.cpp b/src/plugins/studiowelcome/stylemodel.cpp new file mode 100644 index 00000000000..9aee4ff7d32 --- /dev/null +++ b/src/plugins/studiowelcome/stylemodel.cpp @@ -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 + +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(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(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(); + } +} diff --git a/src/plugins/studiowelcome/stylemodel.h b/src/plugins/studiowelcome/stylemodel.h new file mode 100644 index 00000000000..6d6d09620d1 --- /dev/null +++ b/src/plugins/studiowelcome/stylemodel.h @@ -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 +#include + +class StyleModel : public QAbstractListModel +{ + Q_OBJECT + +private: + using Items = std::vector; + +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(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 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 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 m_roles; +}; + diff --git a/src/plugins/studiowelcome/wizardfactories.cpp b/src/plugins/studiowelcome/wizardfactories.cpp new file mode 100644 index 00000000000..a7cb20b0c62 --- /dev/null +++ b/src/plugins/studiowelcome/wizardfactories.cpp @@ -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 +#include +#include + +#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 &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 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 WizardFactories::makeProjectItemsGroupedByCategory() +{ + QMap 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(); +} diff --git a/src/plugins/studiowelcome/wizardfactories.h b/src/plugins/studiowelcome/wizardfactories.h new file mode 100644 index 00000000000..c41300ff091 --- /dev/null +++ b/src/plugins/studiowelcome/wizardfactories.h @@ -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 + +namespace Core { +class IWizardFactory; +} + +namespace StudioWelcome { + +class WizardFactories +{ +public: + WizardFactories(QList &factories, QWidget *wizardParent, + const Utils::Id &platform); + + const Core::IWizardFactory *front() const { return m_factories.front(); } + const std::map &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 makeProjectItemsGroupedByCategory(); + +private: + QSet m_blacklist; + QWidget *m_wizardParent; + Utils::Id m_platform; + + QList m_factories; + std::map m_projectItems; +}; + +} // namespace StudioWelcome diff --git a/src/plugins/studiowelcome/wizardhandler.cpp b/src/plugins/studiowelcome/wizardhandler.cpp new file mode 100644 index 00000000000..fce7aa120f9 --- /dev/null +++ b/src/plugins/studiowelcome/wizardhandler.cpp @@ -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 +#include + +#include "wizardhandler.h" + +#include +#include + +#include + +#include "utils/wizard.h" +#include + +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(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(projectPage); + QTC_ASSERT(jpp, return); + + jpp->setFilePath(location); +} + +void WizardHandler::initializeProjectPage(QWizardPage *page) +{ + auto *jpp = dynamic_cast(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(page); // required for page->jsonField + QTC_ASSERT(fieldsPage, return); + m_detailsPage = fieldsPage; + + fieldsPage->initializePage(); +} + +void WizardHandler::onProjectIntroCompleteChanged() +{ + auto *page = dynamic_cast(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(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(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(field); + QTC_ASSERT(cbfield, return); + + cbfield->selectRow(index); +} + +void WizardHandler::setTargetQtVersionIndex(int index) +{ + auto *field = m_detailsPage->jsonField("TargetQtVersion"); + auto *cbfield = dynamic_cast(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(field); + QTC_ASSERT(cbfield, return); + + cbfield->selectRow(index); +} + +int WizardHandler::styleIndex() const +{ + auto *field = m_detailsPage->jsonField("ControlsStyle"); + auto *cbfield = dynamic_cast(field); + QTC_ASSERT(cbfield, return -1); + + return cbfield->selectedRow(); +} + +void WizardHandler::setUseVirtualKeyboard(bool value) +{ + auto *field = m_detailsPage->jsonField("UseVirtualKeyboard"); + auto *cbfield = dynamic_cast(field); + QTC_ASSERT(cbfield, return); + + cbfield->setChecked(value); +} + +bool WizardHandler::haveVirtualKeyboard() const +{ + return m_wizard->hasField("UseVirtualKeyboard"); +} + +void WizardHandler::run(const std::function &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(); +} diff --git a/src/plugins/studiowelcome/wizardhandler.h b/src/plugins/studiowelcome/wizardhandler.h new file mode 100644 index 00000000000..a828d595f66 --- /dev/null +++ b/src/plugins/studiowelcome/wizardhandler.h @@ -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 +#include + +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 &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