aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSamuel Ghinet <samuel.ghinet@qt.io>2022-02-23 15:32:34 +0200
committerSamuel Ghinet <samuel.ghinet@qt.io>2022-03-08 09:39:43 +0000
commit618eda3572a2df97d28ffdf603daa35b509fbcf1 (patch)
treef86be7777e702f7a13d9588c753ece1adb241b1a
parentec02c157eec9aeece5cdbb6b5ab112d0eae4c4e2 (diff)
Implement custom presets
Task-number: QDS-4989 Change-Id: I95844ae97204ad3bb94905c89f8e16b79eed8f64 Reviewed-by: Alessandro Portale <alessandro.portale@qt.io>
-rw-r--r--share/qtcreator/qmldesigner/newprojectdialog/NewProjectDialog.qml117
-rw-r--r--share/qtcreator/qmldesigner/newprojectdialog/image/style-basic.pngbin3891 -> 3642 bytes
-rw-r--r--share/qtcreator/qmldesigner/newprojectdialog/image/style-basic@2x.pngbin9643 -> 5661 bytes
-rw-r--r--share/qtcreator/qmldesigner/newprojectdialog/image/style-default.pngbin1617 -> 2222 bytes
-rw-r--r--share/qtcreator/qmldesigner/newprojectdialog/image/style-default@2x.pngbin4165 -> 4150 bytes
-rw-r--r--share/qtcreator/qmldesigner/newprojectdialog/image/style-fusion.pngbin2436 -> 3371 bytes
-rw-r--r--share/qtcreator/qmldesigner/newprojectdialog/image/style-fusion@2x.pngbin8903 -> 5328 bytes
-rw-r--r--share/qtcreator/qmldesigner/newprojectdialog/image/style-imagine.pngbin3598 -> 3527 bytes
-rw-r--r--share/qtcreator/qmldesigner/newprojectdialog/image/style-imagine@2x.pngbin8809 -> 5090 bytes
-rw-r--r--share/qtcreator/qmldesigner/newprojectdialog/image/style-macOs@2x.pngbin4165 -> 0 bytes
-rw-r--r--share/qtcreator/qmldesigner/newprojectdialog/image/style-macos.pngbin1617 -> 2222 bytes
-rw-r--r--share/qtcreator/qmldesigner/newprojectdialog/image/style-macos@2x.pngbin0 -> 4150 bytes
-rw-r--r--share/qtcreator/qmldesigner/newprojectdialog/image/style-material_dark.pngbin4033 -> 3406 bytes
-rw-r--r--share/qtcreator/qmldesigner/newprojectdialog/image/style-material_dark@2x.pngbin10919 -> 5410 bytes
-rw-r--r--share/qtcreator/qmldesigner/newprojectdialog/image/style-material_light.pngbin3780 -> 3588 bytes
-rw-r--r--share/qtcreator/qmldesigner/newprojectdialog/image/style-material_light@2x.pngbin9504 -> 5467 bytes
-rw-r--r--share/qtcreator/qmldesigner/newprojectdialog/image/style-universal_dark.pngbin3887 -> 3338 bytes
-rw-r--r--share/qtcreator/qmldesigner/newprojectdialog/image/style-universal_dark@2x.pngbin9579 -> 5417 bytes
-rw-r--r--share/qtcreator/qmldesigner/newprojectdialog/image/style-universal_light.pngbin2486 -> 3461 bytes
-rw-r--r--share/qtcreator/qmldesigner/newprojectdialog/image/style-universal_light@2x.pngbin9157 -> 5297 bytes
-rw-r--r--share/qtcreator/qmldesigner/newprojectdialog/image/style-universal_system.pngbin3395 -> 3354 bytes
-rw-r--r--share/qtcreator/qmldesigner/newprojectdialog/image/style-universal_system@2x.pngbin8527 -> 5172 bytes
-rw-r--r--share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/Details.qml180
-rw-r--r--share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/DialogValues.qml48
-rw-r--r--share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/NewProjectView.qml303
-rw-r--r--share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/PopupDialog.qml66
-rw-r--r--share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/PopupDialogButton.qml107
-rw-r--r--share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/PopupDialogButtonBox.qml46
-rw-r--r--share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/Styles.qml192
-rw-r--r--share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/qmldir7
-rw-r--r--share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/VerticalScrollBar.qml31
-rw-r--r--share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/Values.qml2
-rw-r--r--src/plugins/studiowelcome/CMakeLists.txt1
-rw-r--r--src/plugins/studiowelcome/presetmodel.cpp139
-rw-r--r--src/plugins/studiowelcome/presetmodel.h114
-rw-r--r--src/plugins/studiowelcome/qdsnewdialog.cpp186
-rw-r--r--src/plugins/studiowelcome/qdsnewdialog.h28
-rw-r--r--src/plugins/studiowelcome/recentpresets.cpp86
-rw-r--r--src/plugins/studiowelcome/recentpresets.h58
-rw-r--r--src/plugins/studiowelcome/studiowelcome.qbs4
-rw-r--r--src/plugins/studiowelcome/userpresets.cpp125
-rw-r--r--src/plugins/studiowelcome/userpresets.h91
-rw-r--r--src/plugins/studiowelcome/wizardfactories.cpp23
-rw-r--r--src/plugins/studiowelcome/wizardfactories.h2
-rw-r--r--src/plugins/studiowelcome/wizardhandler.cpp82
-rw-r--r--src/plugins/studiowelcome/wizardhandler.h14
-rw-r--r--tests/auto/qml/qmldesigner/wizard/CMakeLists.txt2
-rw-r--r--tests/auto/qml/qmldesigner/wizard/presetmodel-test.cpp187
-rw-r--r--tests/auto/qml/qmldesigner/wizard/recentpresets-test.cpp150
-rw-r--r--tests/auto/qml/qmldesigner/wizard/userpresets-test.cpp308
-rw-r--r--tests/auto/qml/qmldesigner/wizard/wizardfactories-test.cpp10
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
index 389a4eab873..d139d6e0266 100644
--- a/share/qtcreator/qmldesigner/newprojectdialog/image/style-basic.png
+++ b/share/qtcreator/qmldesigner/newprojectdialog/image/style-basic.png
Binary files differ
diff --git a/share/qtcreator/qmldesigner/newprojectdialog/image/style-basic@2x.png b/share/qtcreator/qmldesigner/newprojectdialog/image/style-basic@2x.png
index 3636791da1e..03f87cc4b7d 100644
--- a/share/qtcreator/qmldesigner/newprojectdialog/image/style-basic@2x.png
+++ b/share/qtcreator/qmldesigner/newprojectdialog/image/style-basic@2x.png
Binary files differ
diff --git a/share/qtcreator/qmldesigner/newprojectdialog/image/style-default.png b/share/qtcreator/qmldesigner/newprojectdialog/image/style-default.png
index 6c50e4e1fa9..ceb573b339f 100644
--- a/share/qtcreator/qmldesigner/newprojectdialog/image/style-default.png
+++ b/share/qtcreator/qmldesigner/newprojectdialog/image/style-default.png
Binary files differ
diff --git a/share/qtcreator/qmldesigner/newprojectdialog/image/style-default@2x.png b/share/qtcreator/qmldesigner/newprojectdialog/image/style-default@2x.png
index 09ea1ce94d5..dd86707377b 100644
--- a/share/qtcreator/qmldesigner/newprojectdialog/image/style-default@2x.png
+++ b/share/qtcreator/qmldesigner/newprojectdialog/image/style-default@2x.png
Binary files differ
diff --git a/share/qtcreator/qmldesigner/newprojectdialog/image/style-fusion.png b/share/qtcreator/qmldesigner/newprojectdialog/image/style-fusion.png
index 745828c1f7e..d196646855f 100644
--- a/share/qtcreator/qmldesigner/newprojectdialog/image/style-fusion.png
+++ b/share/qtcreator/qmldesigner/newprojectdialog/image/style-fusion.png
Binary files differ
diff --git a/share/qtcreator/qmldesigner/newprojectdialog/image/style-fusion@2x.png b/share/qtcreator/qmldesigner/newprojectdialog/image/style-fusion@2x.png
index c2232f3a754..6bdb9952a4b 100644
--- a/share/qtcreator/qmldesigner/newprojectdialog/image/style-fusion@2x.png
+++ b/share/qtcreator/qmldesigner/newprojectdialog/image/style-fusion@2x.png
Binary files differ
diff --git a/share/qtcreator/qmldesigner/newprojectdialog/image/style-imagine.png b/share/qtcreator/qmldesigner/newprojectdialog/image/style-imagine.png
index 40c96993a0c..76612e8a8e8 100644
--- a/share/qtcreator/qmldesigner/newprojectdialog/image/style-imagine.png
+++ b/share/qtcreator/qmldesigner/newprojectdialog/image/style-imagine.png
Binary files differ
diff --git a/share/qtcreator/qmldesigner/newprojectdialog/image/style-imagine@2x.png b/share/qtcreator/qmldesigner/newprojectdialog/image/style-imagine@2x.png
index c4ef9b7854d..34c41c3587f 100644
--- a/share/qtcreator/qmldesigner/newprojectdialog/image/style-imagine@2x.png
+++ b/share/qtcreator/qmldesigner/newprojectdialog/image/style-imagine@2x.png
Binary files differ
diff --git a/share/qtcreator/qmldesigner/newprojectdialog/image/style-macOs@2x.png b/share/qtcreator/qmldesigner/newprojectdialog/image/style-macOs@2x.png
deleted file mode 100644
index 09ea1ce94d5..00000000000
--- a/share/qtcreator/qmldesigner/newprojectdialog/image/style-macOs@2x.png
+++ /dev/null
Binary files differ
diff --git a/share/qtcreator/qmldesigner/newprojectdialog/image/style-macos.png b/share/qtcreator/qmldesigner/newprojectdialog/image/style-macos.png
index 6c50e4e1fa9..ceb573b339f 100644
--- a/share/qtcreator/qmldesigner/newprojectdialog/image/style-macos.png
+++ b/share/qtcreator/qmldesigner/newprojectdialog/image/style-macos.png
Binary files differ
diff --git a/share/qtcreator/qmldesigner/newprojectdialog/image/style-macos@2x.png b/share/qtcreator/qmldesigner/newprojectdialog/image/style-macos@2x.png
new file mode 100644
index 00000000000..dd86707377b
--- /dev/null
+++ b/share/qtcreator/qmldesigner/newprojectdialog/image/style-macos@2x.png
Binary files differ
diff --git a/share/qtcreator/qmldesigner/newprojectdialog/image/style-material_dark.png b/share/qtcreator/qmldesigner/newprojectdialog/image/style-material_dark.png
index 515f5e0b8e4..cf7891232a9 100644
--- a/share/qtcreator/qmldesigner/newprojectdialog/image/style-material_dark.png
+++ b/share/qtcreator/qmldesigner/newprojectdialog/image/style-material_dark.png
Binary files differ
diff --git a/share/qtcreator/qmldesigner/newprojectdialog/image/style-material_dark@2x.png b/share/qtcreator/qmldesigner/newprojectdialog/image/style-material_dark@2x.png
index 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
Binary files differ
diff --git a/share/qtcreator/qmldesigner/newprojectdialog/image/style-material_light.png b/share/qtcreator/qmldesigner/newprojectdialog/image/style-material_light.png
index d41b3c5ff88..e826ad00811 100644
--- a/share/qtcreator/qmldesigner/newprojectdialog/image/style-material_light.png
+++ b/share/qtcreator/qmldesigner/newprojectdialog/image/style-material_light.png
Binary files differ
diff --git a/share/qtcreator/qmldesigner/newprojectdialog/image/style-material_light@2x.png b/share/qtcreator/qmldesigner/newprojectdialog/image/style-material_light@2x.png
index 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
Binary files differ
diff --git a/share/qtcreator/qmldesigner/newprojectdialog/image/style-universal_dark.png b/share/qtcreator/qmldesigner/newprojectdialog/image/style-universal_dark.png
index 339342bd913..9a9988e91d6 100644
--- a/share/qtcreator/qmldesigner/newprojectdialog/image/style-universal_dark.png
+++ b/share/qtcreator/qmldesigner/newprojectdialog/image/style-universal_dark.png
Binary files differ
diff --git a/share/qtcreator/qmldesigner/newprojectdialog/image/style-universal_dark@2x.png b/share/qtcreator/qmldesigner/newprojectdialog/image/style-universal_dark@2x.png
index 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
Binary files differ
diff --git a/share/qtcreator/qmldesigner/newprojectdialog/image/style-universal_light.png b/share/qtcreator/qmldesigner/newprojectdialog/image/style-universal_light.png
index 7b457bae8f4..169581e05dd 100644
--- a/share/qtcreator/qmldesigner/newprojectdialog/image/style-universal_light.png
+++ b/share/qtcreator/qmldesigner/newprojectdialog/image/style-universal_light.png
Binary files differ
diff --git a/share/qtcreator/qmldesigner/newprojectdialog/image/style-universal_light@2x.png b/share/qtcreator/qmldesigner/newprojectdialog/image/style-universal_light@2x.png
index 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
Binary files differ
diff --git a/share/qtcreator/qmldesigner/newprojectdialog/image/style-universal_system.png b/share/qtcreator/qmldesigner/newprojectdialog/image/style-universal_system.png
index 65fbae504ff..08872abd229 100644
--- a/share/qtcreator/qmldesigner/newprojectdialog/image/style-universal_system.png
+++ b/share/qtcreator/qmldesigner/newprojectdialog/image/style-universal_system.png
Binary files differ
diff --git a/share/qtcreator/qmldesigner/newprojectdialog/image/style-universal_system@2x.png b/share/qtcreator/qmldesigner/newprojectdialog/image/style-universal_system@2x.png
index 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
Binary files differ
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);
}