diff options
author | Samuel Ghinet <samuel.ghinet@qt.io> | 2022-02-23 15:32:34 +0200 |
---|---|---|
committer | Samuel Ghinet <samuel.ghinet@qt.io> | 2022-03-08 09:39:43 +0000 |
commit | 618eda3572a2df97d28ffdf603daa35b509fbcf1 (patch) | |
tree | f86be7777e702f7a13d9588c753ece1adb241b1a | |
parent | ec02c157eec9aeece5cdbb6b5ab112d0eae4c4e2 (diff) |
Implement custom presets
Task-number: QDS-4989
Change-Id: I95844ae97204ad3bb94905c89f8e16b79eed8f64
Reviewed-by: Alessandro Portale <alessandro.portale@qt.io>
51 files changed, 2220 insertions, 489 deletions
diff --git a/share/qtcreator/qmldesigner/newprojectdialog/NewProjectDialog.qml b/share/qtcreator/qmldesigner/newprojectdialog/NewProjectDialog.qml index 9176af9e34d..5d4d281ff86 100644 --- a/share/qtcreator/qmldesigner/newprojectdialog/NewProjectDialog.qml +++ b/share/qtcreator/qmldesigner/newprojectdialog/NewProjectDialog.qml @@ -32,6 +32,7 @@ import StudioTheme as StudioTheme import StudioControls as SC import NewProjectDialog +import BackendApi Item { id: rootDialog @@ -161,17 +162,43 @@ Item { readonly property int animDur: 500 id: tabBar x: 10 // left padding - width: parent.width - 64 // right padding + width: parent.width - 20 // right padding height: DialogValues.projectViewHeaderHeight color: DialogValues.lightPaneColor + function selectTab(tabIndex, selectLast = false) { + var item = repeater.itemAt(tabIndex) + tabBarRow.currIndex = tabIndex + + projectView.selectLast = selectLast + BackendApi.presetModel.setPage(tabIndex) // NOTE: it resets preset model + } + + Connections { + target: BackendApi + + function onUserPresetSaved() { + var customTabIndex = repeater.count - 1 + tabBar.selectTab(customTabIndex, true) + } + + function onLastUserPresetRemoved() { + tabBar.selectTab(0, false) + } + } + Row { id: tabBarRow spacing: 20 property int currIndex: 0 + readonly property string currentTabName: + repeater.count > 0 && repeater.itemAt(currIndex) + ? repeater.itemAt(currIndex).text + : '' Repeater { - model: categoryModel + id: repeater + model: BackendApi.categoryModel Text { text: name font.weight: Font.DemiBold @@ -184,13 +211,7 @@ Item { MouseArea { anchors.fill: parent onClicked: { - tabBarRow.currIndex = index - presetModel.setPage(index) - projectView.currentIndex = 0 - projectView.currentIndexChanged() - - strip.x = parent.x - strip.width = parent.width + tabBar.selectTab(index) } } @@ -199,8 +220,19 @@ Item { } // tabBarRow Rectangle { + function computeX() { + var item = tabBarRow.children[tabBarRow.currIndex] ?? tabBarRow.children[0] + return item.x; + } + + function computeWidth() { + var item = tabBarRow.children[tabBarRow.currIndex] ?? tabBarRow.children[0] + return item.width; + } + id: strip - width: tabBarRow.children[0].width + x: computeX() + width: computeWidth() height: 5 radius: 2 color: DialogValues.textColorInteraction @@ -209,35 +241,40 @@ Item { Behavior on x { SmoothedAnimation { duration: tabBar.animDur } } Behavior on width { SmoothedAnimation { duration: strip.width === 0 ? 0 : tabBar.animDur } } // do not animate initial width } - - Connections { - target: rootDialog - function onWidthChanged() { - if (rootDialog.width < 1200) { // 1200 = the width threshold - tabBar.width = tabBar.parent.width - 20 - projectView.width = projectView.parent.width - 20 - } else { - tabBar.width = tabBar.parent.width - 64 - projectView.width = projectView.parent.width - 64 - } - } - } } // Rectangle - NewProjectView { - id: projectView + Rectangle { + id: projectViewFrame x: 10 // left padding - width: parent.width - 64 // right padding + width: parent.width - 20 // right padding height: DialogValues.projectViewHeight - loader: projectDetailsLoader - - Connections { - target: rootDialog - function onHeightChanged() { - if (rootDialog.height < 700) { // 700 = minimum height big dialog - projectView.height = DialogValues.projectViewHeight / 2 - } else { - projectView.height = DialogValues.projectViewHeight + color: DialogValues.darkPaneColor + + Item { + anchors.fill: parent + anchors.margins: DialogValues.gridMargins + + NewProjectView { + id: projectView + anchors.fill: parent + + loader: projectDetailsLoader + currentTabName: tabBarRow.currentTabName + + Connections { + target: rootDialog + function onHeightChanged() { + if (rootDialog.height < 720) { // 720 = minimum height big dialog + DialogValues.projectViewHeight = + DialogValues.projectItemHeight + + 2 * DialogValues.gridMargins + } else { + DialogValues.projectViewHeight = + DialogValues.projectItemHeight * 2 + + DialogValues.gridSpacing + + 2 * DialogValues.gridMargins + } + } } } } @@ -247,12 +284,12 @@ Item { Text { id: descriptionText - text: dialogBox.projectDescription + text: BackendApi.projectDescription font.pixelSize: DialogValues.defaultPixelSize lineHeight: DialogValues.defaultLineHeight lineHeightMode: Text.FixedHeight leftPadding: 14 - width: projectView.width + width: projectViewFrame.width color: DialogValues.textColor wrapMode: Text.WordWrap maximumLineCount: 4 @@ -298,7 +335,7 @@ Item { iconFont: StudioTheme.Constants.font onClicked: { - dialogBox.reject(); + BackendApi.reject(); } } @@ -310,11 +347,11 @@ Item { visible: true buttonIcon: qsTr("Create") iconSize: DialogValues.defaultPixelSize - enabled: dialogBox.fieldsValid + enabled: BackendApi.fieldsValid iconFont: StudioTheme.Constants.font onClicked: { - dialogBox.accept(); + BackendApi.accept(); } } } // RowLayout diff --git a/share/qtcreator/qmldesigner/newprojectdialog/image/style-basic.png b/share/qtcreator/qmldesigner/newprojectdialog/image/style-basic.png Binary files differindex 389a4eab873..d139d6e0266 100644 --- a/share/qtcreator/qmldesigner/newprojectdialog/image/style-basic.png +++ b/share/qtcreator/qmldesigner/newprojectdialog/image/style-basic.png diff --git a/share/qtcreator/qmldesigner/newprojectdialog/image/style-basic@2x.png b/share/qtcreator/qmldesigner/newprojectdialog/image/style-basic@2x.png Binary files differindex 3636791da1e..03f87cc4b7d 100644 --- a/share/qtcreator/qmldesigner/newprojectdialog/image/style-basic@2x.png +++ b/share/qtcreator/qmldesigner/newprojectdialog/image/style-basic@2x.png diff --git a/share/qtcreator/qmldesigner/newprojectdialog/image/style-default.png b/share/qtcreator/qmldesigner/newprojectdialog/image/style-default.png Binary files differindex 6c50e4e1fa9..ceb573b339f 100644 --- a/share/qtcreator/qmldesigner/newprojectdialog/image/style-default.png +++ b/share/qtcreator/qmldesigner/newprojectdialog/image/style-default.png diff --git a/share/qtcreator/qmldesigner/newprojectdialog/image/style-default@2x.png b/share/qtcreator/qmldesigner/newprojectdialog/image/style-default@2x.png Binary files differindex 09ea1ce94d5..dd86707377b 100644 --- a/share/qtcreator/qmldesigner/newprojectdialog/image/style-default@2x.png +++ b/share/qtcreator/qmldesigner/newprojectdialog/image/style-default@2x.png diff --git a/share/qtcreator/qmldesigner/newprojectdialog/image/style-fusion.png b/share/qtcreator/qmldesigner/newprojectdialog/image/style-fusion.png Binary files differindex 745828c1f7e..d196646855f 100644 --- a/share/qtcreator/qmldesigner/newprojectdialog/image/style-fusion.png +++ b/share/qtcreator/qmldesigner/newprojectdialog/image/style-fusion.png diff --git a/share/qtcreator/qmldesigner/newprojectdialog/image/style-fusion@2x.png b/share/qtcreator/qmldesigner/newprojectdialog/image/style-fusion@2x.png Binary files differindex c2232f3a754..6bdb9952a4b 100644 --- a/share/qtcreator/qmldesigner/newprojectdialog/image/style-fusion@2x.png +++ b/share/qtcreator/qmldesigner/newprojectdialog/image/style-fusion@2x.png diff --git a/share/qtcreator/qmldesigner/newprojectdialog/image/style-imagine.png b/share/qtcreator/qmldesigner/newprojectdialog/image/style-imagine.png Binary files differindex 40c96993a0c..76612e8a8e8 100644 --- a/share/qtcreator/qmldesigner/newprojectdialog/image/style-imagine.png +++ b/share/qtcreator/qmldesigner/newprojectdialog/image/style-imagine.png diff --git a/share/qtcreator/qmldesigner/newprojectdialog/image/style-imagine@2x.png b/share/qtcreator/qmldesigner/newprojectdialog/image/style-imagine@2x.png Binary files differindex c4ef9b7854d..34c41c3587f 100644 --- a/share/qtcreator/qmldesigner/newprojectdialog/image/style-imagine@2x.png +++ b/share/qtcreator/qmldesigner/newprojectdialog/image/style-imagine@2x.png diff --git a/share/qtcreator/qmldesigner/newprojectdialog/image/style-macOs@2x.png b/share/qtcreator/qmldesigner/newprojectdialog/image/style-macOs@2x.png Binary files differdeleted file mode 100644 index 09ea1ce94d5..00000000000 --- a/share/qtcreator/qmldesigner/newprojectdialog/image/style-macOs@2x.png +++ /dev/null diff --git a/share/qtcreator/qmldesigner/newprojectdialog/image/style-macos.png b/share/qtcreator/qmldesigner/newprojectdialog/image/style-macos.png Binary files differindex 6c50e4e1fa9..ceb573b339f 100644 --- a/share/qtcreator/qmldesigner/newprojectdialog/image/style-macos.png +++ b/share/qtcreator/qmldesigner/newprojectdialog/image/style-macos.png diff --git a/share/qtcreator/qmldesigner/newprojectdialog/image/style-macos@2x.png b/share/qtcreator/qmldesigner/newprojectdialog/image/style-macos@2x.png Binary files differnew file mode 100644 index 00000000000..dd86707377b --- /dev/null +++ b/share/qtcreator/qmldesigner/newprojectdialog/image/style-macos@2x.png diff --git a/share/qtcreator/qmldesigner/newprojectdialog/image/style-material_dark.png b/share/qtcreator/qmldesigner/newprojectdialog/image/style-material_dark.png Binary files differindex 515f5e0b8e4..cf7891232a9 100644 --- a/share/qtcreator/qmldesigner/newprojectdialog/image/style-material_dark.png +++ b/share/qtcreator/qmldesigner/newprojectdialog/image/style-material_dark.png diff --git a/share/qtcreator/qmldesigner/newprojectdialog/image/style-material_dark@2x.png b/share/qtcreator/qmldesigner/newprojectdialog/image/style-material_dark@2x.png Binary files differindex 1967a363dc0..b353dfa6d9d 100644 --- a/share/qtcreator/qmldesigner/newprojectdialog/image/style-material_dark@2x.png +++ b/share/qtcreator/qmldesigner/newprojectdialog/image/style-material_dark@2x.png diff --git a/share/qtcreator/qmldesigner/newprojectdialog/image/style-material_light.png b/share/qtcreator/qmldesigner/newprojectdialog/image/style-material_light.png Binary files differindex d41b3c5ff88..e826ad00811 100644 --- a/share/qtcreator/qmldesigner/newprojectdialog/image/style-material_light.png +++ b/share/qtcreator/qmldesigner/newprojectdialog/image/style-material_light.png diff --git a/share/qtcreator/qmldesigner/newprojectdialog/image/style-material_light@2x.png b/share/qtcreator/qmldesigner/newprojectdialog/image/style-material_light@2x.png Binary files differindex 03c1ae66037..2d3c7c53b6b 100644 --- a/share/qtcreator/qmldesigner/newprojectdialog/image/style-material_light@2x.png +++ b/share/qtcreator/qmldesigner/newprojectdialog/image/style-material_light@2x.png diff --git a/share/qtcreator/qmldesigner/newprojectdialog/image/style-universal_dark.png b/share/qtcreator/qmldesigner/newprojectdialog/image/style-universal_dark.png Binary files differindex 339342bd913..9a9988e91d6 100644 --- a/share/qtcreator/qmldesigner/newprojectdialog/image/style-universal_dark.png +++ b/share/qtcreator/qmldesigner/newprojectdialog/image/style-universal_dark.png diff --git a/share/qtcreator/qmldesigner/newprojectdialog/image/style-universal_dark@2x.png b/share/qtcreator/qmldesigner/newprojectdialog/image/style-universal_dark@2x.png Binary files differindex 2ec60f0453c..bd778aaa071 100644 --- a/share/qtcreator/qmldesigner/newprojectdialog/image/style-universal_dark@2x.png +++ b/share/qtcreator/qmldesigner/newprojectdialog/image/style-universal_dark@2x.png diff --git a/share/qtcreator/qmldesigner/newprojectdialog/image/style-universal_light.png b/share/qtcreator/qmldesigner/newprojectdialog/image/style-universal_light.png Binary files differindex 7b457bae8f4..169581e05dd 100644 --- a/share/qtcreator/qmldesigner/newprojectdialog/image/style-universal_light.png +++ b/share/qtcreator/qmldesigner/newprojectdialog/image/style-universal_light.png diff --git a/share/qtcreator/qmldesigner/newprojectdialog/image/style-universal_light@2x.png b/share/qtcreator/qmldesigner/newprojectdialog/image/style-universal_light@2x.png Binary files differindex 22e200c0d70..80239a95a80 100644 --- a/share/qtcreator/qmldesigner/newprojectdialog/image/style-universal_light@2x.png +++ b/share/qtcreator/qmldesigner/newprojectdialog/image/style-universal_light@2x.png diff --git a/share/qtcreator/qmldesigner/newprojectdialog/image/style-universal_system.png b/share/qtcreator/qmldesigner/newprojectdialog/image/style-universal_system.png Binary files differindex 65fbae504ff..08872abd229 100644 --- a/share/qtcreator/qmldesigner/newprojectdialog/image/style-universal_system.png +++ b/share/qtcreator/qmldesigner/newprojectdialog/image/style-universal_system.png diff --git a/share/qtcreator/qmldesigner/newprojectdialog/image/style-universal_system@2x.png b/share/qtcreator/qmldesigner/newprojectdialog/image/style-universal_system@2x.png Binary files differindex 5d05168537d..5b06ca29874 100644 --- a/share/qtcreator/qmldesigner/newprojectdialog/image/style-universal_system@2x.png +++ b/share/qtcreator/qmldesigner/newprojectdialog/image/style-universal_system@2x.png diff --git a/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/Details.qml b/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/Details.qml index d5c37651dce..97a5ac2bf9e 100644 --- a/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/Details.qml +++ b/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/Details.qml @@ -31,16 +31,13 @@ import QtQuick.Layouts import StudioControls as SC import StudioTheme as StudioTheme +import BackendApi + Item { width: DialogValues.detailsPaneWidth - Component.onCompleted: { - dialogBox.detailsLoaded = true; - } - - Component.onDestruction: { - dialogBox.detailsLoaded = false; - } + Component.onCompleted: BackendApi.detailsLoaded = true + Component.onDestruction: BackendApi.detailsLoaded = false Rectangle { color: DialogValues.darkPaneColor @@ -53,13 +50,13 @@ Item { Column { anchors.fill: parent - spacing: DialogValues.defaultPadding + spacing: 5 Text { id: detailsHeading text: qsTr("Details") height: DialogValues.paneTitleTextHeight - width: parent.width; + width: parent.width font.weight: Font.DemiBold font.pixelSize: DialogValues.paneTitlePixelSize lineHeight: DialogValues.paneTitleLineHeight @@ -71,39 +68,36 @@ Item { Flickable { width: parent.width height: parent.height - detailsHeading.height - DialogValues.defaultPadding - + - savePresetButton.height contentWidth: parent.width contentHeight: scrollContent.height boundsBehavior: Flickable.StopAtBounds clip: true - ScrollBar.vertical: SC.VerticalScrollBar { - } + ScrollBar.vertical: SC.VerticalScrollBar {} Column { id: scrollContent width: parent.width - DialogValues.detailsPanePadding - height: DialogValues.detailsScrollableContentHeight spacing: DialogValues.defaultPadding SC.TextField { id: projectNameTextField actionIndicatorVisible: false translationIndicatorVisible: false - text: dialogBox.projectName + text: BackendApi.projectName width: parent.width color: DialogValues.textColor selectByMouse: true + font.pixelSize: DialogValues.defaultPixelSize onEditingFinished: { text = text.charAt(0).toUpperCase() + text.slice(1) } - - font.pixelSize: DialogValues.defaultPixelSize } Binding { - target: dialogBox + target: BackendApi property: "projectName" value: projectNameTextField.text } @@ -118,14 +112,14 @@ Item { id: projectLocationTextField actionIndicatorVisible: false translationIndicatorVisible: false - text: dialogBox.projectLocation + text: BackendApi.projectLocation color: DialogValues.textColor selectByMouse: true font.pixelSize: DialogValues.defaultPixelSize } Binding { - target: dialogBox + target: BackendApi property: "projectLocation" value: projectLocationTextField.text } @@ -138,7 +132,7 @@ Item { iconFont: StudioTheme.Constants.font onClicked: { - var newLocation = dialogBox.chooseProjectLocation() + var newLocation = BackendApi.chooseProjectLocation() if (newLocation) projectLocationTextField.text = newLocation } @@ -159,7 +153,7 @@ Item { Text { id: statusMessage - text: dialogBox.statusMessage + text: BackendApi.statusMessage font.pixelSize: DialogValues.defaultPixelSize lineHeight: DialogValues.defaultLineHeight lineHeightMode: Text.FixedHeight @@ -172,7 +166,7 @@ Item { states: [ State { name: "warning" - when: dialogBox.statusType === "warning" + when: BackendApi.statusType === "warning" PropertyChanges { target: statusMessage color: DialogValues.textWarning @@ -185,7 +179,7 @@ Item { State { name: "error" - when: dialogBox.statusType === "error" + when: BackendApi.statusType === "error" PropertyChanges { target: statusMessage color: DialogValues.textError @@ -208,7 +202,7 @@ Item { } Binding { - target: dialogBox + target: BackendApi property: "saveAsDefaultLocation" value: defaultLocationCheckbox.checked } @@ -219,25 +213,25 @@ Item { id: screenSizeComboBox actionIndicatorVisible: false currentIndex: -1 - model: screenSizeModel + model: BackendApi.screenSizeModel textRole: "display" width: parent.width font.pixelSize: DialogValues.defaultPixelSize onActivated: (index) => { - dialogBox.setScreenSizeIndex(index); + BackendApi.setScreenSizeIndex(index); - var size = screenSizeModel.screenSizes(index); + var size = BackendApi.screenSizeModel.screenSizes(index); widthField.realValue = size.width; heightField.realValue = size.height; } Connections { - target: screenSizeModel + target: BackendApi.screenSizeModel function onModelReset() { var newIndex = screenSizeComboBox.currentIndex > -1 ? screenSizeComboBox.currentIndex - : dialogBox.screenSizeIndex() + : BackendApi.screenSizeIndex() screenSizeComboBox.currentIndex = newIndex screenSizeComboBox.activated(newIndex) @@ -248,10 +242,8 @@ Item { GridLayout { // orientation + width + height width: parent.width height: 85 - columns: 4 rows: 2 - columnSpacing: 10 rowSpacing: 10 @@ -295,10 +287,7 @@ Item { font.pixelSize: DialogValues.defaultPixelSize onRealValueChanged: { - var height = heightField.realValue - var width = realValue - - if (width >= height) + if (widthField.realValue >= heightField.realValue) orientationButton.setHorizontal() else orientationButton.setVertical() @@ -306,7 +295,7 @@ Item { } // Width Text Field Binding { - target: dialogBox + target: BackendApi property: "customWidth" value: widthField.realValue } @@ -323,10 +312,7 @@ Item { font.pixelSize: DialogValues.defaultPixelSize onRealValueChanged: { - var height = realValue - var width = widthField.realValue - - if (width >= height) + if (widthField.realValue >= heightField.realValue) orientationButton.setHorizontal() else orientationButton.setVertical() @@ -334,7 +320,7 @@ Item { } // Height Text Field Binding { - target: dialogBox + target: BackendApi property: "customHeight" value: heightField.realValue } @@ -345,7 +331,6 @@ Item { id: orientationButton implicitWidth: 100 implicitHeight: 50 - checked: false hoverEnabled: false background: Rectangle { @@ -384,19 +369,19 @@ Item { onClicked: { if (widthField.realValue && heightField.realValue) { - [widthField.realValue, heightField.realValue] = [heightField.realValue, widthField.realValue]; - checked = !checked + [widthField.realValue, heightField.realValue] = [heightField.realValue, widthField.realValue] + orientationButton.checked = !orientationButton.checked } } function setHorizontal() { - checked = false + orientationButton.checked = false horizontalBar.color = DialogValues.textColorInteraction verticalBar.color = "white" } function setVertical() { - checked = true + orientationButton.checked = true horizontalBar.color = "white" verticalBar.color = DialogValues.textColorInteraction } @@ -404,23 +389,27 @@ Item { } // GridLayout: orientation + width + height - Rectangle { width: parent.width; height: 1; color: DialogValues.dividerlineColor } + Rectangle { + width: parent.width + height: 1 + color: DialogValues.dividerlineColor + } SC.CheckBox { id: useQtVirtualKeyboard actionIndicatorVisible: false text: qsTr("Use Qt Virtual Keyboard") font.pixelSize: DialogValues.defaultPixelSize - checked: dialogBox.useVirtualKeyboard - visible: dialogBox.haveVirtualKeyboard + checked: BackendApi.useVirtualKeyboard + visible: BackendApi.haveVirtualKeyboard } RowLayout { // Target Qt Version width: parent.width - visible: dialogBox.haveTargetQtVersion + visible: BackendApi.haveTargetQtVersion Text { - text: "Target Qt Version:" + text: qsTr("Target Qt Version:") font.pixelSize: DialogValues.defaultPixelSize lineHeight: DialogValues.defaultLineHeight lineHeightMode: Text.FixedHeight @@ -432,33 +421,98 @@ Item { actionIndicatorVisible: false implicitWidth: 70 Layout.alignment: Qt.AlignRight - currentIndex: 1 + currentIndex: BackendApi.targetQtVersionIndex font.pixelSize: DialogValues.defaultPixelSize model: ListModel { - ListElement { - name: "Qt 5" - } - ListElement { - name: "Qt 6" - } + ListElement { name: "Qt 5" } + ListElement { name: "Qt 6" } } onActivated: (index) => { - dialogBox.setTargetQtVersion(index) + BackendApi.targetQtVersionIndex = index } } // Target Qt Version ComboBox + + Binding { + target: BackendApi + property: "targetQtVersionIndex" + value: qtVersionComboBox.currentIndex + } + } // RowLayout Binding { - target: dialogBox + target: BackendApi property: "useVirtualKeyboard" value: useQtVirtualKeyboard.checked } } // ScrollContent Column } // ScrollView - } // Column + + SC.AbstractButton { + id: savePresetButton + width: StudioTheme.Values.singleControlColumnWidth + buttonIcon: qsTr("Save Custom Preset") + iconFont: StudioTheme.Constants.font + iconSize: DialogValues.defaultPixelSize + anchors.bottom: parent.bottom + anchors.horizontalCenter: parent.horizontalCenter + + onClicked: savePresetDialog.open() + } + + PopupDialog { + id: savePresetDialog + title: qsTr("Save Preset") + standardButtons: Dialog.Save | Dialog.Cancel + modal: true + closePolicy: Popup.CloseOnEscape + anchors.centerIn: parent + width: DialogValues.popupDialogWidth + + onAccepted: BackendApi.savePresetDialogAccept() + + onOpened: { + presetNameTextField.selectAll() + presetNameTextField.forceActiveFocus() + } + + ColumnLayout { + width: parent.width + spacing: 10 + + Text { + text: qsTr("Preset name") + font.pixelSize: DialogValues.defaultPixelSize + color: DialogValues.textColor + } + + SC.TextField { + id: presetNameTextField + actionIndicatorVisible: false + translationIndicatorVisible: false + text: qsTr("MyPreset") + color: DialogValues.textColor + font.pixelSize: DialogValues.defaultPixelSize + Layout.fillWidth: true + maximumLength: 30 + validator: RegularExpressionValidator { regularExpression: /\w[\w ]*/ } + + onEditingFinished: { + presetNameTextField.text = text.trim() + presetNameTextField.text = text.replace(/\s+/g, " ") + } + } + + Binding { + target: BackendApi + property: "presetName" + value: presetNameTextField.text + } + } + } } // Item - } -} + } // Rectangle +} // root Item diff --git a/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/DialogValues.qml b/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/DialogValues.qml index ae26727e07b..7c11f231fc7 100644 --- a/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/DialogValues.qml +++ b/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/DialogValues.qml @@ -29,39 +29,57 @@ import QtQml import StudioTheme as StudioTheme QtObject { + id: root + readonly property int dialogWidth: 1522 readonly property int dialogHeight: 940 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 projectViewMinimumHeight: root.gridCellHeight + readonly property int dialogContentHeight: root.projectViewHeight + 300 // i.e. dialog without header and footer + readonly property int loadedPanesWidth: root.detailsPaneWidth + root.stylesPaneWidth + readonly property int detailsPaneWidth: 330 + root.detailsPanePadding * 2 readonly property int dialogTitleTextHeight: 85 readonly property int paneTitleTextHeight: 47 readonly property int logoWidth: 85 readonly property int logoHeight: 85 - /* detailsScrollableContentHeight - the full height that may need to be scrolled to be fully - visible, if the dialog box is too small. */ - readonly property int detailsScrollableContentHeight: 428 - readonly property int stylesPaneWidth: styleImageWidth + stylesPanePadding * 2 + styleImageBorderWidth * 2 // i.e. 240px + readonly property int stylesPaneWidth: root.styleImageWidth + root.stylesPanePadding * 2 + + root.styleImageBorderWidth * 2 // i.e. 240px readonly property int detailsPanePadding: 18 readonly property int stylesPanePadding: 18 readonly property int defaultPadding: 18 readonly property int dialogLeftPadding: 35 + readonly property int styleListItemHeight: root.styleImageHeight + root.styleTextHeight + + 2 * root.styleImageBorderWidth + + root.styleListItemBottomMargin + + root.styleListItemSpacing + readonly property int styleListItemBottomMargin: 10 + readonly property int styleListItemSpacing: 4 readonly property int styleImageWidth: 200 + readonly property int styleImageHeight: 262 readonly property int styleImageBorderWidth: 2 + readonly property int styleTextHeight: 18 + readonly property int footerHeight: 73 - readonly property int projectItemWidth: 90 - readonly property int projectItemHeight: 144 - readonly property int projectViewHeight: projectItemHeight * 2 + readonly property int projectItemWidth: 136 + readonly property int projectItemHeight: 110 + property int projectViewHeight: root.projectItemHeight * 2 + root.gridSpacing + root.gridMargins * 2 readonly property int projectViewHeaderHeight: 38 + readonly property int gridMargins: 20 + readonly property int gridCellWidth: root.projectItemWidth + root.gridSpacing + readonly property int gridCellHeight: root.projectItemHeight + root.gridSpacing + readonly property int gridSpacing: 2 + readonly property int dialogButtonWidth: 100 - readonly property int loadedPanesHeight: dialogContentHeight - readonly property int detailsPaneHeight: dialogContentHeight + // This is for internal popup dialogs + readonly property int popupDialogWidth: 270 + readonly property int popupDialogPadding: 12 + + readonly property int loadedPanesHeight: root.dialogContentHeight + readonly property int detailsPaneHeight: root.dialogContentHeight readonly property string darkPaneColor: StudioTheme.Values.themeBackgroundColorNormal readonly property string lightPaneColor: StudioTheme.Values.themeBackgroundColorAlternate @@ -71,6 +89,8 @@ QtObject { readonly property string dividerlineColor: StudioTheme.Values.themeTextColorDisabled readonly property string textError: StudioTheme.Values.themeError readonly property string textWarning: StudioTheme.Values.themeWarning + readonly property string presetItemBackgroundHover: StudioTheme.Values.themeControlBackgroundGlobalHover + readonly property string presetItemBackgroundHoverInteraction: StudioTheme.Values.themeControlBackgroundInteraction readonly property real defaultPixelSize: 14 readonly property real defaultLineHeight: 21 @@ -91,6 +111,6 @@ QtObject { item and spacing after it). So we have to subtract 2 x layout spacing before setting our own, narrower, spacing. */ - return -layoutSpacing -layoutSpacing + value + return -layoutSpacing - layoutSpacing + value } } diff --git a/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/NewProjectView.qml b/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/NewProjectView.qml index e011057892b..997260747b3 100644 --- a/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/NewProjectView.qml +++ b/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/NewProjectView.qml @@ -28,111 +28,252 @@ import QtQuick.Controls import QtQuick import QtQuick.Layouts +import StudioControls as SC import StudioTheme as StudioTheme -GridView { - id: projectView +import BackendApi + +ScrollView { + id: scrollView required property Item loader + required property string currentTabName - cellWidth: DialogValues.projectItemWidth - cellHeight: DialogValues.projectItemHeight - clip: true + property string backgroundHoverColor: DialogValues.presetItemBackgroundHover - boundsBehavior: Flickable.StopAtBounds + // selectLast: if true, it will select last item in the model after a model reset. + property bool selectLast: false - children: [ - Rectangle { - color: DialogValues.darkPaneColor - anchors.fill: parent - z: -1 - } - ] + ScrollBar.horizontal.policy: ScrollBar.AlwaysOff + ScrollBar.vertical: SC.VerticalScrollBar { + parent: scrollView + x: scrollView.width + (DialogValues.gridMargins + - StudioTheme.Values.scrollBarThickness) * 0.5 + y: scrollView.topPadding + height: scrollView.availableHeight + } - model: presetModel + contentWidth: gridView.contentItem.childrenRect.width + contentHeight: gridView.contentItem.childrenRect.height - // called by onModelReset and when user clicks on an item, or when the header item is changed. - onCurrentIndexChanged: { - dialogBox.selectedPreset = projectView.currentIndex - var source = dialogBox.currentPresetQmlPath() - loader.source = source - } + GridView { + id: gridView - Connections { - target: presetModel + clip: true + anchors.fill: parent + cellWidth: DialogValues.gridCellWidth + cellHeight: DialogValues.gridCellHeight + rightMargin: -DialogValues.gridSpacing + bottomMargin: -DialogValues.gridSpacing + boundsBehavior: Flickable.StopAtBounds + model: BackendApi.presetModel - // called when data is set (setWizardFactories) - function onModelReset() { - currentIndex = 0 - currentIndexChanged() + // called by onModelReset and when user clicks on an item, or when the header item is changed. + onCurrentIndexChanged: { + BackendApi.selectedPreset = gridView.currentIndex + var source = BackendApi.currentPresetQmlPath() + scrollView.loader.source = source } - } - delegate: ItemDelegate { - id: delegate + Connections { + target: BackendApi.presetModel - width: DialogValues.projectItemWidth - height: DialogValues.projectItemHeight - background: null + // called when data is set (setWizardFactories) + function onModelReset() { + if (scrollView.selectLast) { + gridView.currentIndex = BackendApi.presetModel.rowCount() - 1 + scrollView.selectLast = false + } else { + gridView.currentIndex = 0 + } - function fontIconCode(index) { - var code = presetModel.fontIconCode(index) - return code ? code : StudioTheme.Constants.wizardsUnknown + // This will load Details.qml and Styles.qml by setting "source" on the Loader. + gridView.currentIndexChanged() + } } - Column { - width: parent.width - height: parent.height + delegate: ItemDelegate { + id: delegate - Label { - id: projectTypeIcon - text: fontIconCode(index) - color: DialogValues.textColor + property bool hover: delegate.hovered || removeMouseArea.containsMouse + + width: DialogValues.projectItemWidth + height: DialogValues.projectItemHeight + + onClicked: delegate.GridView.view.currentIndex = index + + background: Rectangle { + id: delegateBackground 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 + height: parent.height + color: delegate.hover ? scrollView.backgroundHoverColor : "transparent" + border.color: delegate.hover ? projectTypeName.color : "transparent" + } + + function fontIconCode(index) { + var code = BackendApi.presetModel.fontIconCode(index) + return code ? code : StudioTheme.Constants.wizardsUnknown } + contentItem: Item { + anchors.fill: parent + + ColumnLayout { + spacing: 0 + anchors.top: parent.top + anchors.topMargin: -1 + anchors.horizontalCenter: parent.horizontalCenter + + Label { + id: projectTypeIcon + text: delegate.fontIconCode(index) + color: DialogValues.textColor + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignBottom + renderType: Text.NativeRendering + font.pixelSize: 65 + font.family: StudioTheme.Constants.iconFont.family + Layout.alignment: Qt.AlignHCenter + } // Preset type icon Label + + Text { + id: projectTypeName + color: DialogValues.textColor + text: name + font.pixelSize: DialogValues.defaultPixelSize + lineHeight: DialogValues.defaultLineHeight + lineHeightMode: Text.FixedHeight + width: DialogValues.projectItemWidth - 16 + elide: Text.ElideRight + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignTop + Layout.alignment: Qt.AlignHCenter + Layout.preferredWidth: projectTypeName.width + Layout.minimumWidth: projectTypeName.width + Layout.maximumWidth: projectTypeName.width + + ToolTip { + id: toolTip + y: -toolTip.height + visible: delegate.hovered && projectTypeName.truncated + text: name + delay: 1000 + height: 20 + + background: Rectangle { + color: StudioTheme.Values.themeToolTipBackground + border.color: StudioTheme.Values.themeToolTipOutline + border.width: StudioTheme.Values.border + } + + contentItem: Text { + color: StudioTheme.Values.themeToolTipText + text: toolTip.text + font.pixelSize: DialogValues.defaultPixelSize + verticalAlignment: Text.AlignVCenter + } + } + } + + Text { + id: projectTypeResolution + color: DialogValues.textColor + text: resolution + font.pixelSize: DialogValues.defaultPixelSize + lineHeight: DialogValues.defaultLineHeight + lineHeightMode: Text.FixedHeight + wrapMode: Text.Wrap + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignTop + Layout.alignment: Qt.AlignHCenter + } + + } // ColumnLayout + + Item { + id: removePresetButton + width: 20 + height: 20 + anchors.top: parent.top + anchors.right: parent.right + anchors.margins: 4 + visible: isUserPreset === true + && delegate.hover + && scrollView.currentTabName !== "Recents" + + Text { + anchors.fill: parent + text: StudioTheme.Constants.closeCross + color: DialogValues.textColor + horizontalAlignment: Qt.AlignHCenter + verticalAlignment: Qt.AlignVCenter + font.family: StudioTheme.Constants.iconFont.family + font.pixelSize: StudioTheme.Values.myIconFontSize + } + + MouseArea { + id: removeMouseArea + anchors.fill: parent + hoverEnabled: true + cursorShape: removeMouseArea.containsMouse ? Qt.PointingHandCursor + : Qt.ArrowCursor + + onClicked: { + removePresetDialog.presetName = projectTypeName.text + removePresetDialog.open() + } + } + } // Delete preset button Item + } // Item + + states: [ + State { + name: "current" + when: delegate.GridView.isCurrentItem + + PropertyChanges { + target: projectTypeName + color: DialogValues.textColorInteraction + } + PropertyChanges { + target: projectTypeResolution + color: DialogValues.textColorInteraction + } + PropertyChanges { + target: projectTypeIcon + color: DialogValues.textColorInteraction + } + PropertyChanges { + target: scrollView + backgroundHoverColor: DialogValues.presetItemBackgroundHoverInteraction + } + } // State + ] + } // ItemDelegate + + PopupDialog { + id: removePresetDialog + + property string presetName + + title: qsTr("Delete Custom Preset") + standardButtons: Dialog.Yes | Dialog.No + modal: true + closePolicy: Popup.CloseOnEscape + anchors.centerIn: parent + width: DialogValues.popupDialogWidth + + onAccepted: BackendApi.removeCurrentPreset() + Text { - id: projectTypeLabel + text: qsTr("Are you sure you want to delete \"" + removePresetDialog.presetName + "\" ?") 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 + wrapMode: Text.WordWrap - MouseArea { - anchors.fill: parent - onClicked: { - delegate.GridView.view.currentIndex = index + width: DialogValues.popupDialogWidth - 2 * DialogValues.popupDialogPadding } - } - - states: [ - State { - when: delegate.GridView.isCurrentItem - PropertyChanges { - target: projectTypeLabel - color: DialogValues.textColorInteraction - } - PropertyChanges { - target: projectTypeIcon - color: DialogValues.textColorInteraction - } - } // State - ] - } // ItemDelegate -} // GridView + } // Dialog + } // GridView +} // ScrollView diff --git a/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/PopupDialog.qml b/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/PopupDialog.qml new file mode 100644 index 00000000000..2c6b3084617 --- /dev/null +++ b/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/PopupDialog.qml @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** Copyright (C) 2022 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 +import QtQuick.Controls +import QtQuick.Layouts + +import StudioTheme as StudioTheme + +import BackendApi + +Dialog { + id: root + padding: DialogValues.popupDialogPadding + + background: Rectangle { + color: DialogValues.darkPaneColor + border.color: StudioTheme.Values.themeInteraction + border.width: StudioTheme.Values.border + } + + header: Label { + text: root.title + visible: root.title + elide: Label.ElideRight + font.bold: true + font.pixelSize: DialogValues.defaultPixelSize + padding: DialogValues.popupDialogPadding + color: DialogValues.textColor + horizontalAlignment: Text.AlignHCenter + + background: Rectangle { + x: 1 + y: 1 + width: parent.width - 2 + height: parent.height - 1 + color: DialogValues.darkPaneColor + } + } + + footer: PopupDialogButtonBox { + visible: count > 0 + } +} diff --git a/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/PopupDialogButton.qml b/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/PopupDialogButton.qml new file mode 100644 index 00000000000..ee64eb90783 --- /dev/null +++ b/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/PopupDialogButton.qml @@ -0,0 +1,107 @@ +/**************************************************************************** +** +** Copyright (C) 2022 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 +import QtQuick.Controls +import StudioTheme as StudioTheme + +Button { + id: root + + implicitWidth: Math.max( + background ? background.implicitWidth : 0, + textItem.implicitWidth + leftPadding + rightPadding) + implicitHeight: Math.max( + background ? background.implicitHeight : 0, + textItem.implicitHeight + topPadding + bottomPadding) + leftPadding: 4 + rightPadding: 4 + + background: Rectangle { + id: background + implicitWidth: 80 + implicitHeight: StudioTheme.Values.height + color: StudioTheme.Values.themeControlBackground + border.color: StudioTheme.Values.themeControlOutline + anchors.fill: parent + } + + contentItem: Text { + id: textItem + text: root.text + font.family: StudioTheme.Constants.font.family + font.pixelSize: DialogValues.defaultPixelSize + color: StudioTheme.Values.themeTextColor + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + renderType: Text.QtRendering + anchors.fill: parent + } + + states: [ + State { + name: "default" + when: !root.hovered && !root.checked && !root.pressed + + PropertyChanges { + target: background + color: StudioTheme.Values.themeControlBackground + border.color: StudioTheme.Values.themeControlOutline + } + PropertyChanges { + target: textItem + color: StudioTheme.Values.themeTextColor + } + }, + State { + name: "hover" + when: root.hovered && !root.checked && !root.pressed + + PropertyChanges { + target: background + color: StudioTheme.Values.themeControlBackgroundHover + border.color: StudioTheme.Values.themeControlOutline + } + PropertyChanges { + target: textItem + color: StudioTheme.Values.themeTextColor + } + }, + State { + name: "press" + when: root.hovered && root.pressed + + PropertyChanges { + target: background + color: StudioTheme.Values.themeInteraction + border.color: StudioTheme.Values.themeInteraction + } + PropertyChanges { + target: textItem + color: StudioTheme.Values.themeIconColor + } + } + ] +} diff --git a/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/PopupDialogButtonBox.qml b/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/PopupDialogButtonBox.qml new file mode 100644 index 00000000000..d4d867f862d --- /dev/null +++ b/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/PopupDialogButtonBox.qml @@ -0,0 +1,46 @@ +/**************************************************************************** +** +** Copyright (C) 2022 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 +import QtQuick.Controls + +DialogButtonBox { + id: root + padding: DialogValues.popupDialogPadding + alignment: Qt.AlignRight | Qt.AlignBottom + + background: Rectangle { + implicitHeight: 40 + x: 1 + y: 1 + width: parent.width - 2 + height: parent.height - 2 + color: DialogValues.darkPaneColor + } + + delegate: PopupDialogButton { + width: root.count === 1 ? root.availableWidth / 2 : undefined + } +} diff --git a/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/Styles.qml b/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/Styles.qml index e6955bb21b7..72be983e33a 100644 --- a/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/Styles.qml +++ b/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/Styles.qml @@ -23,20 +23,21 @@ ** ****************************************************************************/ +import QtQuick import QtQuick.Window import QtQuick.Controls - -import QtQuick import QtQuick.Layouts import StudioControls as SC import StudioTheme as StudioTheme +import BackendApi + Item { width: DialogValues.stylesPaneWidth Component.onCompleted: { - dialogBox.stylesLoaded = true; + BackendApi.stylesLoaded = true /* * TODO: roleNames is called before the backend model (in the proxy class StyleModel) is @@ -47,7 +48,7 @@ Item { } Component.onDestruction: { - dialogBox.stylesLoaded = false; + BackendApi.stylesLoaded = false } Rectangle { @@ -57,116 +58,120 @@ Item { Item { x: DialogValues.stylesPanePadding - width: parent.width - DialogValues.stylesPanePadding * 2 + styleScrollBar.width + width: parent.width - DialogValues.stylesPanePadding * 2 height: parent.height - ColumnLayout { - anchors.fill: parent - spacing: 5 - - Text { - id: styleTitleText - text: qsTr("Style") - Layout.minimumHeight: DialogValues.paneTitleTextHeight - font.weight: Font.DemiBold - font.pixelSize: DialogValues.paneTitlePixelSize - lineHeight: DialogValues.paneTitleLineHeight - lineHeightMode: Text.FixedHeight - color: DialogValues.textColor - verticalAlignment: Qt.AlignVCenter - - function refresh() { - text = qsTr("Style") + " (" + styleModel.rowCount() + ")" - } + Text { + id: styleTitleText + text: qsTr("Style") + height: DialogValues.paneTitleTextHeight + width: parent.width + font.weight: Font.DemiBold + font.pixelSize: DialogValues.paneTitlePixelSize + lineHeight: DialogValues.paneTitleLineHeight + lineHeightMode: Text.FixedHeight + color: DialogValues.textColor + verticalAlignment: Qt.AlignVCenter + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + function refresh() { + styleTitleText.text = qsTr("Style") + " (" + BackendApi.styleModel.rowCount() + ")" + } + } + + SC.ComboBox { // Style Filter ComboBox + id: styleComboBox + actionIndicatorVisible: false + currentIndex: 0 + textRole: "text" + valueRole: "value" + font.pixelSize: DialogValues.defaultPixelSize + width: parent.width + + anchors.top: styleTitleText.bottom + anchors.topMargin: 5 + + model: ListModel { + ListElement { text: qsTr("All"); value: "all" } + ListElement { text: qsTr("Light"); value: "light" } + ListElement { text: qsTr("Dark"); value: "dark" } } - 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 - - Item { implicitWidth: 1; implicitHeight: 9 } + onActivated: (index) => { + BackendApi.styleModel.filter(currentValue.toLowerCase()) + styleTitleText.refresh() + } + } // Style Filter ComboBox + + ScrollView { + id: scrollView + + anchors.top: styleComboBox.bottom + anchors.topMargin: 11 + anchors.bottom: parent.bottom + anchors.bottomMargin: DialogValues.stylesPanePadding + width: parent.width + + ScrollBar.horizontal.policy: ScrollBar.AlwaysOff + ScrollBar.vertical: SC.VerticalScrollBar { + id: styleScrollBar + x: stylesList.width + (DialogValues.stylesPanePadding + - StudioTheme.Values.scrollBarThickness) * 0.5 + } ListView { id: stylesList - Layout.fillWidth: true - Layout.fillHeight: true - clip: true - model: styleModel - - MouseArea { - id: listViewMouseArea - anchors.fill: parent - hoverEnabled: true - propagateComposedEvents: true - } - + anchors.fill: parent focus: true + clip: true + model: BackendApi.styleModel boundsBehavior: Flickable.StopAtBounds - highlightFollowsCurrentItem: false - - ScrollBar.vertical: SC.VerticalScrollBar { - id: styleScrollBar - property int extraPadding: 0 - bottomInset: extraPadding - bottomPadding: bottomInset + 16 - viewMouseArea: listViewMouseArea - } // ScrollBar + bottomMargin: -DialogValues.styleListItemBottomMargin onCurrentIndexChanged: { - if (styleModel.rowCount() > 0) - dialogBox.styleIndex = stylesList.currentIndex; + if (BackendApi.styleModel.rowCount() > 0) + BackendApi.styleIndex = stylesList.currentIndex } delegate: ItemDelegate { id: delegateId - height: styleImage.height + DialogValues.styleImageBorderWidth + styleText.height + extraPadding.height + 1 - width: stylesList.width - styleScrollBar.width + width: stylesList.width + height: DialogValues.styleListItemHeight - Component.onCompleted: { - styleScrollBar.extraPadding = styleText.height + extraPadding.height - } + onClicked: stylesList.currentIndex = index - Rectangle { + background: Rectangle { anchors.fill: parent color: DialogValues.lightPaneColor + } + + contentItem: Item { + anchors.fill: parent Column { - spacing: 0 anchors.fill: parent + spacing: DialogValues.styleListItemSpacing Rectangle { - border.color: index == stylesList.currentIndex ? DialogValues.textColorInteraction : "transparent" - border.width: index == stylesList.currentIndex ? DialogValues.styleImageBorderWidth : 0 + width: DialogValues.styleImageWidth + + 2 * DialogValues.styleImageBorderWidth + height: DialogValues.styleImageHeight + + 2 * DialogValues.styleImageBorderWidth + 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 - extraPadding.height Image { id: styleImage - asynchronous: false - source: "image://newprojectdialog_library/" + styleModel.iconId(model.index) - width: 200 - height: 262 x: DialogValues.styleImageBorderWidth y: DialogValues.styleImageBorderWidth + width: DialogValues.styleImageWidth + height: DialogValues.styleImageHeight + asynchronous: false + source: "image://newprojectdialog_library/" + BackendApi.styleModel.iconId(model.index) } } // Rectangle @@ -175,35 +180,26 @@ Item { text: model.display font.pixelSize: DialogValues.defaultPixelSize lineHeight: DialogValues.defaultLineHeight - height: 18 + height: DialogValues.styleTextHeight lineHeightMode: Text.FixedHeight horizontalAlignment: Text.AlignHCenter width: parent.width color: DialogValues.textColor } - - Item { id: extraPadding; width: 1; height: 10 } } // Column - } // Rectangle - - MouseArea { - anchors.fill: parent - onClicked: { - stylesList.currentIndex = index - } } } Connections { - target: styleModel + target: BackendApi.styleModel function onModelReset() { - stylesList.currentIndex = dialogBox.styleIndex; - stylesList.currentIndexChanged(); - styleTitleText.refresh(); + stylesList.currentIndex = BackendApi.styleIndex + stylesList.currentIndexChanged() + styleTitleText.refresh() } } } // ListView - } // ColumnLayout + } // ScrollView } // Parent Item } // Rectangle } diff --git a/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/qmldir b/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/qmldir index d7c1562164f..996b9ec3561 100644 --- a/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/qmldir +++ b/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/qmldir @@ -1,4 +1,7 @@ -singleton DialogValues 1.0 DialogValues.qml +PopupDialog 1.0 PopupDialog.qml +PopupDialogButton 1.0 PopupDialogButton.qml +PopupDialogButtonBox 1.0 PopupDialogButtonBox.qml Details 1.0 Details.qml -Styles 1.0 Styles.qml +singleton DialogValues 1.0 DialogValues.qml NewProjectView 1.0 NewProjectView.qml +Styles 1.0 Styles.qml diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/VerticalScrollBar.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/VerticalScrollBar.qml index 4b344130959..b0e6f82bc17 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/VerticalScrollBar.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/VerticalScrollBar.qml @@ -35,37 +35,26 @@ ScrollBar { implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, implicitContentHeight + topPadding + bottomPadding) - property bool scrollBarVisible: parent.childrenRect.height > parent.height - // viewMouseArea: if set, the scrollbar will be visible only on hover over the view containing - // the mouse area item. - property MouseArea viewMouseArea: null - - minimumSize: orientation == Qt.Horizontal ? height / width : width / height + property bool scrollBarVisible: parent.contentHeight > scrollBar.height + minimumSize: scrollBar.width / scrollBar.height orientation: Qt.Vertical - policy: computePolicy() - x: parent.width - width - y: 0 + policy: scrollBar.scrollBarVisible ? ScrollBar.AlwaysOn : ScrollBar.AlwaysOff + height: parent.availableHeight - (parent.bothVisible ? parent.horizontalThickness : 0) - padding: 0 + padding: scrollBar.active ? StudioTheme.Values.scrollBarActivePadding + : StudioTheme.Values.scrollBarInactivePadding background: Rectangle { + implicitWidth: StudioTheme.Values.scrollBarThickness + implicitHeight: StudioTheme.Values.scrollBarThickness color: StudioTheme.Values.themeScrollBarTrack } contentItem: Rectangle { - implicitWidth: StudioTheme.Values.scrollBarThickness + implicitWidth: StudioTheme.Values.scrollBarThickness - 2 * scrollBar.padding + implicitHeight: StudioTheme.Values.scrollBarThickness - 2 * scrollBar.padding color: StudioTheme.Values.themeScrollBarHandle } - - function computePolicy() { - if (!scrollBar.scrollBarVisible) - return ScrollBar.AlwaysOff; - - if (scrollBar.viewMouseArea) - return scrollBar.viewMouseArea.containsMouse ? ScrollBar.AlwaysOn : ScrollBar.AlwaysOff - else - return ScrollBar.AlwaysOn; - } } diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/Values.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/Values.qml index 29d0edb14db..f63aa36ab14 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/Values.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/Values.qml @@ -89,6 +89,8 @@ QtObject { property real typeLabelVerticalShift: Math.round(6 * values.scaleFactor) property real scrollBarThickness: 10 + property real scrollBarActivePadding: 1 + property real scrollBarInactivePadding: 2 property real toolTipHeight: 25 property int toolTipDelay: 1000 diff --git a/src/plugins/studiowelcome/CMakeLists.txt b/src/plugins/studiowelcome/CMakeLists.txt index a8b92ab1f8b..209bb80b01c 100644 --- a/src/plugins/studiowelcome/CMakeLists.txt +++ b/src/plugins/studiowelcome/CMakeLists.txt @@ -14,6 +14,7 @@ add_qtc_plugin(StudioWelcome createproject.cpp createproject.h wizardhandler.cpp wizardhandler.h recentpresets.cpp recentpresets.h + userpresets.cpp userpresets.h screensizemodel.h algorithm.h stylemodel.h stylemodel.cpp diff --git a/src/plugins/studiowelcome/presetmodel.cpp b/src/plugins/studiowelcome/presetmodel.cpp index b8db0503f24..e9d7438139d 100644 --- a/src/plugins/studiowelcome/presetmodel.cpp +++ b/src/plugins/studiowelcome/presetmodel.cpp @@ -25,22 +25,30 @@ #include "presetmodel.h" #include <utils/optional.h> -#include <utils/qtcassert.h> #include "algorithm.h" using namespace StudioWelcome; +constexpr int NameRole = Qt::UserRole; +constexpr int ScreenSizeRole = Qt::UserRole + 1; +constexpr int IsUserPresetRole = Qt::UserRole + 2; + +static const QString RecentsTabName = QObject::tr("Recents"); +static const QString CustomTabName = QObject::tr("Custom"); + /****************** PresetData ******************/ void PresetData::setData(const PresetsByCategory &presetsByCategory, - const std::vector<RecentPreset> &loadedRecents) + const std::vector<UserPresetData> &userPresetsData, + const std::vector<RecentPresetData> &loadedRecentsData) { - QTC_ASSERT(!presetsByCategory.empty(), return); - m_recents = loadedRecents; + QTC_ASSERT(!presetsByCategory.empty(), return ); + m_recents = loadedRecentsData; + m_userPresets = userPresetsData; if (!m_recents.empty()) { - m_categories.push_back("Recents"); + m_categories.push_back(RecentsTabName); m_presets.push_back({}); } @@ -49,28 +57,95 @@ void PresetData::setData(const PresetsByCategory &presetsByCategory, m_presets.push_back(category.items); } - PresetItems presets = Utils::flatten(m_presets); - - std::vector<PresetItem> recentPresets = makeRecentPresets(presets); + PresetItems wizardPresets = Utils::flatten(m_presets); - if (!m_recents.empty()) + PresetItems recentPresets = makeRecentPresets(wizardPresets); + if (!m_recents.empty()) { m_presets[0] = recentPresets; + } + + PresetItems userPresetItems = makeUserPresets(wizardPresets); + if (!userPresetItems.empty()) { + m_categories.push_back(CustomTabName); + m_presets.push_back(userPresetItems); + } + + m_presetsByCategory = presetsByCategory; } -std::vector<PresetItem> PresetData::makeRecentPresets(const PresetItems &wizardPresets) +void PresetData::reload(const std::vector<UserPresetData> &userPresetsData, + const std::vector<RecentPresetData> &loadedRecentsData) { - static const PresetItem empty; + m_categories.clear(); + m_presets.clear(); + m_recents.clear(); + m_userPresets.clear(); + setData(m_presetsByCategory, userPresetsData, loadedRecentsData); +} +std::shared_ptr<PresetItem> PresetData::findPresetItemForUserPreset(const UserPresetData &preset, + const PresetItems &wizardPresets) +{ + return Utils::findOrDefault(wizardPresets, [&preset](const std::shared_ptr<PresetItem> &item) { + return item->wizardName == preset.wizardName && item->categoryId == preset.categoryId; + }); +} + +PresetItems PresetData::makeUserPresets(const PresetItems &wizardPresets) +{ PresetItems result; - for (const RecentPreset &recent : m_recents) { - auto item = Utils::findOptional(wizardPresets, [&recent](const PresetItem &item) { - return item.categoryId == std::get<0>(recent) && item.name == std::get<1>(recent); - }); + for (const UserPresetData &userPresetData : m_userPresets) { + std::shared_ptr<PresetItem> foundPreset = findPresetItemForUserPreset(userPresetData, + wizardPresets); + if (!foundPreset) + continue; + + auto presetItem = std::make_shared<UserPresetItem>(); + + presetItem->categoryId = userPresetData.categoryId; + presetItem->wizardName = userPresetData.wizardName; + presetItem->screenSizeName = userPresetData.screenSize; + + presetItem->userName = userPresetData.name; + presetItem->qtVersion = userPresetData.qtVersion; + presetItem->styleName = userPresetData.styleName; + presetItem->useQtVirtualKeyboard = userPresetData.useQtVirtualKeyboard; + + presetItem->create = foundPreset->create; + presetItem->description = foundPreset->description; + presetItem->fontIconCode = foundPreset->fontIconCode; + presetItem->qmlPath = foundPreset->qmlPath; - if (item) { - item->screenSizeName = std::get<2>(recent); - result.push_back(item.value()); + result.push_back(presetItem); + } + + return result; +} + +std::shared_ptr<PresetItem> PresetData::findPresetItemForRecent(const RecentPresetData &recent, const PresetItems &wizardPresets) +{ + return Utils::findOrDefault(wizardPresets, [&recent](const std::shared_ptr<PresetItem> &item) { + bool sameName = item->categoryId == recent.category + && item->displayName() == recent.presetName; + + bool sameType = (recent.isUserPreset ? item->isUserPreset() : !item->isUserPreset()); + + return sameName && sameType; + }); +} + +PresetItems PresetData::makeRecentPresets(const PresetItems &wizardPresets) +{ + PresetItems result; + + for (const RecentPresetData &recent : m_recents) { + std::shared_ptr<PresetItem> preset = findPresetItemForRecent(recent, wizardPresets); + + if (preset) { + auto clone = std::shared_ptr<PresetItem>{preset->clone()}; + clone->screenSizeName = recent.sizeName; + result.push_back(clone); } } @@ -86,8 +161,8 @@ BasePresetModel::BasePresetModel(const PresetData *data, QObject *parent) QHash<int, QByteArray> BasePresetModel::roleNames() const { - QHash<int, QByteArray> roleNames; - roleNames[Qt::UserRole] = "name"; + static QHash<int, QByteArray> roleNames{{NameRole, "name"}, + {ScreenSizeRole, "resolution"}}; return roleNames; } @@ -97,8 +172,9 @@ PresetCategoryModel::PresetCategoryModel(const PresetData *data, QObject *parent : BasePresetModel(data, parent) {} -int PresetCategoryModel::rowCount(const QModelIndex &) const +int PresetCategoryModel::rowCount(const QModelIndex &parent) const { + Q_UNUSED(parent) return static_cast<int>(m_data->categories().size()); } @@ -116,9 +192,9 @@ PresetModel::PresetModel(const PresetData *data, QObject *parent) QHash<int, QByteArray> PresetModel::roleNames() const { - QHash<int, QByteArray> roleNames; - roleNames[Qt::UserRole] = "name"; - roleNames[Qt::UserRole + 1] = "size"; + static QHash<int, QByteArray> roleNames{{NameRole, "name"}, + {ScreenSizeRole, "resolution"}, + {IsUserPresetRole, "isUserPreset"}}; return roleNames; } @@ -132,9 +208,16 @@ int PresetModel::rowCount(const QModelIndex &) const QVariant PresetModel::data(const QModelIndex &index, int role) const { - Q_UNUSED(role) - PresetItem preset = presetsOfCurrentCategory().at(index.row()); - return QVariant::fromValue<QString>(preset.name + "\n" + preset.screenSizeName); + std::shared_ptr<PresetItem> preset = presetsOfCurrentCategory().at(index.row()); + + if (role == NameRole) + return preset->displayName(); + else if (role == ScreenSizeRole) + return preset->screenSize(); + else if (role == IsUserPresetRole) + return preset->isUserPreset(); + else + return {}; } void PresetModel::setPage(int index) @@ -148,7 +231,7 @@ void PresetModel::setPage(int index) QString PresetModel::fontIconCode(int index) const { - Utils::optional<PresetItem> presetItem = preset(index); + std::shared_ptr<PresetItem> presetItem = preset(index); if (!presetItem) return {}; diff --git a/src/plugins/studiowelcome/presetmodel.h b/src/plugins/studiowelcome/presetmodel.h index a1c9b0e7d27..5bc19755885 100644 --- a/src/plugins/studiowelcome/presetmodel.h +++ b/src/plugins/studiowelcome/presetmodel.h @@ -31,8 +31,10 @@ #include <utils/filepath.h> #include <utils/optional.h> +#include <utils/qtcassert.h> #include "recentpresets.h" +#include "userpresets.h" namespace Utils { class Wizard; @@ -40,38 +42,115 @@ class Wizard; namespace StudioWelcome { +struct UserPresetItem; + struct PresetItem { - QString name; + PresetItem() = default; + PresetItem(const QString &wizardName, const QString &category, const QString &sizeName = "") + : wizardName{wizardName} + , categoryId{category} + , screenSizeName{sizeName} + {} + + virtual ~PresetItem() {} + virtual QString displayName() const { return wizardName; } + virtual QString screenSize() const { return screenSizeName; } + virtual std::unique_ptr<PresetItem> clone() const + { + return std::unique_ptr<PresetItem>{new PresetItem{*this}}; + } + + virtual bool isUserPreset() const { return false; } + virtual UserPresetItem *asUserPreset() { return nullptr; } + std::function<Utils::Wizard *(const Utils::FilePath &path)> create; + +public: + QString wizardName; QString categoryId; QString screenSizeName; QString description; QUrl qmlPath; QString fontIconCode; - std::function<Utils::Wizard *(const Utils::FilePath &path)> create; }; +struct UserPresetItem : public PresetItem +{ + UserPresetItem() = default; + UserPresetItem(const QString &userName, + const QString &wizardName, + const QString &category, + const QString &sizeName = "") + : PresetItem{wizardName, category, sizeName} + , userName{userName} + {} + + QString displayName() const override { return userName; } + std::unique_ptr<PresetItem> clone() const override + { + return std::unique_ptr<PresetItem>{new UserPresetItem{*this}}; + } + + bool isUserPreset() const override { return true; } + UserPresetItem *asUserPreset() override { return this; } + + bool isValid() const + { + return !categoryId.isEmpty() && !wizardName.isEmpty() && !userName.isEmpty(); + } + +public: + QString userName; + bool useQtVirtualKeyboard; + QString qtVersion; + QString styleName; +}; + +inline QDebug &operator<<(QDebug &d, const UserPresetItem &item); + inline QDebug &operator<<(QDebug &d, const PresetItem &item) { - d << "name=" << item.name; + d << "wizardName=" << item.wizardName; d << "; category = " << item.categoryId; d << "; size = " << item.screenSizeName; + if (item.isUserPreset()) + d << (UserPresetItem &) item; + + return d; +} + +inline QDebug &operator<<(QDebug &d, const UserPresetItem &item) +{ + d << "userName=" << item.userName; + return d; } inline bool operator==(const PresetItem &lhs, const PresetItem &rhs) { - return lhs.categoryId == rhs.categoryId && lhs.name == rhs.name; + return lhs.categoryId == rhs.categoryId && lhs.wizardName == rhs.wizardName; } +using PresetItems = std::vector<std::shared_ptr<PresetItem>>; + struct WizardCategory { QString id; QString name; - std::vector<PresetItem> items; + PresetItems items; }; +inline QDebug &operator<<(QDebug &d, const std::shared_ptr<PresetItem> &preset) +{ + if (preset) + d << *preset; + else + d << "(null)"; + + return d; +} + inline QDebug &operator<<(QDebug &d, const WizardCategory &cat) { d << "id=" << cat.id; @@ -82,7 +161,6 @@ inline QDebug &operator<<(QDebug &d, const WizardCategory &cat) } using PresetsByCategory = std::map<QString, WizardCategory>; -using PresetItems = std::vector<PresetItem>; using Categories = std::vector<QString>; /****************** PresetData ******************/ @@ -90,18 +168,28 @@ using Categories = std::vector<QString>; class PresetData { public: - void setData(const PresetsByCategory &presets, const std::vector<RecentPreset> &recents); + void reload(const std::vector<UserPresetData> &userPresets, + const std::vector<RecentPresetData> &loadedRecents); + void setData(const PresetsByCategory &presets, + const std::vector<UserPresetData> &userPresets, + const std::vector<RecentPresetData> &recents); const std::vector<PresetItems> &presets() const { return m_presets; } const Categories &categories() const { return m_categories; } private: - std::vector<PresetItem> makeRecentPresets(const PresetItems &wizardPresets); + PresetItems makeRecentPresets(const PresetItems &wizardPresets); + PresetItems makeUserPresets(const PresetItems &wizardPresets); + + std::shared_ptr<PresetItem> findPresetItemForUserPreset(const UserPresetData &preset, const PresetItems &wizardPresets); + std::shared_ptr<PresetItem> findPresetItemForRecent(const RecentPresetData &recent, const PresetItems &wizardPresets); private: std::vector<PresetItems> m_presets; Categories m_categories; - std::vector<RecentPreset> m_recents; + std::vector<RecentPresetData> m_recents; + std::vector<UserPresetData> m_userPresets; + PresetsByCategory m_presetsByCategory; }; /****************** PresetCategoryModel ******************/ @@ -149,14 +237,14 @@ public: int page() const { return static_cast<int>(m_page); } - Utils::optional<PresetItem> preset(size_t selection) const + const std::shared_ptr<PresetItem> preset(size_t selection) const { auto presets = m_data->presets(); if (presets.empty()) return {}; if (m_page < presets.size()) { - const std::vector<PresetItem> presetsOfCategory = presets.at(m_page); + const PresetItems presetsOfCategory = presets.at(m_page); if (selection < presetsOfCategory.size()) return presets.at(m_page).at(selection); } @@ -166,8 +254,10 @@ public: bool empty() const { return m_data->presets().empty(); } private: - const std::vector<PresetItem> presetsOfCurrentCategory() const + const PresetItems presetsOfCurrentCategory() const { + QTC_ASSERT(m_page < m_data->presets().size(), return {}); + return m_data->presets().at(m_page); } diff --git a/src/plugins/studiowelcome/qdsnewdialog.cpp b/src/plugins/studiowelcome/qdsnewdialog.cpp index 0324a7de93c..88b7b0fa55f 100644 --- a/src/plugins/studiowelcome/qdsnewdialog.cpp +++ b/src/plugins/studiowelcome/qdsnewdialog.cpp @@ -76,22 +76,14 @@ QdsNewDialog::QdsNewDialog(QWidget *parent) { setParent(m_dialog); - m_dialog->rootContext()->setContextProperties(QVector<QQmlContext::PropertyPair>{ - {{"categoryModel"}, QVariant::fromValue(m_categoryModel.data())}, - {{"presetModel"}, QVariant::fromValue(m_presetModel.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()); + qmlRegisterSingletonInstance<QdsNewDialog>("BackendApi", 1, 0, "BackendApi", this); 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->setSource(QUrl::fromLocalFile(qmlPath())); m_dialog->setWindowModality(Qt::ApplicationModal); m_dialog->setWindowFlags(Qt::Dialog); @@ -114,7 +106,7 @@ QdsNewDialog::QdsNewDialog(QWidget *parent) }); QObject::connect(m_styleModel.data(), &StyleModel::modelAboutToBeReset, this, [this]() { - this->m_qmlStyleIndex = -1; + m_qmlStyleIndex = -1; }); } @@ -188,18 +180,47 @@ void QdsNewDialog::onWizardCreated(QStandardItemModel *screenSizeModel, QStandar m_screenSizeModel->setBackendModel(screenSizeModel); m_styleModel->setBackendModel(styleModel); + auto userPreset = m_currentPreset->asUserPreset(); + if (m_qmlDetailsLoaded) { - updateScreenSizes(); + if (m_currentPreset->isUserPreset()) { + if (m_wizard.haveVirtualKeyboard()) + setUseVirtualKeyboard(userPreset->useQtVirtualKeyboard); + + if (m_wizard.haveTargetQtVersion()) { + int index = m_wizard.targetQtVersionIndex(userPreset->qtVersion); + if (index != -1) + setTargetQtVersionIndex(index); + } + } else { + if (m_wizard.haveTargetQtVersion()) { + int index = m_wizard.targetQtVersionIndex(); + if (index != -1) + setTargetQtVersionIndex(index); + } + } emit haveVirtualKeyboardChanged(); emit haveTargetQtVersionChanged(); + updateScreenSizes(); + setProjectName(m_qmlProjectName); setProjectLocation(m_qmlProjectLocation.toString()); } - if (m_qmlStylesLoaded) + if (m_qmlStylesLoaded && m_wizard.haveStyleModel()) { + if (m_currentPreset->isUserPreset()) { + int index = m_wizard.styleIndex(userPreset->styleName); + if (index != -1) + setStyleIndex(index); + } else { + /* NOTE: For a builtin preset, we don't need to set style index. That's because defaults + * will be loaded from the backend Wizard. + */ + } m_styleModel->reset(); + } } QString QdsNewDialog::currentPresetQmlPath() const @@ -221,10 +242,19 @@ int QdsNewDialog::screenSizeIndex() const return m_wizard.screenSizeIndex(); } -void QdsNewDialog::setTargetQtVersion(int index) +void QdsNewDialog::setTargetQtVersionIndex(int index) +{ + if (m_qmlTargetQtVersionIndex != index) { + m_wizard.setTargetQtVersionIndex(index); + m_qmlTargetQtVersionIndex = index; + + emit targetQtVersionIndexChanged(); + } +} + +int QdsNewDialog::getTargetQtVersionIndex() const { - m_wizard.setTargetQtVersionIndex(index); - m_qmlTargetQtVersionIndex = index; + return m_qmlTargetQtVersionIndex; } void QdsNewDialog::setStyleIndex(int index) @@ -267,6 +297,14 @@ int QdsNewDialog::getStyleIndex() const return m_styleModel->actualIndex(m_qmlStyleIndex); } +void QdsNewDialog::setUseVirtualKeyboard(bool value) +{ + if (m_qmlUseVirtualKeyboard != value) { + m_qmlUseVirtualKeyboard = value; + emit useVirtualKeyboardChanged(); + } +} + void QdsNewDialog::setWizardFactories(QList<Core::IWizardFactory *> factories_, const Utils::FilePath &defaultLocation, const QVariantMap &) @@ -275,8 +313,9 @@ void QdsNewDialog::setWizardFactories(QList<Core::IWizardFactory *> factories_, WizardFactories factories{factories_, m_dialog, platform}; - std::vector<RecentPreset> recents = m_recentsStore.fetchAll(); - m_presetData.setData(factories.presetsGroupedByCategory(), recents); + std::vector<RecentPresetData> recents = m_recentsStore.fetchAll(); + std::vector<UserPresetData> userPresets = m_userPresetsStore.fetchAll(); + m_presetData.setData(factories.presetsGroupedByCategory(), userPresets, recents); m_categoryModel->reset(); m_presetModel->reset(); @@ -296,12 +335,45 @@ void QdsNewDialog::setWizardFactories(QList<Core::IWizardFactory *> factories_, m_qmlProjectLocation = Utils::FilePath::fromString(QDir::toNativeSeparators(projectLocation.toString())); emit projectLocationChanged(); // So that QML knows to update the field + /* NOTE: + * Here we expect that details are loaded && that styles are loaded. We use the + * functionality below to update the state of the first item that is selected right when + * the dialog pops up. Otherwise, relying solely on onWizardCreated is not useful, since + * for the dialog popup, that wizard is created before details & styles are loaded. + * + * It might be a better alternative to receive notifications from QML in the cpp file, that + * style is loaded, and that details is loaded, and do updates from there. But, if we handle + * those events, they may be called before the wizard is created - so we would need to make + * sure that all events have occurred before we go ahead and configure the wizard. + */ + + auto userPreset = m_currentPreset->asUserPreset(); + if (m_qmlDetailsLoaded) { updateScreenSizes(); + + if (m_wizard.haveTargetQtVersion()) { + int index = (userPreset ? m_wizard.targetQtVersionIndex(userPreset->qtVersion) + : m_wizard.targetQtVersionIndex()); + if (index != -1) + setTargetQtVersionIndex(index); + } + + if (m_wizard.haveVirtualKeyboard() && userPreset) + setUseVirtualKeyboard(userPreset->useQtVirtualKeyboard); + + emit haveVirtualKeyboardChanged(); + emit haveTargetQtVersionChanged(); } - if (m_qmlStylesLoaded) + if (m_qmlStylesLoaded && m_wizard.haveStyleModel()) { + if (userPreset) { + int index = m_wizard.styleIndex(userPreset->styleName); + if (index != -1) + setStyleIndex(index); + } m_styleModel->reset(); + } } QString QdsNewDialog::qmlPath() const @@ -338,10 +410,10 @@ void QdsNewDialog::accept() .withTargetQtVersion(m_qmlTargetQtVersionIndex) .execute(); - PresetItem item = m_wizard.preset(); + std::shared_ptr<PresetItem> item = m_wizard.preset(); QString customSizeName = m_qmlCustomWidth + " x " + m_qmlCustomHeight; - m_recentsStore.add(item.categoryId, item.name, customSizeName); + m_recentsStore.add(item->categoryId, item->displayName(), customSizeName, item->isUserPreset()); m_dialog->close(); m_dialog->deleteLater(); @@ -355,6 +427,7 @@ void QdsNewDialog::reject() m_wizard.destroyWizard(); m_dialog->close(); + m_dialog = nullptr; } QString QdsNewDialog::chooseProjectLocation() @@ -375,7 +448,76 @@ void QdsNewDialog::setSelectedPreset(int selection) setProjectDescription(m_currentPreset->description); m_presetPage = m_presetModel->page(); - m_wizard.reset(m_currentPreset.value(), m_qmlSelectedPreset); + m_wizard.reset(m_currentPreset, m_qmlSelectedPreset); } } } + +void QdsNewDialog::savePresetDialogAccept() +{ + QString screenSize = m_qmlCustomWidth + " x " + m_qmlCustomHeight; + QString targetQtVersion = ""; + QString styleName = ""; + bool useVirtualKeyboard = false; + + if (m_wizard.haveTargetQtVersion()) + targetQtVersion = m_wizard.targetQtVersionName(m_qmlTargetQtVersionIndex); + + if (m_wizard.haveStyleModel()) + styleName = m_wizard.styleName(m_qmlStyleIndex); + + if (m_wizard.haveVirtualKeyboard()) + useVirtualKeyboard = m_qmlUseVirtualKeyboard; + + UserPresetData preset = {m_currentPreset->categoryId, + m_currentPreset->wizardName, + m_qmlPresetName, + screenSize, + useVirtualKeyboard, + targetQtVersion, + styleName}; + + if (!m_userPresetsStore.save(preset)) { + QMessageBox::warning(m_dialog, + tr("Save Preset"), + tr("A preset with this name already exists.")); + return; + } + + // reload model + std::vector<RecentPresetData> recents = m_recentsStore.fetchAll(); + std::vector<UserPresetData> userPresets = m_userPresetsStore.fetchAll(); + m_presetData.reload(userPresets, recents); + + m_categoryModel->reset(); + + emit userPresetSaved(); +} + +void QdsNewDialog::removeCurrentPreset() +{ + if (!m_currentPreset->isUserPreset()) { + qWarning() << "Will not attempt to remove non-user preset"; + return; + } + + // remove preset & reload model + std::vector<RecentPresetData> recents = m_recentsStore.remove(m_currentPreset->categoryId, + m_currentPreset->displayName()); + + auto userPreset = m_currentPreset->asUserPreset(); + m_userPresetsStore.remove(userPreset->categoryId, userPreset->displayName()); + std::vector<UserPresetData> userPresets = m_userPresetsStore.fetchAll(); + m_presetData.reload(userPresets, recents); + + m_qmlSelectedPreset = -1; + m_presetPage = -1; + + if (userPresets.size() == 0) { + m_presetModel->setPage(0); + emit lastUserPresetRemoved(); + } + + m_categoryModel->reset(); + m_presetModel->reset(); +} diff --git a/src/plugins/studiowelcome/qdsnewdialog.h b/src/plugins/studiowelcome/qdsnewdialog.h index a4afa69e927..bb5e44343fd 100644 --- a/src/plugins/studiowelcome/qdsnewdialog.h +++ b/src/plugins/studiowelcome/qdsnewdialog.h @@ -29,13 +29,13 @@ #include <coreplugin/dialogs/newdialog.h> #include <utils/infolabel.h> -#include <utils/optional.h> #include "wizardhandler.h" #include "presetmodel.h" #include "screensizemodel.h" #include "stylemodel.h" #include "recentpresets.h" +#include "userpresets.h" QT_BEGIN_NAMESPACE class QStandardItemModel; @@ -57,22 +57,31 @@ public: 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(int targetQtVersionIndex MEMBER m_qmlTargetQtVersionIndex READ getTargetQtVersionIndex WRITE setTargetQtVersionIndex NOTIFY targetQtVersionIndexChanged) 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(QString presetName MEMBER m_qmlPresetName) Q_PROPERTY(bool detailsLoaded MEMBER m_qmlDetailsLoaded) Q_PROPERTY(bool stylesLoaded MEMBER m_qmlStylesLoaded) + Q_INVOKABLE void removeCurrentPreset(); Q_INVOKABLE QString currentPresetQmlPath() const; // TODO: screen size index should better be a property Q_INVOKABLE void setScreenSizeIndex(int index); // called when ComboBox item is "activated" Q_INVOKABLE int screenSizeIndex() const; - Q_INVOKABLE void setTargetQtVersion(int index); Q_INVOKABLE QString chooseProjectLocation(); + Q_PROPERTY(QAbstractListModel *categoryModel MEMBER m_categoryModel CONSTANT); + Q_PROPERTY(QAbstractListModel *presetModel MEMBER m_presetModel CONSTANT); + Q_PROPERTY(QAbstractListModel *screenSizeModel MEMBER m_screenSizeModel CONSTANT); + Q_PROPERTY(QAbstractListModel *styleModel MEMBER m_styleModel CONSTANT); + + /*********************/ + explicit QdsNewDialog(QWidget *parent); QWidget *widget() override { return m_dialog; } @@ -85,7 +94,11 @@ public: void setStyleIndex(int index); int getStyleIndex() const; - void setUseVirtualKeyboard(bool value) { m_qmlUseVirtualKeyboard = value; } + + void setTargetQtVersionIndex(int index); + int getTargetQtVersionIndex() const; + + void setUseVirtualKeyboard(bool value); bool getUseVirtualKeyboard() const { return m_qmlUseVirtualKeyboard; } bool getFieldsValid() const { return m_qmlFieldsValid; } @@ -101,6 +114,8 @@ public slots: void accept(); void reject(); + void savePresetDialogAccept(); + signals: void projectNameChanged(); void projectLocationChanged(); @@ -111,6 +126,9 @@ signals: void statusMessageChanged(); void statusTypeChanged(); void fieldsValidChanged(); + void targetQtVersionIndexChanged(); + void userPresetSaved(); + void lastUserPresetRemoved(); private slots: void onStatusMessageChanged(Utils::InfoLabel::InfoType type, const QString &message); @@ -160,6 +178,7 @@ private: bool m_qmlFieldsValid = false; QString m_qmlStatusMessage; QString m_qmlStatusType; + QString m_qmlPresetName; int m_presetPage = -1; // i.e. the page in the Presets View @@ -169,10 +188,11 @@ private: bool m_qmlDetailsLoaded = false; bool m_qmlStylesLoaded = false; - Utils::optional<PresetItem> m_currentPreset; + std::shared_ptr<PresetItem> m_currentPreset; WizardHandler m_wizard; RecentPresetsStore m_recentsStore; + UserPresetsStore m_userPresetsStore; }; } //namespace StudioWelcome diff --git a/src/plugins/studiowelcome/recentpresets.cpp b/src/plugins/studiowelcome/recentpresets.cpp index f6e1335b479..cad2846d757 100644 --- a/src/plugins/studiowelcome/recentpresets.cpp +++ b/src/plugins/studiowelcome/recentpresets.cpp @@ -32,19 +32,27 @@ #include <utils/qtcassert.h> #include <utils/qtcsettings.h> -using Core::ICore; -using Utils::QtcSettings; - using namespace StudioWelcome; constexpr char GROUP_NAME[] = "RecentPresets"; constexpr char WIZARDS[] = "Wizards"; -void RecentPresetsStore::add(const QString &categoryId, const QString &name, const QString &sizeName) +void RecentPresetsStore::add(const QString &categoryId, + const QString &name, + const QString &sizeName, + bool isUserPreset) { - std::vector<RecentPreset> existing = fetchAll(); - QStringList encodedRecents = addRecentToExisting(RecentPreset{categoryId, name, sizeName}, - existing); + std::vector<RecentPresetData> existing = fetchAll(); + + std::vector<RecentPresetData> recents + = addRecentToExisting(RecentPresetData{categoryId, name, sizeName, isUserPreset}, existing); + + save(recents); +} + +void RecentPresetsStore::save(const std::vector<RecentPresetData> &recents) +{ + QStringList encodedRecents = encodeRecentPresets(recents); m_settings->beginGroup(GROUP_NAME); m_settings->setValue(WIZARDS, encodedRecents); @@ -52,8 +60,26 @@ void RecentPresetsStore::add(const QString &categoryId, const QString &name, con m_settings->sync(); } -QStringList RecentPresetsStore::addRecentToExisting(const RecentPreset &preset, - std::vector<RecentPreset> &recents) +std::vector<RecentPresetData> RecentPresetsStore::remove(const QString &categoryId, const QString &presetName) +{ + std::vector<RecentPresetData> recents = fetchAll(); + size_t countBefore = recents.size(); + + /* NOTE: when removing one preset, it may happen that there are more than one recent for that + * preset. In that case, we need to remove all associated recents, for the preset.*/ + + Utils::erase(recents, [&](const RecentPresetData &p) { + return p.category == categoryId && p.presetName == presetName; + }); + + if (recents.size() < countBefore) + save(recents); + + return recents; +} + +std::vector<RecentPresetData> RecentPresetsStore::addRecentToExisting( + const RecentPresetData &preset, std::vector<RecentPresetData> &recents) { Utils::erase_one(recents, preset); Utils::prepend(recents, preset); @@ -61,48 +87,64 @@ QStringList RecentPresetsStore::addRecentToExisting(const RecentPreset &preset, if (int(recents.size()) > m_max) recents.pop_back(); - return encodeRecentPresets(recents); + return recents; } -std::vector<RecentPreset> RecentPresetsStore::fetchAll() const +std::vector<RecentPresetData> RecentPresetsStore::fetchAll() const { m_settings->beginGroup(GROUP_NAME); QVariant value = m_settings->value(WIZARDS); m_settings->endGroup(); - std::vector<RecentPreset> result; + std::vector<RecentPresetData> result; if (value.type() == QVariant::String) result.push_back(decodeOneRecentPreset(value.toString())); else if (value.type() == QVariant::StringList) Utils::concat(result, decodeRecentPresets(value.toList())); - const RecentPreset empty; - return Utils::filtered(result, [&empty](const RecentPreset &recent) { return recent != empty; }); + const RecentPresetData empty; + return Utils::filtered(result, [&empty](const RecentPresetData &recent) { return recent != empty; }); } -QStringList RecentPresetsStore::encodeRecentPresets(const std::vector<RecentPreset> &recents) +QStringList RecentPresetsStore::encodeRecentPresets(const std::vector<RecentPresetData> &recents) { - return Utils::transform<QList>(recents, [](const RecentPreset &p) -> QString { - return std::get<0>(p) + "/" + std::get<1>(p) + ":" + std::get<2>(p); + return Utils::transform<QList>(recents, [](const RecentPresetData &p) -> QString { + QString name = p.presetName; + if (p.isUserPreset) + name.prepend("[U]"); + + return p.category + "/" + name + ":" + p.sizeName; }); } -RecentPreset RecentPresetsStore::decodeOneRecentPreset(const QString &encoded) +RecentPresetData RecentPresetsStore::decodeOneRecentPreset(const QString &encoded) { - QRegularExpression pattern{R"(^(\S+)/(.+):(\d+ x \d+))"}; + QRegularExpression pattern{R"(^(\S+)/(.+):(\d+ x \d+)$)"}; auto m = pattern.match(encoded); if (!m.hasMatch()) - return RecentPreset{}; + return RecentPresetData{}; QString category = m.captured(1); QString name = m.captured(2); QString size = m.captured(3); + bool isUserPreset = name.startsWith("[U]"); + if (isUserPreset) + name = name.split("[U]")[1]; + + if (!QRegularExpression{R"(^\w[\w ]*$)"}.match(name).hasMatch()) + return RecentPresetData{}; + + RecentPresetData result; + result.category = category; + result.presetName = name; + result.sizeName = size; + result.isUserPreset = isUserPreset; - return std::make_tuple(category, name, size); + return result; } -std::vector<RecentPreset> RecentPresetsStore::decodeRecentPresets(const QVariantList &values) +std::vector<RecentPresetData> RecentPresetsStore::decodeRecentPresets(const QVariantList &values) { return Utils::transform<std::vector>(values, [](const QVariant &value) { return decodeOneRecentPreset(value.toString()); diff --git a/src/plugins/studiowelcome/recentpresets.h b/src/plugins/studiowelcome/recentpresets.h index 3c223d8df55..83ab5fb8cc5 100644 --- a/src/plugins/studiowelcome/recentpresets.h +++ b/src/plugins/studiowelcome/recentpresets.h @@ -31,8 +31,43 @@ namespace StudioWelcome { -// preset category, preset name, size name -using RecentPreset = std::tuple<QString, QString, QString>; +struct RecentPresetData +{ + RecentPresetData() = default; + RecentPresetData(const QString &category, + const QString &name, + const QString &size, + bool isUserPreset = false) + : category{category} + , presetName{name} + , sizeName{size} + , isUserPreset{isUserPreset} + {} + + QString category; + QString presetName; + QString sizeName; + bool isUserPreset = false; +}; + +inline bool operator==(const RecentPresetData &lhs, const RecentPresetData &rhs) +{ + return lhs.category == rhs.category && lhs.presetName == rhs.presetName + && lhs.sizeName == rhs.sizeName && lhs.isUserPreset == rhs.isUserPreset; +} + +inline bool operator!=(const RecentPresetData &lhs, const RecentPresetData &rhs) +{ + return !(lhs == rhs); +} + +inline QDebug &operator<<(QDebug &d, const RecentPresetData &preset) +{ + d << "RecentPreset{category=" << preset.category << "; name=" << preset.presetName + << "; size=" << preset.sizeName << "; isUserPreset=" << preset.isUserPreset << "}"; + + return d; +} class RecentPresetsStore { @@ -42,14 +77,21 @@ public: {} void setMaximum(int n) { m_max = n; } - void add(const QString &categoryId, const QString &name, const QString &sizeName); - std::vector<RecentPreset> fetchAll() const; + void add(const QString &categoryId, + const QString &name, + const QString &sizeName, + bool isUserPreset = false); + + std::vector<RecentPresetData> remove(const QString &categoryId, const QString &presetName); + std::vector<RecentPresetData> fetchAll() const; private: - QStringList addRecentToExisting(const RecentPreset &preset, std::vector<RecentPreset> &recents); - static QStringList encodeRecentPresets(const std::vector<RecentPreset> &recents); - static std::vector<RecentPreset> decodeRecentPresets(const QVariantList &values); - static RecentPreset decodeOneRecentPreset(const QString &encoded); + std::vector<RecentPresetData> addRecentToExisting(const RecentPresetData &preset, + std::vector<RecentPresetData> &recents); + static QStringList encodeRecentPresets(const std::vector<RecentPresetData> &recents); + static std::vector<RecentPresetData> decodeRecentPresets(const QVariantList &values); + static RecentPresetData decodeOneRecentPreset(const QString &encoded); + void save(const std::vector<RecentPresetData> &recents); private: QSettings *m_settings = nullptr; diff --git a/src/plugins/studiowelcome/studiowelcome.qbs b/src/plugins/studiowelcome/studiowelcome.qbs index cc655ea5a23..9648eda14d3 100644 --- a/src/plugins/studiowelcome/studiowelcome.qbs +++ b/src/plugins/studiowelcome/studiowelcome.qbs @@ -38,7 +38,9 @@ QtcPlugin { "wizardhandler.cpp", "wizardhandler.h", "recentpresets.cpp", - "recentpresets.h" + "recentpresets.h", + "userpresets.cpp", + "userpresets.h" ] Group { diff --git a/src/plugins/studiowelcome/userpresets.cpp b/src/plugins/studiowelcome/userpresets.cpp new file mode 100644 index 00000000000..d468c3522eb --- /dev/null +++ b/src/plugins/studiowelcome/userpresets.cpp @@ -0,0 +1,125 @@ +/**************************************************************************** +** +** Copyright (C) 2022 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 "userpresets.h" + +#include <coreplugin/icore.h> +#include <utils/algorithm.h> +#include <utils/qtcassert.h> + +using namespace StudioWelcome; + +constexpr char PREFIX[] = "UserPresets"; + +UserPresetsStore::UserPresetsStore() +{ + m_settings = std::make_unique<QSettings>(fullFilePath(), QSettings::IniFormat); +} + +UserPresetsStore::UserPresetsStore(std::unique_ptr<QSettings> &&settings) + : m_settings{std::move(settings)} +{} + +void UserPresetsStore::savePresets(const std::vector<UserPresetData> &presets) +{ + m_settings->beginWriteArray(PREFIX, static_cast<int>(presets.size())); + + for (size_t i = 0; i < presets.size(); ++i) { + m_settings->setArrayIndex(static_cast<int>(i)); + const auto &preset = presets[i]; + + m_settings->setValue("categoryId", preset.categoryId); + m_settings->setValue("wizardName", preset.wizardName); + m_settings->setValue("name", preset.name); + m_settings->setValue("screenSize", preset.screenSize); + m_settings->setValue("useQtVirtualKeyboard", preset.useQtVirtualKeyboard); + m_settings->setValue("qtVersion", preset.qtVersion); + m_settings->setValue("styleName", preset.styleName); + } + m_settings->endArray(); + m_settings->sync(); + +} + +bool UserPresetsStore::save(const UserPresetData &newPreset) +{ + QTC_ASSERT(newPreset.isValid(), return false); + + std::vector<UserPresetData> presetItems = fetchAll(); + if (Utils::anyOf(presetItems, + [&newPreset](const UserPresetData &p) { return p.name == newPreset.name; })) { + return false; + } + + presetItems.push_back(newPreset); + savePresets(presetItems); + + return true; +} + +void UserPresetsStore::remove(const QString &category, const QString &name) +{ + std::vector<UserPresetData> presetItems = fetchAll(); + auto item = Utils::take(presetItems, [&](const UserPresetData &p) { + return p.categoryId == category && p.name == name; + }); + + if (!item) + return; + + savePresets(presetItems); +} + +std::vector<UserPresetData> UserPresetsStore::fetchAll() const +{ + std::vector<UserPresetData> result; + int size = m_settings->beginReadArray(PREFIX); + if (size >= 0) + result.reserve(static_cast<size_t>(size) + 1); + + for (int i = 0; i < size; ++i) { + m_settings->setArrayIndex(i); + + UserPresetData preset; + preset.categoryId = m_settings->value("categoryId").toString(); + preset.wizardName = m_settings->value("wizardName").toString(); + preset.name = m_settings->value("name").toString(); + preset.screenSize = m_settings->value("screenSize").toString(); + preset.useQtVirtualKeyboard = m_settings->value("useQtVirtualKeyboard").toBool(); + preset.qtVersion = m_settings->value("qtVersion").toString(); + preset.styleName = m_settings->value("styleName").toString(); + + if (preset.isValid()) + result.push_back(std::move(preset)); + } + m_settings->endArray(); + + return result; +} + +QString UserPresetsStore::fullFilePath() const +{ + return Core::ICore::userResourcePath("UserPresets.ini").toString(); +} diff --git a/src/plugins/studiowelcome/userpresets.h b/src/plugins/studiowelcome/userpresets.h new file mode 100644 index 00000000000..4f6053ef260 --- /dev/null +++ b/src/plugins/studiowelcome/userpresets.h @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** Copyright (C) 2022 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 <vector> +#include <QSettings> + +namespace StudioWelcome { + +struct UserPresetData +{ + QString categoryId; + QString wizardName; + QString name; + QString screenSize; + + bool useQtVirtualKeyboard; + QString qtVersion; + QString styleName; + + bool isValid() const + { + return !categoryId.isEmpty() + && !wizardName.isEmpty() + && !name.isEmpty(); + } +}; + +inline QDebug &operator<<(QDebug &d, const UserPresetData &preset) +{ + d << "UserPreset{category = " << preset.categoryId; + d << "; wizardName = " << preset.wizardName; + d << "; name = " << preset.name; + d << "; screenSize = " << preset.screenSize; + d << "; keyboard = " << preset.useQtVirtualKeyboard; + d << "; qt = " << preset.qtVersion; + d << "; style = " << preset.styleName; + d << "}"; + + return d; +} + +inline bool operator==(const UserPresetData &lhs, const UserPresetData &rhs) +{ + return lhs.categoryId == rhs.categoryId && lhs.wizardName == rhs.wizardName + && lhs.name == rhs.name && lhs.screenSize == rhs.screenSize + && lhs.useQtVirtualKeyboard == rhs.useQtVirtualKeyboard && lhs.qtVersion == rhs.qtVersion + && lhs.styleName == rhs.styleName; +} + +class UserPresetsStore +{ +public: + UserPresetsStore(); + UserPresetsStore(std::unique_ptr<QSettings> &&settings); + + bool save(const UserPresetData &preset); + void remove(const QString &category, const QString &name); + std::vector<UserPresetData> fetchAll() const; + +private: + QString fullFilePath() const; + void savePresets(const std::vector<UserPresetData> &presets); + + std::unique_ptr<QSettings> m_settings; +}; + +} // namespace StudioWelcome diff --git a/src/plugins/studiowelcome/wizardfactories.cpp b/src/plugins/studiowelcome/wizardfactories.cpp index d374a5258c8..e271d5572e4 100644 --- a/src/plugins/studiowelcome/wizardfactories.cpp +++ b/src/plugins/studiowelcome/wizardfactories.cpp @@ -76,7 +76,7 @@ void WizardFactories::filter() m_factories = acceptedFactories; } -PresetItem WizardFactories::makePresetItem(JsonWizardFactory *f, QWidget *parent, +std::shared_ptr<PresetItem> WizardFactories::makePresetItem(JsonWizardFactory *f, QWidget *parent, const Utils::Id &platform) { using namespace std::placeholders; @@ -89,16 +89,17 @@ PresetItem WizardFactories::makePresetItem(JsonWizardFactory *f, QWidget *parent else sizeName = screenSizes[index]; - return { - /*.name =*/f->displayName(), - /*.categoryId =*/f->category(), - /*.screenSizeName=*/sizeName, - /*.description =*/f->description(), - /*.qmlPath =*/f->detailsPageQmlPath(), - /*.fontIconCode =*/m_getIconUnicode(f->fontIconName()), - /*.create =*/ std::bind(&JsonWizardFactory::runWizard, f, _1, parent, platform, - QVariantMap(), false), - }; + auto result = std::make_shared<PresetItem>(); + result->wizardName = f->displayName(); + result->categoryId = f->category(); + result->screenSizeName=sizeName; + result->description = f->description(); + result->qmlPath = f->detailsPageQmlPath(); + result->fontIconCode = m_getIconUnicode(f->fontIconName()); + result->create + = std::bind(&JsonWizardFactory::runWizard, f, _1, parent, platform, QVariantMap(), false); + + return result; } std::map<QString, WizardCategory> WizardFactories::makePresetItemsGroupedByCategory() diff --git a/src/plugins/studiowelcome/wizardfactories.h b/src/plugins/studiowelcome/wizardfactories.h index aa209362de6..c98fd5e1027 100644 --- a/src/plugins/studiowelcome/wizardfactories.h +++ b/src/plugins/studiowelcome/wizardfactories.h @@ -64,7 +64,7 @@ private: void sortByCategoryAndId(); void filter(); - PresetItem makePresetItem(JsonWizardFactory *f, QWidget *parent, const Utils::Id &platform); + std::shared_ptr<PresetItem> makePresetItem(JsonWizardFactory *f, QWidget *parent, const Utils::Id &platform); std::map<QString, WizardCategory> makePresetItemsGroupedByCategory(); private: diff --git a/src/plugins/studiowelcome/wizardhandler.cpp b/src/plugins/studiowelcome/wizardhandler.cpp index cd176707ac9..56c53fcad92 100644 --- a/src/plugins/studiowelcome/wizardhandler.cpp +++ b/src/plugins/studiowelcome/wizardhandler.cpp @@ -38,7 +38,7 @@ using namespace StudioWelcome; -void WizardHandler::reset(const PresetItem &presetInfo, int presetSelection) +void WizardHandler::reset(const std::shared_ptr<PresetItem> &presetInfo, int presetSelection) { m_preset = presetInfo; m_selectedPreset = presetSelection; @@ -67,7 +67,7 @@ void WizardHandler::destroyWizard() void WizardHandler::setupWizard() { - m_wizard = m_preset.create(m_projectLocation); + m_wizard = m_preset->create(m_projectLocation); if (!m_wizard) { emit wizardCreationFailed(); return; @@ -142,6 +142,11 @@ QStandardItemModel *WizardHandler::getScreenFactorModel(ProjectExplorer::JsonFie return cbfield->model(); } +bool WizardHandler::haveStyleModel() const +{ + return m_wizard->hasField("ControlsStyle"); +} + QStandardItemModel *WizardHandler::getStyleModel(ProjectExplorer::JsonFieldPage *page) { auto *field = page->jsonField("ControlsStyle"); @@ -229,6 +234,47 @@ bool WizardHandler::haveTargetQtVersion() const return m_wizard->hasField("TargetQtVersion"); } +QString WizardHandler::targetQtVersionName(int index) const +{ + auto *field = m_detailsPage->jsonField("TargetQtVersion"); + auto *cbfield = dynamic_cast<ProjectExplorer::ComboBoxField *>(field); + QTC_ASSERT(cbfield, return ""); + + QStandardItemModel *model = cbfield->model(); + if (index < 0 || index >= model->rowCount()) + return {}; + + QString text = model->item(index)->text(); + return text; +} + +int WizardHandler::targetQtVersionIndex(const QString &qtVersionName) const +{ + auto *field = m_detailsPage->jsonField("TargetQtVersion"); + auto *cbfield = dynamic_cast<ProjectExplorer::ComboBoxField *>(field); + QTC_ASSERT(cbfield, return -1); + + const QStandardItemModel *model = cbfield->model(); + for (int i = 0; i < model->rowCount(); ++i) { + const QStandardItem *item = model->item(i, 0); + const QString text = item->text(); + + if (text == qtVersionName) + return i; + } + + return -1; +} + +int WizardHandler::targetQtVersionIndex() const +{ + auto *field = m_detailsPage->jsonField("TargetQtVersion"); + auto *cbfield = dynamic_cast<ProjectExplorer::ComboBoxField *>(field); + QTC_ASSERT(cbfield, return -1); + + return cbfield->selectedRow(); +} + void WizardHandler::setStyleIndex(int index) { auto *field = m_detailsPage->jsonField("ControlsStyle"); @@ -247,6 +293,38 @@ int WizardHandler::styleIndex() const return cbfield->selectedRow(); } +int WizardHandler::styleIndex(const QString &styleName) const +{ + auto *field = m_detailsPage->jsonField("ControlsStyle"); + auto *cbfield = dynamic_cast<ProjectExplorer::ComboBoxField *>(field); + QTC_ASSERT(cbfield, return -1); + + const QStandardItemModel *model = cbfield->model(); + for (int i = 0; i < model->rowCount(); ++i) { + const QStandardItem *item = model->item(i, 0); + const QString text = item->text(); + + if (text == styleName) + return i; + } + + return -1; +} + +QString WizardHandler::styleName(int index) const +{ + auto *field = m_detailsPage->jsonField("ControlsStyle"); + auto *cbfield = dynamic_cast<ProjectExplorer::ComboBoxField *>(field); + QTC_ASSERT(cbfield, return ""); + + QStandardItemModel *model = cbfield->model(); + if (index < 0 || index >= model->rowCount()) + return {}; + + QString text = model->item(index)->text(); + return text; +} + void WizardHandler::setUseVirtualKeyboard(bool value) { auto *field = m_detailsPage->jsonField("UseVirtualKeyboard"); diff --git a/src/plugins/studiowelcome/wizardhandler.h b/src/plugins/studiowelcome/wizardhandler.h index 112accfb158..ce13b77572f 100644 --- a/src/plugins/studiowelcome/wizardhandler.h +++ b/src/plugins/studiowelcome/wizardhandler.h @@ -47,15 +47,23 @@ class WizardHandler: public QObject Q_OBJECT public: - void reset(const PresetItem &presetInfo, int presetSelection); + void reset(const std::shared_ptr<PresetItem> &presetInfo, int presetSelection); void setScreenSizeIndex(int index); int screenSizeIndex(const QString &sizeName) const; QString screenSizeName(int index) const; int screenSizeIndex() const; + int targetQtVersionIndex() const; + int targetQtVersionIndex(const QString &qtVersionName) const; void setTargetQtVersionIndex(int index); bool haveTargetQtVersion() const; + QString targetQtVersionName(int index) const; + void setStyleIndex(int index); int styleIndex() const; + int styleIndex(const QString &styleName) const; + QString styleName(int index) const; + bool haveStyleModel() const; + void destroyWizard(); void setUseVirtualKeyboard(bool value); @@ -66,7 +74,7 @@ public: void run(const std::function<void (QWizardPage *)> &processPage); - PresetItem preset() const { return m_preset; } + std::shared_ptr<PresetItem> preset() const { return m_preset; } signals: void deletingWizard(); @@ -93,7 +101,7 @@ private: int m_selectedPreset = -1; - PresetItem m_preset; + std::shared_ptr<PresetItem> m_preset; Utils::FilePath m_projectLocation; }; diff --git a/tests/auto/qml/qmldesigner/wizard/CMakeLists.txt b/tests/auto/qml/qmldesigner/wizard/CMakeLists.txt index 0606be1a550..c5040112efd 100644 --- a/tests/auto/qml/qmldesigner/wizard/CMakeLists.txt +++ b/tests/auto/qml/qmldesigner/wizard/CMakeLists.txt @@ -18,12 +18,14 @@ add_qtc_test(tst_qml_wizard wizardfactories-test.cpp stylemodel-test.cpp recentpresets-test.cpp + userpresets-test.cpp presetmodel-test.cpp test-utilities.h test-main.cpp "${StudioWelcomeDir}/wizardfactories.cpp" "${StudioWelcomeDir}/stylemodel.cpp" "${StudioWelcomeDir}/recentpresets.cpp" + "${StudioWelcomeDir}/userpresets.cpp" "${StudioWelcomeDir}/presetmodel.cpp" ) diff --git a/tests/auto/qml/qmldesigner/wizard/presetmodel-test.cpp b/tests/auto/qml/qmldesigner/wizard/presetmodel-test.cpp index dc855b67a33..f2233eee855 100644 --- a/tests/auto/qml/qmldesigner/wizard/presetmodel-test.cpp +++ b/tests/auto/qml/qmldesigner/wizard/presetmodel-test.cpp @@ -33,10 +33,30 @@ using ::testing::ElementsAreArray; using ::testing::PrintToString; namespace StudioWelcome { + +void PrintTo(const UserPresetItem &item, std::ostream *os); + void PrintTo(const PresetItem &item, std::ostream *os) { + if (typeid(item) == typeid(UserPresetItem)) { + PrintTo((UserPresetItem &) item, os); + return; + } + + *os << "{categId: " << item.categoryId << ", " + << "name: " << item.wizardName; + + if (!item.screenSizeName.isEmpty()) + *os << ", size: " << item.screenSizeName; + + *os << "}"; +} + +void PrintTo(const UserPresetItem &item, std::ostream *os) +{ *os << "{categId: " << item.categoryId << ", " - << "name: " << item.name; + << "name: " << item.wizardName << ", " + << "user name: " << item.userName; if (!item.screenSizeName.isEmpty()) *os << ", size: " << item.screenSizeName; @@ -44,6 +64,14 @@ void PrintTo(const PresetItem &item, std::ostream *os) *os << "}"; } +void PrintTo(const std::shared_ptr<PresetItem> &p, std::ostream *os) +{ + if (p) + PrintTo(*p, os); + else + *os << "{null}"; +} + } // namespace StudioWelcome namespace { @@ -51,20 +79,47 @@ std::pair<QString, WizardCategory> aCategory(const QString &categId, const QString &categName, const std::vector<QString> &names) { - std::vector<PresetItem> items = Utils::transform(names, [&categId](const QString &name) { - return PresetItem{name, categId}; - }); + std::vector<std::shared_ptr<PresetItem>> items + = Utils::transform(names, [&categId](const QString &name) { + std::shared_ptr<PresetItem> item{new PresetItem}; + item->wizardName = name; + item->categoryId = categId; + + return item; + }); return std::make_pair(categId, WizardCategory{categId, categName, items}); } +UserPresetData aUserPreset(const QString &categId, const QString &wizardName, const QString &userName) +{ + UserPresetData preset; + preset.categoryId = categId; + preset.wizardName = wizardName; + preset.name = userName; + + return preset; +} + MATCHER_P2(PresetIs, category, name, PrintToString(PresetItem{name, category})) { - return arg.categoryId == category && arg.name == name; + return arg->categoryId == category && arg->wizardName == name; +} + +MATCHER_P3(UserPresetIs, + category, + wizardName, + userName, + PrintToString(UserPresetItem{wizardName, userName, category})) +{ + auto userPreset = dynamic_cast<UserPresetItem *>(arg.get()); + + return userPreset->categoryId == category && userPreset->wizardName == wizardName + && userPreset->userName == userName; } MATCHER_P3(PresetIs, category, name, size, PrintToString(PresetItem{name, category, size})) { - return arg.categoryId == category && arg.name == name && size == arg.screenSizeName; + return arg->categoryId == category && arg->wizardName == name && size == arg->screenSizeName; } } // namespace @@ -88,6 +143,7 @@ TEST(QdsPresetModel, haveSameArraySizeForPresetsAndCategories) aCategory("A.categ", "A", {"item a", "item b"}), aCategory("B.categ", "B", {"item c", "item d"}), }, + {/*user presets*/}, {/*recents*/}); ASSERT_THAT(data.presets(), SizeIs(2)); @@ -105,6 +161,7 @@ TEST(QdsPresetModel, haveWizardPresetsNoRecents) aCategory("A.categ", "A", {"item a", "item b"}), aCategory("B.categ", "B", {"item c", "item d"}), }, + {/*user presets*/}, {/*recents*/}); // Then @@ -115,11 +172,30 @@ TEST(QdsPresetModel, haveWizardPresetsNoRecents) ElementsAre(PresetIs("B.categ", "item c"), PresetIs("B.categ", "item d"))); } +TEST(QdsPresetModel, whenHaveUserPresetsButNoWizardPresetsReturnEmpty) +{ + // Given + PresetData data; + + // When + data.setData({/*builtin presets*/}, + { + aUserPreset("A.Mobile", "Scroll", "iPhone5"), + aUserPreset("B.Desktop", "Launcher", "MacBook"), + }, + {/*recents*/}); + + // Then + ASSERT_THAT(data.categories(), IsEmpty()); + ASSERT_THAT(data.presets(), IsEmpty()); +} + TEST(QdsPresetModel, haveRecentsNoWizardPresets) { PresetData data; data.setData({/*wizardPresets*/}, + {/*user presets*/}, { {"A.categ", "Desktop", "640 x 480"}, {"B.categ", "Mobile", "800 x 600"}, @@ -129,7 +205,7 @@ TEST(QdsPresetModel, haveRecentsNoWizardPresets) ASSERT_THAT(data.presets(), IsEmpty()); } -TEST(QdsPresetModel, recentsAddedBeforeWizardPresets) +TEST(QdsPresetModel, recentsAddedWithWizardPresets) { // Given PresetData data; @@ -141,6 +217,7 @@ TEST(QdsPresetModel, recentsAddedBeforeWizardPresets) aCategory("A.categ", "A", {"Desktop", "item b"}), aCategory("B.categ", "B", {"item c", "Mobile"}), }, + {/*user presets*/}, /*recents*/ { {"A.categ", "Desktop", "800 x 600"}, @@ -158,7 +235,98 @@ TEST(QdsPresetModel, recentsAddedBeforeWizardPresets) ElementsAre(PresetIs("B.categ", "item c"), PresetIs("B.categ", "Mobile"))})); } -TEST(QdsPresetModel, recentsShouldNotSorted) +TEST(QdsPresetModel, userPresetsAddedWithWizardPresets) +{ + // Given + PresetData data; + + // When + data.setData( + /*wizard presets*/ + { + aCategory("A.categ", "A", {"Desktop", "item b"}), + aCategory("B.categ", "B", {"Mobile"}), + }, + { + aUserPreset("A.categ", "Desktop", "Windows10"), + }, + {/*recents*/}); + + // Then + ASSERT_THAT(data.categories(), ElementsAre("A", "B", "Custom")); + ASSERT_THAT(data.presets(), + ElementsAre(ElementsAre(PresetIs("A.categ", "Desktop"), + PresetIs("A.categ", "item b")), + ElementsAre(PresetIs("B.categ", "Mobile")), + ElementsAre(UserPresetIs("A.categ", "Desktop", "Windows10")))); +} + +TEST(QdsPresetModel, doesNotAddUserPresetsOfNonExistingCategory) +{ + // Given + PresetData data; + + // When + data.setData( + /*wizard presets*/ + { + aCategory("A.categ", "A", {"Desktop"}), // Only category "A.categ" exists + }, + { + aUserPreset("Bad.Categ", "Desktop", "Windows8"), // Bad.Categ does not exist + }, + {/*recents*/}); + + // Then + ASSERT_THAT(data.categories(), ElementsAre("A")); + ASSERT_THAT(data.presets(), ElementsAre(ElementsAre(PresetIs("A.categ", "Desktop")))); +} + +TEST(QdsPresetModel, doesNotAddUserPresetIfWizardPresetItRefersToDoesNotExist) +{ + // Given + PresetData data; + + // When + data.setData( + /*wizard presets*/ + { + aCategory("A.categ", "A", {"Desktop"}), + }, + { + aUserPreset("B.categ", "BadWizard", "Tablet"), // BadWizard referenced does not exist + }, + {/*recents*/}); + + // Then + ASSERT_THAT(data.categories(), ElementsAre("A")); + ASSERT_THAT(data.presets(), ElementsAre(ElementsAre(PresetIs("A.categ", "Desktop")))); +} + +TEST(QdsPresetModel, userPresetWithSameNameAsWizardPreset) +{ + // Given + PresetData data; + + // When + data.setData( + /*wizard presets*/ + { + aCategory("A.categ", "A", {"Desktop"}), + }, + { + aUserPreset("A.categ", "Desktop", "Desktop"), + }, + {/*recents*/}); + + // Then + ASSERT_THAT(data.categories(), ElementsAre("A", "Custom")); + ASSERT_THAT(data.presets(), + ElementsAre(ElementsAre(PresetIs("A.categ", "Desktop")), + ElementsAre(UserPresetIs("A.categ", "Desktop", "Desktop")))); +} + +TEST(QdsPresetModel, recentsShouldNotBeSorted) { // Given PresetData data; @@ -171,6 +339,7 @@ TEST(QdsPresetModel, recentsShouldNotSorted) aCategory("B.categ", "B", {"item c", "Mobile"}), aCategory("Z.categ", "Z", {"Z.desktop"}), }, + {/*user presets*/}, /*recents*/ { {"Z.categ", "Z.desktop", "200 x 300"}, @@ -197,6 +366,7 @@ TEST(QdsPresetModel, recentsOfSameWizardProjectButDifferentSizesAreRecognizedAsD aCategory("A.categ", "A", {"Desktop"}), aCategory("B.categ", "B", {"Mobile"}), }, + {/*user presets*/}, /*recents*/ { {"B.categ", "Mobile", "400 x 400"}, @@ -223,6 +393,7 @@ TEST(QdsPresetModel, outdatedRecentsAreNotShown) aCategory("A.categ", "A", {"Desktop"}), aCategory("B.categ", "B", {"Mobile"}), }, + {/*user presets*/}, /*recents*/ { {"B.categ", "NoLongerExists", "400 x 400"}, diff --git a/tests/auto/qml/qmldesigner/wizard/recentpresets-test.cpp b/tests/auto/qml/qmldesigner/wizard/recentpresets-test.cpp index b1bb0e06260..745462f5c1b 100644 --- a/tests/auto/qml/qmldesigner/wizard/recentpresets-test.cpp +++ b/tests/auto/qml/qmldesigner/wizard/recentpresets-test.cpp @@ -38,6 +38,16 @@ using namespace StudioWelcome; constexpr char GROUP_NAME[] = "RecentPresets"; constexpr char ITEMS[] = "Wizards"; +namespace StudioWelcome { +void PrintTo(const RecentPresetData &recent, std::ostream *os) +{ + *os << "{categId: " << recent.category << ", name: " << recent.presetName + << ", size: " << recent.sizeName << ", isUser: " << recent.isUserPreset; + + *os << "}"; +} +} // namespace StudioWelcome + class QdsRecentPresets : public ::testing::Test { protected: @@ -73,7 +83,7 @@ TEST_F(QdsRecentPresets, readFromEmptyStore) { RecentPresetsStore store{&settings}; - std::vector<RecentPreset> recents = store.fetchAll(); + std::vector<RecentPresetData> recents = store.fetchAll(); ASSERT_THAT(recents, IsEmpty()); } @@ -82,7 +92,7 @@ TEST_F(QdsRecentPresets, readEmptyRecentPresets) { RecentPresetsStore store = aStoreWithOne(""); - std::vector<RecentPreset> recents = store.fetchAll(); + std::vector<RecentPresetData> recents = store.fetchAll(); ASSERT_THAT(recents, IsEmpty()); } @@ -91,25 +101,34 @@ TEST_F(QdsRecentPresets, readOneRecentPresetAsList) { RecentPresetsStore store = aStoreWithRecents({"category/preset:640 x 480"}); - std::vector<RecentPreset> recents = store.fetchAll(); + std::vector<RecentPresetData> recents = store.fetchAll(); - ASSERT_THAT(recents, ElementsAre(RecentPreset("category", "preset", "640 x 480"))); + ASSERT_THAT(recents, ElementsAre(RecentPresetData("category", "preset", "640 x 480"))); } TEST_F(QdsRecentPresets, readOneRecentPresetAsString) { RecentPresetsStore store = aStoreWithOne("category/preset:200 x 300"); - std::vector<RecentPreset> recents = store.fetchAll(); + std::vector<RecentPresetData> recents = store.fetchAll(); + + ASSERT_THAT(recents, ElementsAre(RecentPresetData("category", "preset", "200 x 300"))); +} + +TEST_F(QdsRecentPresets, readOneRecentUserPresetAsString) +{ + RecentPresetsStore store = aStoreWithOne("category/[U]preset:200 x 300"); + + std::vector<RecentPresetData> recents = store.fetchAll(); - ASSERT_THAT(recents, ElementsAre(RecentPreset("category", "preset", "200 x 300"))); + ASSERT_THAT(recents, ElementsAre(RecentPresetData("category", "preset", "200 x 300", true))); } TEST_F(QdsRecentPresets, readBadRecentPresetAsString) { RecentPresetsStore store = aStoreWithOne("no_category_only_preset"); - std::vector<RecentPreset> recents = store.fetchAll(); + std::vector<RecentPresetData> recents = store.fetchAll(); ASSERT_THAT(recents, IsEmpty()); } @@ -118,24 +137,32 @@ TEST_F(QdsRecentPresets, readBadRecentPresetAsInt) { RecentPresetsStore store = aStoreWithOne(32); - std::vector<RecentPreset> recents = store.fetchAll(); + std::vector<RecentPresetData> recents = store.fetchAll(); ASSERT_THAT(recents, IsEmpty()); } TEST_F(QdsRecentPresets, readBadRecentPresetsInList) { - RecentPresetsStore store = aStoreWithRecents({"bad1", // no category, no size - "categ/name:800 x 600", // good - "categ/bad2", //no size - "categ/bad3:", //no size - "categ 1/bad4:200 x 300", // category has space - "categ/bad5: 400 x 300", // size starts with space - "categ/bad6:400"}); // bad size - - std::vector<RecentPreset> recents = store.fetchAll(); + RecentPresetsStore store = aStoreWithRecents({ + "bad1", // no category, no size + "categ/name:800 x 600", // good + "categ/bad2", //no size + "categ/bad3:", //no size + "categ 1/bad4:200 x 300", // category has space + "categ/bad5: 400 x 300", // size starts with space + "categ/bad6:400", // bad size + "categ/[U]user:300 x 200", // good + "categ/[u]user2:300 x 200", // small cap "U" + "categ/[x]user3:300 x 200", // must be letter "U" + "categ/[U] user4:300 x 200", // space + }); + + std::vector<RecentPresetData> recents = store.fetchAll(); - ASSERT_THAT(recents, ElementsAre(RecentPreset("categ", "name", "800 x 600"))); + ASSERT_THAT(recents, + ElementsAre(RecentPresetData("categ", "name", "800 x 600", false), + RecentPresetData("categ", "user", "300 x 200", true))); } TEST_F(QdsRecentPresets, readTwoRecentPresets) @@ -143,11 +170,23 @@ TEST_F(QdsRecentPresets, readTwoRecentPresets) RecentPresetsStore store = aStoreWithRecents( {"category_1/preset 1:640 x 480", "category_2/preset 2:320 x 200"}); - std::vector<RecentPreset> recents = store.fetchAll(); + std::vector<RecentPresetData> recents = store.fetchAll(); ASSERT_THAT(recents, - ElementsAre(RecentPreset("category_1", "preset 1", "640 x 480"), - RecentPreset("category_2", "preset 2", "320 x 200"))); + ElementsAre(RecentPresetData("category_1", "preset 1", "640 x 480"), + RecentPresetData("category_2", "preset 2", "320 x 200"))); +} + +TEST_F(QdsRecentPresets, readRecentsToDifferentKindsOfPresets) +{ + RecentPresetsStore store = aStoreWithRecents( + {"category_1/preset 1:640 x 480", "category_2/[U]preset 2:320 x 200"}); + + std::vector<RecentPresetData> recents = store.fetchAll(); + + ASSERT_THAT(recents, + ElementsAre(RecentPresetData("category_1", "preset 1", "640 x 480", false), + RecentPresetData("category_2", "preset 2", "320 x 200", true))); } TEST_F(QdsRecentPresets, addFirstRecentPreset) @@ -155,19 +194,44 @@ TEST_F(QdsRecentPresets, addFirstRecentPreset) RecentPresetsStore store{&settings}; store.add("A.Category", "Normal Application", "400 x 600"); - std::vector<RecentPreset> recents = store.fetchAll(); + std::vector<RecentPresetData> recents = store.fetchAll(); - ASSERT_THAT(recents, ElementsAre(RecentPreset("A.Category", "Normal Application", "400 x 600"))); + ASSERT_THAT(recents, ElementsAre(RecentPresetData("A.Category", "Normal Application", "400 x 600"))); +} + +TEST_F(QdsRecentPresets, addFirstRecentUserPreset) +{ + RecentPresetsStore store{&settings}; + + store.add("A.Category", "Normal Application", "400 x 600", /*user preset*/ true); + std::vector<RecentPresetData> recents = store.fetchAll(); + + ASSERT_THAT(recents, + ElementsAre(RecentPresetData("A.Category", "Normal Application", "400 x 600", true))); } TEST_F(QdsRecentPresets, addExistingFirstRecentPreset) { - RecentPresetsStore store = aStoreWithRecents({"category/preset"}); + RecentPresetsStore store = aStoreWithRecents({"category/preset:200 x 300"}); + ASSERT_THAT(store.fetchAll(), ElementsAre(RecentPresetData("category", "preset", "200 x 300"))); store.add("category", "preset", "200 x 300"); - std::vector<RecentPreset> recents = store.fetchAll(); + std::vector<RecentPresetData> recents = store.fetchAll(); + + ASSERT_THAT(recents, ElementsAre(RecentPresetData("category", "preset", "200 x 300"))); +} - ASSERT_THAT(recents, ElementsAre(RecentPreset("category", "preset", "200 x 300"))); +TEST_F(QdsRecentPresets, addRecentUserPresetWithSameNameAsExistingRecentNormalPreset) +{ + RecentPresetsStore store = aStoreWithRecents({"category/preset:200 x 300"}); + ASSERT_THAT(store.fetchAll(), ElementsAre(RecentPresetData("category", "preset", "200 x 300"))); + + store.add("category", "preset", "200 x 300", /*user preset*/ true); + std::vector<RecentPresetData> recents = store.fetchAll(); + + ASSERT_THAT(recents, + ElementsAre(RecentPresetData("category", "preset", "200 x 300", true), + RecentPresetData("category", "preset", "200 x 300", false))); } TEST_F(QdsRecentPresets, addSecondRecentPreset) @@ -175,11 +239,11 @@ TEST_F(QdsRecentPresets, addSecondRecentPreset) RecentPresetsStore store = aStoreWithRecents({"A.Category/Preset 1:800 x 600"}); store.add("A.Category", "Preset 2", "640 x 480"); - std::vector<RecentPreset> recents = store.fetchAll(); + std::vector<RecentPresetData> recents = store.fetchAll(); ASSERT_THAT(recents, - ElementsAre(RecentPreset("A.Category", "Preset 2", "640 x 480"), - RecentPreset("A.Category", "Preset 1", "800 x 600"))); + ElementsAre(RecentPresetData("A.Category", "Preset 2", "640 x 480"), + RecentPresetData("A.Category", "Preset 1", "800 x 600"))); } TEST_F(QdsRecentPresets, addSecondRecentPresetSameKindButDifferentSize) @@ -187,11 +251,11 @@ TEST_F(QdsRecentPresets, addSecondRecentPresetSameKindButDifferentSize) RecentPresetsStore store = aStoreWithRecents({"A.Category/Preset:800 x 600"}); store.add("A.Category", "Preset", "640 x 480"); - std::vector<RecentPreset> recents = store.fetchAll(); + std::vector<RecentPresetData> recents = store.fetchAll(); ASSERT_THAT(recents, - ElementsAre(RecentPreset("A.Category", "Preset", "640 x 480"), - RecentPreset("A.Category", "Preset", "800 x 600"))); + ElementsAre(RecentPresetData("A.Category", "Preset", "640 x 480"), + RecentPresetData("A.Category", "Preset", "800 x 600"))); } TEST_F(QdsRecentPresets, fetchesRecentPresetsInTheReverseOrderTheyWereAdded) @@ -201,12 +265,12 @@ TEST_F(QdsRecentPresets, fetchesRecentPresetsInTheReverseOrderTheyWereAdded) store.add("A.Category", "Preset 1", "640 x 480"); store.add("A.Category", "Preset 2", "640 x 480"); store.add("A.Category", "Preset 3", "800 x 600"); - std::vector<RecentPreset> recents = store.fetchAll(); + std::vector<RecentPresetData> recents = store.fetchAll(); ASSERT_THAT(recents, - ElementsAre(RecentPreset("A.Category", "Preset 3", "800 x 600"), - RecentPreset("A.Category", "Preset 2", "640 x 480"), - RecentPreset("A.Category", "Preset 1", "640 x 480"))); + ElementsAre(RecentPresetData("A.Category", "Preset 3", "800 x 600"), + RecentPresetData("A.Category", "Preset 2", "640 x 480"), + RecentPresetData("A.Category", "Preset 1", "640 x 480"))); } TEST_F(QdsRecentPresets, addingAnExistingRecentPresetMakesItTheFirst) @@ -216,12 +280,12 @@ TEST_F(QdsRecentPresets, addingAnExistingRecentPresetMakesItTheFirst) "A.Category/Preset 3:640 x 480"}); store.add("A.Category", "Preset 3", "640 x 480"); - std::vector<RecentPreset> recents = store.fetchAll(); + std::vector<RecentPresetData> recents = store.fetchAll(); ASSERT_THAT(recents, - ElementsAre(RecentPreset("A.Category", "Preset 3", "640 x 480"), - RecentPreset("A.Category", "Preset 1", "200 x 300"), - RecentPreset("A.Category", "Preset 2", "200 x 300"))); + ElementsAre(RecentPresetData("A.Category", "Preset 3", "640 x 480"), + RecentPresetData("A.Category", "Preset 1", "200 x 300"), + RecentPresetData("A.Category", "Preset 2", "200 x 300"))); } TEST_F(QdsRecentPresets, addingTooManyRecentPresetsRemovesTheOldestOne) @@ -231,9 +295,9 @@ TEST_F(QdsRecentPresets, addingTooManyRecentPresetsRemovesTheOldestOne) store.setMaximum(2); store.add("A.Category", "Preset 3", "200 x 300"); - std::vector<RecentPreset> recents = store.fetchAll(); + std::vector<RecentPresetData> recents = store.fetchAll(); ASSERT_THAT(recents, - ElementsAre(RecentPreset("A.Category", "Preset 3", "200 x 300"), - RecentPreset("A.Category", "Preset 2", "200 x 300"))); + ElementsAre(RecentPresetData("A.Category", "Preset 3", "200 x 300"), + RecentPresetData("A.Category", "Preset 2", "200 x 300"))); } diff --git a/tests/auto/qml/qmldesigner/wizard/userpresets-test.cpp b/tests/auto/qml/qmldesigner/wizard/userpresets-test.cpp new file mode 100644 index 00000000000..8fa73402c08 --- /dev/null +++ b/tests/auto/qml/qmldesigner/wizard/userpresets-test.cpp @@ -0,0 +1,308 @@ +/**************************************************************************** +** +** Copyright (C) 2022 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 "test-utilities.h" +#include "userpresets.h" + +#include <utils/filepath.h> +#include <utils/temporarydirectory.h> + +namespace StudioWelcome { + +void PrintTo(const UserPresetData &preset, std::ostream *os) +{ + *os << "UserPresetData{category = " << preset.categoryId; + *os << "; wizardName = " << preset.wizardName; + *os << "; name = " << preset.name; + *os << "; screenSize = " << preset.screenSize; + *os << "; keyboard = " << preset.useQtVirtualKeyboard; + *os << "; qt = " << preset.qtVersion; + *os << "; style = " << preset.styleName; + *os << "}"; +} + +void PrintTo(const std::vector<UserPresetData> &presets, std::ostream *os) +{ + if (presets.size() == 0) { + *os << "{}" << std::endl; + } else { + *os << "{" << std::endl; + for (size_t i = 0; i < presets.size(); ++i) { + *os << "#" << i << ": "; + PrintTo(presets[i], os); + *os << std::endl; + } + *os << "}" << std::endl; + } +} + +} // namespace StudioWelcome + +using namespace StudioWelcome; + +constexpr char ARRAY_NAME[] = "UserPresets"; + +class QdsUserPresets : public ::testing::Test +{ +protected: + void SetUp() + { + settings = std::make_unique<QSettings>(tempDir.filePath("test").toString(), + QSettings::IniFormat); + } + + UserPresetsStore anEmptyStore() { return UserPresetsStore{std::move(settings)}; } + + UserPresetsStore aStoreWithZeroItems() + { + settings->beginWriteArray(ARRAY_NAME, 0); + settings->endArray(); + + return UserPresetsStore{std::move(settings)}; + } + + UserPresetsStore aStoreWithOne(const UserPresetData &preset) + { + settings->beginWriteArray(ARRAY_NAME, 1); + settings->setArrayIndex(0); + + settings->setValue("categoryId", preset.categoryId); + settings->setValue("wizardName", preset.wizardName); + settings->setValue("name", preset.name); + settings->setValue("screenSize", preset.screenSize); + settings->setValue("useQtVirtualKeyboard", preset.useQtVirtualKeyboard); + settings->setValue("qtVersion", preset.qtVersion); + settings->setValue("styleName", preset.styleName); + + settings->endArray(); + + return UserPresetsStore{std::move(settings)}; + } + + UserPresetsStore aStoreWithPresets(const std::vector<UserPresetData> &presets) + { + settings->beginWriteArray(ARRAY_NAME, presets.size()); + + for (size_t i = 0; i < presets.size(); ++i) { + settings->setArrayIndex(i); + const auto &preset = presets[i]; + + settings->setValue("categoryId", preset.categoryId); + settings->setValue("wizardName", preset.wizardName); + settings->setValue("name", preset.name); + settings->setValue("screenSize", preset.screenSize); + settings->setValue("useQtVirtualKeyboard", preset.useQtVirtualKeyboard); + settings->setValue("qtVersion", preset.qtVersion); + settings->setValue("styleName", preset.styleName); + } + settings->endArray(); + + return UserPresetsStore{std::move(settings)}; + } + + Utils::TemporaryDirectory tempDir{"userpresets-XXXXXX"}; + std::unique_ptr<QSettings> settings; + +private: + QString settingsPath; +}; + +/******************* TESTS *******************/ + +TEST_F(QdsUserPresets, readEmptyUserPresets) +{ + auto store = anEmptyStore(); + + auto presets = store.fetchAll(); + + ASSERT_THAT(presets, IsEmpty()); +} + +TEST_F(QdsUserPresets, readEmptyArrayOfUserPresets) +{ + auto store = aStoreWithZeroItems(); + + auto presets = store.fetchAll(); + + ASSERT_THAT(presets, IsEmpty()); +} + +TEST_F(QdsUserPresets, readOneUserPreset) +{ + UserPresetData preset{"A.categ", "3D App", "iPhone7", "400 x 20", true, "Qt 5", "Material Dark"}; + auto store = aStoreWithOne(preset); + + auto presets = store.fetchAll(); + + ASSERT_THAT(presets, ElementsAreArray({preset})); +} + +TEST_F(QdsUserPresets, readOneIncompleteUserPreset) +{ + // A preset entry that has the required entries, but not the others. + UserPresetData preset{"A.categ", "3D App", "iPhone7", "", false, "", ""}; + auto store = aStoreWithOne(preset); + + auto presets = store.fetchAll(); + + ASSERT_THAT(presets, ElementsAreArray({preset})); +} + +TEST_F(QdsUserPresets, doesNotReadPresetsThatLackRequiredEntries) +{ + // Required entries are: Category id, wizard name, preset name. + auto presetsInStore = std::vector<UserPresetData>{ + UserPresetData{"", "3D App", "iPhone7", "400 x 20", true, "Qt 5", "Material Dark"}, + UserPresetData{"A.categ", "", "iPhone7", "400 x 20", true, "Qt 5", "Material Dark"}, + UserPresetData{"A.categ", "3D App", "", "400 x 20", true, "Qt 5", "Material Dark"}, + }; + auto store = aStoreWithPresets(presetsInStore); + + auto presets = store.fetchAll(); + + ASSERT_THAT(presets, IsEmpty()); +} + +TEST_F(QdsUserPresets, readSeveralUserPresets) +{ + auto presetsInStore = std::vector<UserPresetData>{ + UserPresetData{"A.categ", "3D App", "iPhone7", "400 x 20", true, "Qt 5", "Material Dark"}, + UserPresetData{"B.categ", "2D App", "iPhone6", "200 x 50", false, "Qt 6", "Fusion"}, + UserPresetData{"C.categ", "Empty", "Some Other", "60 x 30", true, "Qt 7", "Material Light"}, + }; + auto store = aStoreWithPresets(presetsInStore); + + auto presets = store.fetchAll(); + + ASSERT_THAT(presets, ElementsAreArray(presetsInStore)); +} + +TEST_F(QdsUserPresets, cannotSaveInvalidPreset) +{ + // an invalid preset is a preset that lacks required fields. + UserPresetData invalidPreset{"", "3D App", "iPhone7", "400 x 20", true, "Qt 5", "Material Dark"}; + auto store = anEmptyStore(); + + bool saved = store.save(invalidPreset); + + auto presets = store.fetchAll(); + ASSERT_THAT(presets, IsEmpty()); + ASSERT_FALSE(saved); +} + +TEST_F(QdsUserPresets, savePresetInEmptyStore) +{ + UserPresetData preset{"B.categ", "3D App", "iPhone7", "400 x 20", true, "Qt 5", "Material Dark"}; + auto store = anEmptyStore(); + + store.save(preset); + + auto presets = store.fetchAll(); + ASSERT_THAT(presets, ElementsAre(preset)); +} + +TEST_F(QdsUserPresets, saveIncompletePreset) +{ + UserPresetData preset{"C.categ", "2D App", "Android", "", false, "", ""}; + auto store = anEmptyStore(); + + store.save(preset); + + auto presets = store.fetchAll(); + ASSERT_THAT(presets, ElementsAre(preset)); +} + +TEST_F(QdsUserPresets, cannotSavePresetWithSameName) +{ + UserPresetData existing{"B.categ", "3D App", "Same Name", "400 x 20", true, "Qt 5", "Material Dark"}; + auto store = aStoreWithOne(existing); + + UserPresetData newPreset{"C.categ", "Empty", "Same Name", "100 x 30", false, "Qt 6", "Fusion"}; + bool saved = store.save(newPreset); + + ASSERT_FALSE(saved); + ASSERT_THAT(store.fetchAll(), ElementsAreArray({existing})); +} + +TEST_F(QdsUserPresets, saveNewPreset) +{ + UserPresetData existing{"A.categ", "3D App", "iPhone7", "400 x 20", true, "Qt 5", "Material Dark"}; + auto store = aStoreWithOne(existing); + + UserPresetData newPreset{"A.categ", "Empty", "Huawei", "100 x 30", true, "Qt 6", "Fusion"}; + store.save(newPreset); + + auto presets = store.fetchAll(); + ASSERT_THAT(presets, ElementsAre(existing, newPreset)); +} + +TEST_F(QdsUserPresets, removeUserPresetFromEmptyStore) +{ + UserPresetData preset{"C.categ", "2D App", "Android", "", false, "", ""}; + auto store = anEmptyStore(); + + store.remove("A.categ", "User preset name"); + + auto presets = store.fetchAll(); + ASSERT_THAT(presets, IsEmpty()); +} + +TEST_F(QdsUserPresets, removeExistingUserPreset) +{ + UserPresetData existing{"A.categ", "3D App", "iPhone7", "400 x 20", true, "Qt 5", "Material Dark"}; + auto store = aStoreWithOne(existing); + + store.remove("A.categ", "iPhone7"); + + auto presets = store.fetchAll(); + ASSERT_THAT(presets, IsEmpty()); +} + +TEST_F(QdsUserPresets, removeNonExistingUserPreset) +{ + UserPresetData existing{"A.categ", "3D App", "iPhone7", "400 x 20", true, "Qt 5", "Material Dark"}; + auto store = aStoreWithOne(existing); + + store.remove("A.categ", "Android"); + + auto presets = store.fetchAll(); + ASSERT_THAT(presets, ElementsAre(existing)); +} + +TEST_F(QdsUserPresets, removeExistingUserPresetInStoreWithManyPresets) +{ + auto presetsInStore = std::vector<UserPresetData>{ + UserPresetData{"A.categ", "3D App", "iPhone7", "400 x 20", true, "Qt 5", "Material Dark"}, + UserPresetData{"B.categ", "2D App", "iPhone6", "200 x 50", false, "Qt 6", "Fusion"}, + UserPresetData{"C.categ", "Empty", "Some Other", "60 x 30", true, "Qt 7", "Material Light"}, + }; + auto store = aStoreWithPresets(presetsInStore); + + store.remove("B.categ", "iPhone6"); + + auto presets = store.fetchAll(); + ASSERT_THAT(presets, ElementsAre(presetsInStore[0], presetsInStore[2])); +} + diff --git a/tests/auto/qml/qmldesigner/wizard/wizardfactories-test.cpp b/tests/auto/qml/qmldesigner/wizard/wizardfactories-test.cpp index 30c79e9090d..6541e7a1637 100644 --- a/tests/auto/qml/qmldesigner/wizard/wizardfactories-test.cpp +++ b/tests/auto/qml/qmldesigner/wizard/wizardfactories-test.cpp @@ -151,7 +151,7 @@ private: QStringList presetNames(const WizardCategory &cat) { - QStringList result = Utils::transform<QStringList>(cat.items, &PresetItem::name); + QStringList result = Utils::transform<QStringList>(cat.items, &PresetItem::wizardName); return result; } @@ -319,9 +319,9 @@ TEST_F(QdsWizardFactories, createsPresetItemAndCategoryCorrectlyFromWizardFactor ASSERT_EQ("myDisplayCategory", category.name); auto presetItem = presets["myCategoryId"].items[0]; - ASSERT_EQ("myName", presetItem.name); - ASSERT_EQ("myDescription", presetItem.description); - ASSERT_EQ("qrc:/my/qml/path", presetItem.qmlPath.toString()); - ASSERT_EQ("\uABCD", presetItem.fontIconCode); + ASSERT_EQ("myName", presetItem->wizardName); + ASSERT_EQ("myDescription", presetItem->description); + ASSERT_EQ("qrc:/my/qml/path", presetItem->qmlPath.toString()); + ASSERT_EQ("\uABCD", presetItem->fontIconCode); } |