Implement custom presets

Task-number: QDS-4989
Change-Id: I95844ae97204ad3bb94905c89f8e16b79eed8f64
Reviewed-by: Alessandro Portale <alessandro.portale@qt.io>
This commit is contained in:
Samuel Ghinet
2022-02-23 15:32:34 +02:00
parent ec02c157ee
commit 618eda3572
51 changed files with 2218 additions and 487 deletions

View File

@@ -32,6 +32,7 @@ import StudioTheme as StudioTheme
import StudioControls as SC import StudioControls as SC
import NewProjectDialog import NewProjectDialog
import BackendApi
Item { Item {
id: rootDialog id: rootDialog
@@ -161,17 +162,43 @@ Item {
readonly property int animDur: 500 readonly property int animDur: 500
id: tabBar id: tabBar
x: 10 // left padding x: 10 // left padding
width: parent.width - 64 // right padding width: parent.width - 20 // right padding
height: DialogValues.projectViewHeaderHeight height: DialogValues.projectViewHeaderHeight
color: DialogValues.lightPaneColor 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 { Row {
id: tabBarRow id: tabBarRow
spacing: 20 spacing: 20
property int currIndex: 0 property int currIndex: 0
readonly property string currentTabName:
repeater.count > 0 && repeater.itemAt(currIndex)
? repeater.itemAt(currIndex).text
: ''
Repeater { Repeater {
model: categoryModel id: repeater
model: BackendApi.categoryModel
Text { Text {
text: name text: name
font.weight: Font.DemiBold font.weight: Font.DemiBold
@@ -184,13 +211,7 @@ Item {
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
onClicked: { onClicked: {
tabBarRow.currIndex = index tabBar.selectTab(index)
presetModel.setPage(index)
projectView.currentIndex = 0
projectView.currentIndexChanged()
strip.x = parent.x
strip.width = parent.width
} }
} }
@@ -199,8 +220,19 @@ Item {
} // tabBarRow } // tabBarRow
Rectangle { 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 id: strip
width: tabBarRow.children[0].width x: computeX()
width: computeWidth()
height: 5 height: 5
radius: 2 radius: 2
color: DialogValues.textColorInteraction color: DialogValues.textColorInteraction
@@ -209,35 +241,40 @@ Item {
Behavior on x { SmoothedAnimation { duration: tabBar.animDur } } Behavior on x { SmoothedAnimation { duration: tabBar.animDur } }
Behavior on width { SmoothedAnimation { duration: strip.width === 0 ? 0 : tabBar.animDur } } // do not animate initial width 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 } // Rectangle
NewProjectView { Rectangle {
id: projectView id: projectViewFrame
x: 10 // left padding x: 10 // left padding
width: parent.width - 64 // right padding width: parent.width - 20 // right padding
height: DialogValues.projectViewHeight height: DialogValues.projectViewHeight
loader: projectDetailsLoader color: DialogValues.darkPaneColor
Connections { Item {
target: rootDialog anchors.fill: parent
function onHeightChanged() { anchors.margins: DialogValues.gridMargins
if (rootDialog.height < 700) { // 700 = minimum height big dialog
projectView.height = DialogValues.projectViewHeight / 2 NewProjectView {
} else { id: projectView
projectView.height = DialogValues.projectViewHeight 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 { Text {
id: descriptionText id: descriptionText
text: dialogBox.projectDescription text: BackendApi.projectDescription
font.pixelSize: DialogValues.defaultPixelSize font.pixelSize: DialogValues.defaultPixelSize
lineHeight: DialogValues.defaultLineHeight lineHeight: DialogValues.defaultLineHeight
lineHeightMode: Text.FixedHeight lineHeightMode: Text.FixedHeight
leftPadding: 14 leftPadding: 14
width: projectView.width width: projectViewFrame.width
color: DialogValues.textColor color: DialogValues.textColor
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
maximumLineCount: 4 maximumLineCount: 4
@@ -298,7 +335,7 @@ Item {
iconFont: StudioTheme.Constants.font iconFont: StudioTheme.Constants.font
onClicked: { onClicked: {
dialogBox.reject(); BackendApi.reject();
} }
} }
@@ -310,11 +347,11 @@ Item {
visible: true visible: true
buttonIcon: qsTr("Create") buttonIcon: qsTr("Create")
iconSize: DialogValues.defaultPixelSize iconSize: DialogValues.defaultPixelSize
enabled: dialogBox.fieldsValid enabled: BackendApi.fieldsValid
iconFont: StudioTheme.Constants.font iconFont: StudioTheme.Constants.font
onClicked: { onClicked: {
dialogBox.accept(); BackendApi.accept();
} }
} }
} // RowLayout } // RowLayout

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.3 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

@@ -31,16 +31,13 @@ import QtQuick.Layouts
import StudioControls as SC import StudioControls as SC
import StudioTheme as StudioTheme import StudioTheme as StudioTheme
import BackendApi
Item { Item {
width: DialogValues.detailsPaneWidth width: DialogValues.detailsPaneWidth
Component.onCompleted: { Component.onCompleted: BackendApi.detailsLoaded = true
dialogBox.detailsLoaded = true; Component.onDestruction: BackendApi.detailsLoaded = false
}
Component.onDestruction: {
dialogBox.detailsLoaded = false;
}
Rectangle { Rectangle {
color: DialogValues.darkPaneColor color: DialogValues.darkPaneColor
@@ -53,13 +50,13 @@ Item {
Column { Column {
anchors.fill: parent anchors.fill: parent
spacing: DialogValues.defaultPadding spacing: 5
Text { Text {
id: detailsHeading id: detailsHeading
text: qsTr("Details") text: qsTr("Details")
height: DialogValues.paneTitleTextHeight height: DialogValues.paneTitleTextHeight
width: parent.width; width: parent.width
font.weight: Font.DemiBold font.weight: Font.DemiBold
font.pixelSize: DialogValues.paneTitlePixelSize font.pixelSize: DialogValues.paneTitlePixelSize
lineHeight: DialogValues.paneTitleLineHeight lineHeight: DialogValues.paneTitleLineHeight
@@ -71,39 +68,36 @@ Item {
Flickable { Flickable {
width: parent.width width: parent.width
height: parent.height - detailsHeading.height - DialogValues.defaultPadding height: parent.height - detailsHeading.height - DialogValues.defaultPadding
- savePresetButton.height
contentWidth: parent.width contentWidth: parent.width
contentHeight: scrollContent.height contentHeight: scrollContent.height
boundsBehavior: Flickable.StopAtBounds boundsBehavior: Flickable.StopAtBounds
clip: true clip: true
ScrollBar.vertical: SC.VerticalScrollBar { ScrollBar.vertical: SC.VerticalScrollBar {}
}
Column { Column {
id: scrollContent id: scrollContent
width: parent.width - DialogValues.detailsPanePadding width: parent.width - DialogValues.detailsPanePadding
height: DialogValues.detailsScrollableContentHeight
spacing: DialogValues.defaultPadding spacing: DialogValues.defaultPadding
SC.TextField { SC.TextField {
id: projectNameTextField id: projectNameTextField
actionIndicatorVisible: false actionIndicatorVisible: false
translationIndicatorVisible: false translationIndicatorVisible: false
text: dialogBox.projectName text: BackendApi.projectName
width: parent.width width: parent.width
color: DialogValues.textColor color: DialogValues.textColor
selectByMouse: true selectByMouse: true
font.pixelSize: DialogValues.defaultPixelSize
onEditingFinished: { onEditingFinished: {
text = text.charAt(0).toUpperCase() + text.slice(1) text = text.charAt(0).toUpperCase() + text.slice(1)
} }
font.pixelSize: DialogValues.defaultPixelSize
} }
Binding { Binding {
target: dialogBox target: BackendApi
property: "projectName" property: "projectName"
value: projectNameTextField.text value: projectNameTextField.text
} }
@@ -118,14 +112,14 @@ Item {
id: projectLocationTextField id: projectLocationTextField
actionIndicatorVisible: false actionIndicatorVisible: false
translationIndicatorVisible: false translationIndicatorVisible: false
text: dialogBox.projectLocation text: BackendApi.projectLocation
color: DialogValues.textColor color: DialogValues.textColor
selectByMouse: true selectByMouse: true
font.pixelSize: DialogValues.defaultPixelSize font.pixelSize: DialogValues.defaultPixelSize
} }
Binding { Binding {
target: dialogBox target: BackendApi
property: "projectLocation" property: "projectLocation"
value: projectLocationTextField.text value: projectLocationTextField.text
} }
@@ -138,7 +132,7 @@ Item {
iconFont: StudioTheme.Constants.font iconFont: StudioTheme.Constants.font
onClicked: { onClicked: {
var newLocation = dialogBox.chooseProjectLocation() var newLocation = BackendApi.chooseProjectLocation()
if (newLocation) if (newLocation)
projectLocationTextField.text = newLocation projectLocationTextField.text = newLocation
} }
@@ -159,7 +153,7 @@ Item {
Text { Text {
id: statusMessage id: statusMessage
text: dialogBox.statusMessage text: BackendApi.statusMessage
font.pixelSize: DialogValues.defaultPixelSize font.pixelSize: DialogValues.defaultPixelSize
lineHeight: DialogValues.defaultLineHeight lineHeight: DialogValues.defaultLineHeight
lineHeightMode: Text.FixedHeight lineHeightMode: Text.FixedHeight
@@ -172,7 +166,7 @@ Item {
states: [ states: [
State { State {
name: "warning" name: "warning"
when: dialogBox.statusType === "warning" when: BackendApi.statusType === "warning"
PropertyChanges { PropertyChanges {
target: statusMessage target: statusMessage
color: DialogValues.textWarning color: DialogValues.textWarning
@@ -185,7 +179,7 @@ Item {
State { State {
name: "error" name: "error"
when: dialogBox.statusType === "error" when: BackendApi.statusType === "error"
PropertyChanges { PropertyChanges {
target: statusMessage target: statusMessage
color: DialogValues.textError color: DialogValues.textError
@@ -208,7 +202,7 @@ Item {
} }
Binding { Binding {
target: dialogBox target: BackendApi
property: "saveAsDefaultLocation" property: "saveAsDefaultLocation"
value: defaultLocationCheckbox.checked value: defaultLocationCheckbox.checked
} }
@@ -219,25 +213,25 @@ Item {
id: screenSizeComboBox id: screenSizeComboBox
actionIndicatorVisible: false actionIndicatorVisible: false
currentIndex: -1 currentIndex: -1
model: screenSizeModel model: BackendApi.screenSizeModel
textRole: "display" textRole: "display"
width: parent.width width: parent.width
font.pixelSize: DialogValues.defaultPixelSize font.pixelSize: DialogValues.defaultPixelSize
onActivated: (index) => { onActivated: (index) => {
dialogBox.setScreenSizeIndex(index); BackendApi.setScreenSizeIndex(index);
var size = screenSizeModel.screenSizes(index); var size = BackendApi.screenSizeModel.screenSizes(index);
widthField.realValue = size.width; widthField.realValue = size.width;
heightField.realValue = size.height; heightField.realValue = size.height;
} }
Connections { Connections {
target: screenSizeModel target: BackendApi.screenSizeModel
function onModelReset() { function onModelReset() {
var newIndex = screenSizeComboBox.currentIndex > -1 var newIndex = screenSizeComboBox.currentIndex > -1
? screenSizeComboBox.currentIndex ? screenSizeComboBox.currentIndex
: dialogBox.screenSizeIndex() : BackendApi.screenSizeIndex()
screenSizeComboBox.currentIndex = newIndex screenSizeComboBox.currentIndex = newIndex
screenSizeComboBox.activated(newIndex) screenSizeComboBox.activated(newIndex)
@@ -248,10 +242,8 @@ Item {
GridLayout { // orientation + width + height GridLayout { // orientation + width + height
width: parent.width width: parent.width
height: 85 height: 85
columns: 4 columns: 4
rows: 2 rows: 2
columnSpacing: 10 columnSpacing: 10
rowSpacing: 10 rowSpacing: 10
@@ -295,10 +287,7 @@ Item {
font.pixelSize: DialogValues.defaultPixelSize font.pixelSize: DialogValues.defaultPixelSize
onRealValueChanged: { onRealValueChanged: {
var height = heightField.realValue if (widthField.realValue >= heightField.realValue)
var width = realValue
if (width >= height)
orientationButton.setHorizontal() orientationButton.setHorizontal()
else else
orientationButton.setVertical() orientationButton.setVertical()
@@ -306,7 +295,7 @@ Item {
} // Width Text Field } // Width Text Field
Binding { Binding {
target: dialogBox target: BackendApi
property: "customWidth" property: "customWidth"
value: widthField.realValue value: widthField.realValue
} }
@@ -323,10 +312,7 @@ Item {
font.pixelSize: DialogValues.defaultPixelSize font.pixelSize: DialogValues.defaultPixelSize
onRealValueChanged: { onRealValueChanged: {
var height = realValue if (widthField.realValue >= heightField.realValue)
var width = widthField.realValue
if (width >= height)
orientationButton.setHorizontal() orientationButton.setHorizontal()
else else
orientationButton.setVertical() orientationButton.setVertical()
@@ -334,7 +320,7 @@ Item {
} // Height Text Field } // Height Text Field
Binding { Binding {
target: dialogBox target: BackendApi
property: "customHeight" property: "customHeight"
value: heightField.realValue value: heightField.realValue
} }
@@ -345,7 +331,6 @@ Item {
id: orientationButton id: orientationButton
implicitWidth: 100 implicitWidth: 100
implicitHeight: 50 implicitHeight: 50
checked: false checked: false
hoverEnabled: false hoverEnabled: false
background: Rectangle { background: Rectangle {
@@ -384,19 +369,19 @@ Item {
onClicked: { onClicked: {
if (widthField.realValue && heightField.realValue) { if (widthField.realValue && heightField.realValue) {
[widthField.realValue, heightField.realValue] = [heightField.realValue, widthField.realValue]; [widthField.realValue, heightField.realValue] = [heightField.realValue, widthField.realValue]
checked = !checked orientationButton.checked = !orientationButton.checked
} }
} }
function setHorizontal() { function setHorizontal() {
checked = false orientationButton.checked = false
horizontalBar.color = DialogValues.textColorInteraction horizontalBar.color = DialogValues.textColorInteraction
verticalBar.color = "white" verticalBar.color = "white"
} }
function setVertical() { function setVertical() {
checked = true orientationButton.checked = true
horizontalBar.color = "white" horizontalBar.color = "white"
verticalBar.color = DialogValues.textColorInteraction verticalBar.color = DialogValues.textColorInteraction
} }
@@ -404,23 +389,27 @@ Item {
} // GridLayout: orientation + width + height } // GridLayout: orientation + width + height
Rectangle { width: parent.width; height: 1; color: DialogValues.dividerlineColor } Rectangle {
width: parent.width
height: 1
color: DialogValues.dividerlineColor
}
SC.CheckBox { SC.CheckBox {
id: useQtVirtualKeyboard id: useQtVirtualKeyboard
actionIndicatorVisible: false actionIndicatorVisible: false
text: qsTr("Use Qt Virtual Keyboard") text: qsTr("Use Qt Virtual Keyboard")
font.pixelSize: DialogValues.defaultPixelSize font.pixelSize: DialogValues.defaultPixelSize
checked: dialogBox.useVirtualKeyboard checked: BackendApi.useVirtualKeyboard
visible: dialogBox.haveVirtualKeyboard visible: BackendApi.haveVirtualKeyboard
} }
RowLayout { // Target Qt Version RowLayout { // Target Qt Version
width: parent.width width: parent.width
visible: dialogBox.haveTargetQtVersion visible: BackendApi.haveTargetQtVersion
Text { Text {
text: "Target Qt Version:" text: qsTr("Target Qt Version:")
font.pixelSize: DialogValues.defaultPixelSize font.pixelSize: DialogValues.defaultPixelSize
lineHeight: DialogValues.defaultLineHeight lineHeight: DialogValues.defaultLineHeight
lineHeightMode: Text.FixedHeight lineHeightMode: Text.FixedHeight
@@ -432,33 +421,98 @@ Item {
actionIndicatorVisible: false actionIndicatorVisible: false
implicitWidth: 70 implicitWidth: 70
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
currentIndex: 1 currentIndex: BackendApi.targetQtVersionIndex
font.pixelSize: DialogValues.defaultPixelSize font.pixelSize: DialogValues.defaultPixelSize
model: ListModel { model: ListModel {
ListElement { ListElement { name: "Qt 5" }
name: "Qt 5" ListElement { name: "Qt 6" }
}
ListElement {
name: "Qt 6"
}
} }
onActivated: (index) => { onActivated: (index) => {
dialogBox.setTargetQtVersion(index) BackendApi.targetQtVersionIndex = index
} }
} // Target Qt Version ComboBox } // Target Qt Version ComboBox
Binding {
target: BackendApi
property: "targetQtVersionIndex"
value: qtVersionComboBox.currentIndex
}
} // RowLayout } // RowLayout
Binding { Binding {
target: dialogBox target: BackendApi
property: "useVirtualKeyboard" property: "useVirtualKeyboard"
value: useQtVirtualKeyboard.checked value: useQtVirtualKeyboard.checked
} }
} // ScrollContent Column } // ScrollContent Column
} // ScrollView } // ScrollView
} // Column } // 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 } // Item
} } // Rectangle
} } // root Item

View File

@@ -29,39 +29,57 @@ import QtQml
import StudioTheme as StudioTheme import StudioTheme as StudioTheme
QtObject { QtObject {
id: root
readonly property int dialogWidth: 1522 readonly property int dialogWidth: 1522
readonly property int dialogHeight: 940 readonly property int dialogHeight: 940
readonly property int projectViewMinimumWidth: 600 readonly property int projectViewMinimumWidth: 600
readonly property int projectViewMinimumHeight: projectViewHeight readonly property int projectViewMinimumHeight: root.gridCellHeight
readonly property int dialogContentHeight: projectViewHeight + 300 // i.e. dialog without header and footer readonly property int dialogContentHeight: root.projectViewHeight + 300 // i.e. dialog without header and footer
readonly property int loadedPanesWidth: detailsPaneWidth + stylesPaneWidth readonly property int loadedPanesWidth: root.detailsPaneWidth + root.stylesPaneWidth
readonly property int detailsPaneWidth: 330 + detailsPanePadding * 2 readonly property int detailsPaneWidth: 330 + root.detailsPanePadding * 2
readonly property int dialogTitleTextHeight: 85 readonly property int dialogTitleTextHeight: 85
readonly property int paneTitleTextHeight: 47 readonly property int paneTitleTextHeight: 47
readonly property int logoWidth: 85 readonly property int logoWidth: 85
readonly property int logoHeight: 85 readonly property int logoHeight: 85
/* detailsScrollableContentHeight - the full height that may need to be scrolled to be fully readonly property int stylesPaneWidth: root.styleImageWidth + root.stylesPanePadding * 2
visible, if the dialog box is too small. */ + root.styleImageBorderWidth * 2 // i.e. 240px
readonly property int detailsScrollableContentHeight: 428
readonly property int stylesPaneWidth: styleImageWidth + stylesPanePadding * 2 + styleImageBorderWidth * 2 // i.e. 240px
readonly property int detailsPanePadding: 18 readonly property int detailsPanePadding: 18
readonly property int stylesPanePadding: 18 readonly property int stylesPanePadding: 18
readonly property int defaultPadding: 18 readonly property int defaultPadding: 18
readonly property int dialogLeftPadding: 35 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 styleImageWidth: 200
readonly property int styleImageHeight: 262
readonly property int styleImageBorderWidth: 2 readonly property int styleImageBorderWidth: 2
readonly property int styleTextHeight: 18
readonly property int footerHeight: 73 readonly property int footerHeight: 73
readonly property int projectItemWidth: 90 readonly property int projectItemWidth: 136
readonly property int projectItemHeight: 144 readonly property int projectItemHeight: 110
readonly property int projectViewHeight: projectItemHeight * 2 property int projectViewHeight: root.projectItemHeight * 2 + root.gridSpacing + root.gridMargins * 2
readonly property int projectViewHeaderHeight: 38 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 dialogButtonWidth: 100
readonly property int loadedPanesHeight: dialogContentHeight // This is for internal popup dialogs
readonly property int detailsPaneHeight: dialogContentHeight 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 darkPaneColor: StudioTheme.Values.themeBackgroundColorNormal
readonly property string lightPaneColor: StudioTheme.Values.themeBackgroundColorAlternate readonly property string lightPaneColor: StudioTheme.Values.themeBackgroundColorAlternate
@@ -71,6 +89,8 @@ QtObject {
readonly property string dividerlineColor: StudioTheme.Values.themeTextColorDisabled readonly property string dividerlineColor: StudioTheme.Values.themeTextColorDisabled
readonly property string textError: StudioTheme.Values.themeError readonly property string textError: StudioTheme.Values.themeError
readonly property string textWarning: StudioTheme.Values.themeWarning 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 defaultPixelSize: 14
readonly property real defaultLineHeight: 21 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 item and spacing after it). So we have to subtract 2 x layout spacing before setting
our own, narrower, spacing. our own, narrower, spacing.
*/ */
return -layoutSpacing -layoutSpacing + value return -layoutSpacing - layoutSpacing + value
} }
} }

View File

@@ -28,111 +28,252 @@ import QtQuick.Controls
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import StudioControls as SC
import StudioTheme as StudioTheme import StudioTheme as StudioTheme
GridView { import BackendApi
id: projectView
ScrollView {
id: scrollView
required property Item loader required property Item loader
required property string currentTabName
cellWidth: DialogValues.projectItemWidth property string backgroundHoverColor: DialogValues.presetItemBackgroundHover
cellHeight: DialogValues.projectItemHeight
clip: true
boundsBehavior: Flickable.StopAtBounds // selectLast: if true, it will select last item in the model after a model reset.
property bool selectLast: false
children: [ ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
Rectangle { ScrollBar.vertical: SC.VerticalScrollBar {
color: DialogValues.darkPaneColor parent: scrollView
anchors.fill: parent x: scrollView.width + (DialogValues.gridMargins
z: -1 - StudioTheme.Values.scrollBarThickness) * 0.5
} y: scrollView.topPadding
] height: scrollView.availableHeight
model: presetModel
// 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
} }
Connections { contentWidth: gridView.contentItem.childrenRect.width
target: presetModel contentHeight: gridView.contentItem.childrenRect.height
// called when data is set (setWizardFactories) GridView {
function onModelReset() { id: gridView
currentIndex = 0
currentIndexChanged()
}
}
delegate: ItemDelegate { clip: true
id: delegate anchors.fill: parent
cellWidth: DialogValues.gridCellWidth
cellHeight: DialogValues.gridCellHeight
rightMargin: -DialogValues.gridSpacing
bottomMargin: -DialogValues.gridSpacing
boundsBehavior: Flickable.StopAtBounds
model: BackendApi.presetModel
width: DialogValues.projectItemWidth // called by onModelReset and when user clicks on an item, or when the header item is changed.
height: DialogValues.projectItemHeight onCurrentIndexChanged: {
background: null BackendApi.selectedPreset = gridView.currentIndex
var source = BackendApi.currentPresetQmlPath()
function fontIconCode(index) { scrollView.loader.source = source
var code = presetModel.fontIconCode(index)
return code ? code : StudioTheme.Constants.wizardsUnknown
} }
Column { Connections {
width: parent.width target: BackendApi.presetModel
height: parent.height
Label { // called when data is set (setWizardFactories)
id: projectTypeIcon function onModelReset() {
text: fontIconCode(index) if (scrollView.selectLast) {
color: DialogValues.textColor gridView.currentIndex = BackendApi.presetModel.rowCount() - 1
width: parent.width scrollView.selectLast = false
height: DialogValues.projectItemHeight / 2 } else {
horizontalAlignment: Text.AlignHCenter gridView.currentIndex = 0
verticalAlignment: Text.AlignBottom }
renderType: Text.NativeRendering
font.pixelSize: 65 // This will load Details.qml and Styles.qml by setting "source" on the Loader.
font.family: StudioTheme.Constants.iconFont.family gridView.currentIndexChanged()
} }
}
delegate: ItemDelegate {
id: delegate
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: 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 { Text {
id: projectTypeLabel text: qsTr("Are you sure you want to delete \"" + removePresetDialog.presetName + "\" ?")
color: DialogValues.textColor color: DialogValues.textColor
text: name
font.pixelSize: DialogValues.defaultPixelSize font.pixelSize: DialogValues.defaultPixelSize
lineHeight: DialogValues.defaultLineHeight wrapMode: Text.WordWrap
lineHeightMode: Text.FixedHeight
width: parent.width width: DialogValues.popupDialogWidth - 2 * DialogValues.popupDialogPadding
height: DialogValues.projectItemHeight / 2
wrapMode: Text.Wrap
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignTop
} }
} // Column
MouseArea { } // Dialog
anchors.fill: parent } // GridView
onClicked: { } // ScrollView
delegate.GridView.view.currentIndex = index
}
}
states: [
State {
when: delegate.GridView.isCurrentItem
PropertyChanges {
target: projectTypeLabel
color: DialogValues.textColorInteraction
}
PropertyChanges {
target: projectTypeIcon
color: DialogValues.textColorInteraction
}
} // State
]
} // ItemDelegate
} // GridView

View File

@@ -0,0 +1,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
}
}

View File

@@ -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
}
}
]
}

View File

@@ -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
}
}

View File

@@ -23,20 +23,21 @@
** **
****************************************************************************/ ****************************************************************************/
import QtQuick
import QtQuick.Window import QtQuick.Window
import QtQuick.Controls import QtQuick.Controls
import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import StudioControls as SC import StudioControls as SC
import StudioTheme as StudioTheme import StudioTheme as StudioTheme
import BackendApi
Item { Item {
width: DialogValues.stylesPaneWidth width: DialogValues.stylesPaneWidth
Component.onCompleted: { Component.onCompleted: {
dialogBox.stylesLoaded = true; BackendApi.stylesLoaded = true
/* /*
* TODO: roleNames is called before the backend model (in the proxy class StyleModel) is * TODO: roleNames is called before the backend model (in the proxy class StyleModel) is
@@ -47,7 +48,7 @@ Item {
} }
Component.onDestruction: { Component.onDestruction: {
dialogBox.stylesLoaded = false; BackendApi.stylesLoaded = false
} }
Rectangle { Rectangle {
@@ -57,116 +58,120 @@ Item {
Item { Item {
x: DialogValues.stylesPanePadding x: DialogValues.stylesPanePadding
width: parent.width - DialogValues.stylesPanePadding * 2 + styleScrollBar.width width: parent.width - DialogValues.stylesPanePadding * 2
height: parent.height height: parent.height
ColumnLayout { Text {
anchors.fill: parent id: styleTitleText
spacing: 5 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
Text { function refresh() {
id: styleTitleText styleTitleText.text = qsTr("Style") + " (" + BackendApi.styleModel.rowCount() + ")"
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() { SC.ComboBox { // Style Filter ComboBox
text = qsTr("Style") + " (" + styleModel.rowCount() + ")" 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 onActivated: (index) => {
actionIndicatorVisible: false BackendApi.styleModel.filter(currentValue.toLowerCase())
currentIndex: 0 styleTitleText.refresh()
textRole: "text" }
valueRole: "value" } // Style Filter ComboBox
font.pixelSize: DialogValues.defaultPixelSize
model: ListModel { ScrollView {
ListElement { text: qsTr("All"); value: "all" } id: scrollView
ListElement { text: qsTr("Light"); value: "light" }
ListElement { text: qsTr("Dark"); value: "dark" }
}
implicitWidth: parent.width anchors.top: styleComboBox.bottom
anchors.topMargin: 11
anchors.bottom: parent.bottom
anchors.bottomMargin: DialogValues.stylesPanePadding
width: parent.width
onActivated: (index) => { ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
styleModel.filter(currentValue.toLowerCase()); ScrollBar.vertical: SC.VerticalScrollBar {
styleTitleText.refresh(); id: styleScrollBar
} x: stylesList.width + (DialogValues.stylesPanePadding
} // Style Filter ComboBox - StudioTheme.Values.scrollBarThickness) * 0.5
}
Item { implicitWidth: 1; implicitHeight: 9 }
ListView { ListView {
id: stylesList id: stylesList
Layout.fillWidth: true anchors.fill: parent
Layout.fillHeight: true
clip: true
model: styleModel
MouseArea {
id: listViewMouseArea
anchors.fill: parent
hoverEnabled: true
propagateComposedEvents: true
}
focus: true focus: true
clip: true
model: BackendApi.styleModel
boundsBehavior: Flickable.StopAtBounds boundsBehavior: Flickable.StopAtBounds
highlightFollowsCurrentItem: false highlightFollowsCurrentItem: false
bottomMargin: -DialogValues.styleListItemBottomMargin
ScrollBar.vertical: SC.VerticalScrollBar {
id: styleScrollBar
property int extraPadding: 0
bottomInset: extraPadding
bottomPadding: bottomInset + 16
viewMouseArea: listViewMouseArea
} // ScrollBar
onCurrentIndexChanged: { onCurrentIndexChanged: {
if (styleModel.rowCount() > 0) if (BackendApi.styleModel.rowCount() > 0)
dialogBox.styleIndex = stylesList.currentIndex; BackendApi.styleIndex = stylesList.currentIndex
} }
delegate: ItemDelegate { delegate: ItemDelegate {
id: delegateId id: delegateId
height: styleImage.height + DialogValues.styleImageBorderWidth + styleText.height + extraPadding.height + 1 width: stylesList.width
width: stylesList.width - styleScrollBar.width height: DialogValues.styleListItemHeight
Component.onCompleted: { onClicked: stylesList.currentIndex = index
styleScrollBar.extraPadding = styleText.height + extraPadding.height
}
Rectangle { background: Rectangle {
anchors.fill: parent anchors.fill: parent
color: DialogValues.lightPaneColor color: DialogValues.lightPaneColor
}
contentItem: Item {
anchors.fill: parent
Column { Column {
spacing: 0
anchors.fill: parent anchors.fill: parent
spacing: DialogValues.styleListItemSpacing
Rectangle { Rectangle {
border.color: index == stylesList.currentIndex ? DialogValues.textColorInteraction : "transparent" width: DialogValues.styleImageWidth
border.width: index == stylesList.currentIndex ? DialogValues.styleImageBorderWidth : 0 + 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" color: "transparent"
width: parent.width
height: parent.height - styleText.height - extraPadding.height
Image { Image {
id: styleImage id: styleImage
asynchronous: false
source: "image://newprojectdialog_library/" + styleModel.iconId(model.index)
width: 200
height: 262
x: DialogValues.styleImageBorderWidth x: DialogValues.styleImageBorderWidth
y: DialogValues.styleImageBorderWidth y: DialogValues.styleImageBorderWidth
width: DialogValues.styleImageWidth
height: DialogValues.styleImageHeight
asynchronous: false
source: "image://newprojectdialog_library/" + BackendApi.styleModel.iconId(model.index)
} }
} // Rectangle } // Rectangle
@@ -175,35 +180,26 @@ Item {
text: model.display text: model.display
font.pixelSize: DialogValues.defaultPixelSize font.pixelSize: DialogValues.defaultPixelSize
lineHeight: DialogValues.defaultLineHeight lineHeight: DialogValues.defaultLineHeight
height: 18 height: DialogValues.styleTextHeight
lineHeightMode: Text.FixedHeight lineHeightMode: Text.FixedHeight
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
width: parent.width width: parent.width
color: DialogValues.textColor color: DialogValues.textColor
} }
Item { id: extraPadding; width: 1; height: 10 }
} // Column } // Column
} // Rectangle
MouseArea {
anchors.fill: parent
onClicked: {
stylesList.currentIndex = index
}
} }
} }
Connections { Connections {
target: styleModel target: BackendApi.styleModel
function onModelReset() { function onModelReset() {
stylesList.currentIndex = dialogBox.styleIndex; stylesList.currentIndex = BackendApi.styleIndex
stylesList.currentIndexChanged(); stylesList.currentIndexChanged()
styleTitleText.refresh(); styleTitleText.refresh()
} }
} }
} // ListView } // ListView
} // ColumnLayout } // ScrollView
} // Parent Item } // Parent Item
} // Rectangle } // Rectangle
} }

View File

@@ -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 Details 1.0 Details.qml
Styles 1.0 Styles.qml singleton DialogValues 1.0 DialogValues.qml
NewProjectView 1.0 NewProjectView.qml NewProjectView 1.0 NewProjectView.qml
Styles 1.0 Styles.qml

View File

@@ -35,37 +35,26 @@ ScrollBar {
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
implicitContentHeight + topPadding + bottomPadding) implicitContentHeight + topPadding + bottomPadding)
property bool scrollBarVisible: parent.childrenRect.height > parent.height property bool scrollBarVisible: parent.contentHeight > scrollBar.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
minimumSize: scrollBar.width / scrollBar.height
orientation: Qt.Vertical orientation: Qt.Vertical
policy: computePolicy() policy: scrollBar.scrollBarVisible ? ScrollBar.AlwaysOn : ScrollBar.AlwaysOff
x: parent.width - width
y: 0
height: parent.availableHeight height: parent.availableHeight
- (parent.bothVisible ? parent.horizontalThickness : 0) - (parent.bothVisible ? parent.horizontalThickness : 0)
padding: 0 padding: scrollBar.active ? StudioTheme.Values.scrollBarActivePadding
: StudioTheme.Values.scrollBarInactivePadding
background: Rectangle { background: Rectangle {
implicitWidth: StudioTheme.Values.scrollBarThickness
implicitHeight: StudioTheme.Values.scrollBarThickness
color: StudioTheme.Values.themeScrollBarTrack color: StudioTheme.Values.themeScrollBarTrack
} }
contentItem: Rectangle { contentItem: Rectangle {
implicitWidth: StudioTheme.Values.scrollBarThickness implicitWidth: StudioTheme.Values.scrollBarThickness - 2 * scrollBar.padding
implicitHeight: StudioTheme.Values.scrollBarThickness - 2 * scrollBar.padding
color: StudioTheme.Values.themeScrollBarHandle 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;
}
} }

View File

@@ -89,6 +89,8 @@ QtObject {
property real typeLabelVerticalShift: Math.round(6 * values.scaleFactor) property real typeLabelVerticalShift: Math.round(6 * values.scaleFactor)
property real scrollBarThickness: 10 property real scrollBarThickness: 10
property real scrollBarActivePadding: 1
property real scrollBarInactivePadding: 2
property real toolTipHeight: 25 property real toolTipHeight: 25
property int toolTipDelay: 1000 property int toolTipDelay: 1000

View File

@@ -14,6 +14,7 @@ add_qtc_plugin(StudioWelcome
createproject.cpp createproject.h createproject.cpp createproject.h
wizardhandler.cpp wizardhandler.h wizardhandler.cpp wizardhandler.h
recentpresets.cpp recentpresets.h recentpresets.cpp recentpresets.h
userpresets.cpp userpresets.h
screensizemodel.h screensizemodel.h
algorithm.h algorithm.h
stylemodel.h stylemodel.cpp stylemodel.h stylemodel.cpp

View File

@@ -25,22 +25,30 @@
#include "presetmodel.h" #include "presetmodel.h"
#include <utils/optional.h> #include <utils/optional.h>
#include <utils/qtcassert.h>
#include "algorithm.h" #include "algorithm.h"
using namespace StudioWelcome; 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 ******************/ /****************** PresetData ******************/
void PresetData::setData(const PresetsByCategory &presetsByCategory, 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); QTC_ASSERT(!presetsByCategory.empty(), return );
m_recents = loadedRecents; m_recents = loadedRecentsData;
m_userPresets = userPresetsData;
if (!m_recents.empty()) { if (!m_recents.empty()) {
m_categories.push_back("Recents"); m_categories.push_back(RecentsTabName);
m_presets.push_back({}); m_presets.push_back({});
} }
@@ -49,28 +57,95 @@ void PresetData::setData(const PresetsByCategory &presetsByCategory,
m_presets.push_back(category.items); m_presets.push_back(category.items);
} }
PresetItems presets = Utils::flatten(m_presets); PresetItems wizardPresets = Utils::flatten(m_presets);
std::vector<PresetItem> recentPresets = makeRecentPresets(presets); PresetItems recentPresets = makeRecentPresets(wizardPresets);
if (!m_recents.empty()) {
if (!m_recents.empty())
m_presets[0] = recentPresets; 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; PresetItems result;
for (const RecentPreset &recent : m_recents) { for (const UserPresetData &userPresetData : m_userPresets) {
auto item = Utils::findOptional(wizardPresets, [&recent](const PresetItem &item) { std::shared_ptr<PresetItem> foundPreset = findPresetItemForUserPreset(userPresetData,
return item.categoryId == std::get<0>(recent) && item.name == std::get<1>(recent); wizardPresets);
}); if (!foundPreset)
continue;
if (item) { auto presetItem = std::make_shared<UserPresetItem>();
item->screenSizeName = std::get<2>(recent);
result.push_back(item.value()); 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;
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> BasePresetModel::roleNames() const
{ {
QHash<int, QByteArray> roleNames; static QHash<int, QByteArray> roleNames{{NameRole, "name"},
roleNames[Qt::UserRole] = "name"; {ScreenSizeRole, "resolution"}};
return roleNames; return roleNames;
} }
@@ -97,8 +172,9 @@ PresetCategoryModel::PresetCategoryModel(const PresetData *data, QObject *parent
: BasePresetModel(data, 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()); 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> PresetModel::roleNames() const
{ {
QHash<int, QByteArray> roleNames; static QHash<int, QByteArray> roleNames{{NameRole, "name"},
roleNames[Qt::UserRole] = "name"; {ScreenSizeRole, "resolution"},
roleNames[Qt::UserRole + 1] = "size"; {IsUserPresetRole, "isUserPreset"}};
return roleNames; return roleNames;
} }
@@ -132,9 +208,16 @@ int PresetModel::rowCount(const QModelIndex &) const
QVariant PresetModel::data(const QModelIndex &index, int role) const QVariant PresetModel::data(const QModelIndex &index, int role) const
{ {
Q_UNUSED(role) std::shared_ptr<PresetItem> preset = presetsOfCurrentCategory().at(index.row());
PresetItem preset = presetsOfCurrentCategory().at(index.row());
return QVariant::fromValue<QString>(preset.name + "\n" + preset.screenSizeName); 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) void PresetModel::setPage(int index)
@@ -148,7 +231,7 @@ void PresetModel::setPage(int index)
QString PresetModel::fontIconCode(int index) const QString PresetModel::fontIconCode(int index) const
{ {
Utils::optional<PresetItem> presetItem = preset(index); std::shared_ptr<PresetItem> presetItem = preset(index);
if (!presetItem) if (!presetItem)
return {}; return {};

View File

@@ -31,8 +31,10 @@
#include <utils/filepath.h> #include <utils/filepath.h>
#include <utils/optional.h> #include <utils/optional.h>
#include <utils/qtcassert.h>
#include "recentpresets.h" #include "recentpresets.h"
#include "userpresets.h"
namespace Utils { namespace Utils {
class Wizard; class Wizard;
@@ -40,38 +42,115 @@ class Wizard;
namespace StudioWelcome { namespace StudioWelcome {
struct UserPresetItem;
struct PresetItem 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 categoryId;
QString screenSizeName; QString screenSizeName;
QString description; QString description;
QUrl qmlPath; QUrl qmlPath;
QString fontIconCode; 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) inline QDebug &operator<<(QDebug &d, const PresetItem &item)
{ {
d << "name=" << item.name; d << "wizardName=" << item.wizardName;
d << "; category = " << item.categoryId; d << "; category = " << item.categoryId;
d << "; size = " << item.screenSizeName; 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; return d;
} }
inline bool operator==(const PresetItem &lhs, const PresetItem &rhs) 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 struct WizardCategory
{ {
QString id; QString id;
QString name; 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) inline QDebug &operator<<(QDebug &d, const WizardCategory &cat)
{ {
d << "id=" << cat.id; d << "id=" << cat.id;
@@ -82,7 +161,6 @@ inline QDebug &operator<<(QDebug &d, const WizardCategory &cat)
} }
using PresetsByCategory = std::map<QString, WizardCategory>; using PresetsByCategory = std::map<QString, WizardCategory>;
using PresetItems = std::vector<PresetItem>;
using Categories = std::vector<QString>; using Categories = std::vector<QString>;
/****************** PresetData ******************/ /****************** PresetData ******************/
@@ -90,18 +168,28 @@ using Categories = std::vector<QString>;
class PresetData class PresetData
{ {
public: 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 std::vector<PresetItems> &presets() const { return m_presets; }
const Categories &categories() const { return m_categories; } const Categories &categories() const { return m_categories; }
private: 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: private:
std::vector<PresetItems> m_presets; std::vector<PresetItems> m_presets;
Categories m_categories; Categories m_categories;
std::vector<RecentPreset> m_recents; std::vector<RecentPresetData> m_recents;
std::vector<UserPresetData> m_userPresets;
PresetsByCategory m_presetsByCategory;
}; };
/****************** PresetCategoryModel ******************/ /****************** PresetCategoryModel ******************/
@@ -149,14 +237,14 @@ public:
int page() const { return static_cast<int>(m_page); } 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(); auto presets = m_data->presets();
if (presets.empty()) if (presets.empty())
return {}; return {};
if (m_page < presets.size()) { 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()) if (selection < presetsOfCategory.size())
return presets.at(m_page).at(selection); return presets.at(m_page).at(selection);
} }
@@ -166,8 +254,10 @@ public:
bool empty() const { return m_data->presets().empty(); } bool empty() const { return m_data->presets().empty(); }
private: 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); return m_data->presets().at(m_page);
} }

View File

@@ -76,22 +76,14 @@ QdsNewDialog::QdsNewDialog(QWidget *parent)
{ {
setParent(m_dialog); 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->setResizeMode(QQuickWidget::SizeRootObjectToView); // SizeViewToRootObject
m_dialog->engine()->addImageProvider(QStringLiteral("newprojectdialog_library"), m_dialog->engine()->addImageProvider(QStringLiteral("newprojectdialog_library"),
new Internal::NewProjectDialogImageProvider()); new Internal::NewProjectDialogImageProvider());
QmlDesigner::Theme::setupTheme(m_dialog->engine()); 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/propertyEditorQmlSources/imports").toString());
m_dialog->engine()->addImportPath(Core::ICore::resourcePath("qmldesigner/newprojectdialog/imports").toString()); m_dialog->engine()->addImportPath(Core::ICore::resourcePath("qmldesigner/newprojectdialog/imports").toString());
QString sourcesPath = qmlPath(); m_dialog->setSource(QUrl::fromLocalFile(qmlPath()));
m_dialog->setSource(QUrl::fromLocalFile(sourcesPath));
m_dialog->setWindowModality(Qt::ApplicationModal); m_dialog->setWindowModality(Qt::ApplicationModal);
m_dialog->setWindowFlags(Qt::Dialog); m_dialog->setWindowFlags(Qt::Dialog);
@@ -114,7 +106,7 @@ QdsNewDialog::QdsNewDialog(QWidget *parent)
}); });
QObject::connect(m_styleModel.data(), &StyleModel::modelAboutToBeReset, this, [this]() { 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_screenSizeModel->setBackendModel(screenSizeModel);
m_styleModel->setBackendModel(styleModel); m_styleModel->setBackendModel(styleModel);
auto userPreset = m_currentPreset->asUserPreset();
if (m_qmlDetailsLoaded) { 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 haveVirtualKeyboardChanged();
emit haveTargetQtVersionChanged(); emit haveTargetQtVersionChanged();
updateScreenSizes();
setProjectName(m_qmlProjectName); setProjectName(m_qmlProjectName);
setProjectLocation(m_qmlProjectLocation.toString()); 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(); m_styleModel->reset();
}
} }
QString QdsNewDialog::currentPresetQmlPath() const QString QdsNewDialog::currentPresetQmlPath() const
@@ -221,10 +242,19 @@ int QdsNewDialog::screenSizeIndex() const
return m_wizard.screenSizeIndex(); return m_wizard.screenSizeIndex();
} }
void QdsNewDialog::setTargetQtVersion(int index) void QdsNewDialog::setTargetQtVersionIndex(int index)
{ {
m_wizard.setTargetQtVersionIndex(index); if (m_qmlTargetQtVersionIndex != index) {
m_qmlTargetQtVersionIndex = index; m_wizard.setTargetQtVersionIndex(index);
m_qmlTargetQtVersionIndex = index;
emit targetQtVersionIndexChanged();
}
}
int QdsNewDialog::getTargetQtVersionIndex() const
{
return m_qmlTargetQtVersionIndex;
} }
void QdsNewDialog::setStyleIndex(int index) void QdsNewDialog::setStyleIndex(int index)
@@ -267,6 +297,14 @@ int QdsNewDialog::getStyleIndex() const
return m_styleModel->actualIndex(m_qmlStyleIndex); 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_, void QdsNewDialog::setWizardFactories(QList<Core::IWizardFactory *> factories_,
const Utils::FilePath &defaultLocation, const Utils::FilePath &defaultLocation,
const QVariantMap &) const QVariantMap &)
@@ -275,8 +313,9 @@ void QdsNewDialog::setWizardFactories(QList<Core::IWizardFactory *> factories_,
WizardFactories factories{factories_, m_dialog, platform}; WizardFactories factories{factories_, m_dialog, platform};
std::vector<RecentPreset> recents = m_recentsStore.fetchAll(); std::vector<RecentPresetData> recents = m_recentsStore.fetchAll();
m_presetData.setData(factories.presetsGroupedByCategory(), recents); std::vector<UserPresetData> userPresets = m_userPresetsStore.fetchAll();
m_presetData.setData(factories.presetsGroupedByCategory(), userPresets, recents);
m_categoryModel->reset(); m_categoryModel->reset();
m_presetModel->reset(); m_presetModel->reset();
@@ -296,12 +335,45 @@ void QdsNewDialog::setWizardFactories(QList<Core::IWizardFactory *> factories_,
m_qmlProjectLocation = Utils::FilePath::fromString(QDir::toNativeSeparators(projectLocation.toString())); m_qmlProjectLocation = Utils::FilePath::fromString(QDir::toNativeSeparators(projectLocation.toString()));
emit projectLocationChanged(); // So that QML knows to update the field 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) { if (m_qmlDetailsLoaded) {
updateScreenSizes(); 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(); m_styleModel->reset();
}
} }
QString QdsNewDialog::qmlPath() const QString QdsNewDialog::qmlPath() const
@@ -338,10 +410,10 @@ void QdsNewDialog::accept()
.withTargetQtVersion(m_qmlTargetQtVersionIndex) .withTargetQtVersion(m_qmlTargetQtVersionIndex)
.execute(); .execute();
PresetItem item = m_wizard.preset(); std::shared_ptr<PresetItem> item = m_wizard.preset();
QString customSizeName = m_qmlCustomWidth + " x " + m_qmlCustomHeight; 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->close();
m_dialog->deleteLater(); m_dialog->deleteLater();
@@ -355,6 +427,7 @@ void QdsNewDialog::reject()
m_wizard.destroyWizard(); m_wizard.destroyWizard();
m_dialog->close(); m_dialog->close();
m_dialog = nullptr;
} }
QString QdsNewDialog::chooseProjectLocation() QString QdsNewDialog::chooseProjectLocation()
@@ -375,7 +448,76 @@ void QdsNewDialog::setSelectedPreset(int selection)
setProjectDescription(m_currentPreset->description); setProjectDescription(m_currentPreset->description);
m_presetPage = m_presetModel->page(); 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();
}

View File

@@ -29,13 +29,13 @@
#include <coreplugin/dialogs/newdialog.h> #include <coreplugin/dialogs/newdialog.h>
#include <utils/infolabel.h> #include <utils/infolabel.h>
#include <utils/optional.h>
#include "wizardhandler.h" #include "wizardhandler.h"
#include "presetmodel.h" #include "presetmodel.h"
#include "screensizemodel.h" #include "screensizemodel.h"
#include "stylemodel.h" #include "stylemodel.h"
#include "recentpresets.h" #include "recentpresets.h"
#include "userpresets.h"
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
class QStandardItemModel; class QStandardItemModel;
@@ -57,22 +57,31 @@ public:
Q_PROPERTY(bool useVirtualKeyboard MEMBER m_qmlUseVirtualKeyboard READ getUseVirtualKeyboard WRITE setUseVirtualKeyboard NOTIFY useVirtualKeyboardChanged) 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 haveVirtualKeyboard MEMBER m_qmlHaveVirtualKeyboard READ getHaveVirtualKeyboard NOTIFY haveVirtualKeyboardChanged)
Q_PROPERTY(bool haveTargetQtVersion MEMBER m_qmlHaveTargetQtVersion READ getHaveTargetQtVersion NOTIFY haveTargetQtVersionChanged) 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(bool saveAsDefaultLocation MEMBER m_qmlSaveAsDefaultLocation WRITE setSaveAsDefaultLocation)
Q_PROPERTY(QString statusMessage MEMBER m_qmlStatusMessage READ getStatusMessage NOTIFY statusMessageChanged) Q_PROPERTY(QString statusMessage MEMBER m_qmlStatusMessage READ getStatusMessage NOTIFY statusMessageChanged)
Q_PROPERTY(QString statusType MEMBER m_qmlStatusType READ getStatusType NOTIFY statusTypeChanged) Q_PROPERTY(QString statusType MEMBER m_qmlStatusType READ getStatusType NOTIFY statusTypeChanged)
Q_PROPERTY(bool fieldsValid MEMBER m_qmlFieldsValid READ getFieldsValid NOTIFY fieldsValidChanged) Q_PROPERTY(bool 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 detailsLoaded MEMBER m_qmlDetailsLoaded)
Q_PROPERTY(bool stylesLoaded MEMBER m_qmlStylesLoaded) Q_PROPERTY(bool stylesLoaded MEMBER m_qmlStylesLoaded)
Q_INVOKABLE void removeCurrentPreset();
Q_INVOKABLE QString currentPresetQmlPath() const; Q_INVOKABLE QString currentPresetQmlPath() const;
// TODO: screen size index should better be a property // TODO: screen size index should better be a property
Q_INVOKABLE void setScreenSizeIndex(int index); // called when ComboBox item is "activated" Q_INVOKABLE void setScreenSizeIndex(int index); // called when ComboBox item is "activated"
Q_INVOKABLE int screenSizeIndex() const; Q_INVOKABLE int screenSizeIndex() const;
Q_INVOKABLE void setTargetQtVersion(int index);
Q_INVOKABLE QString chooseProjectLocation(); 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); explicit QdsNewDialog(QWidget *parent);
QWidget *widget() override { return m_dialog; } QWidget *widget() override { return m_dialog; }
@@ -85,7 +94,11 @@ public:
void setStyleIndex(int index); void setStyleIndex(int index);
int getStyleIndex() const; 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 getUseVirtualKeyboard() const { return m_qmlUseVirtualKeyboard; }
bool getFieldsValid() const { return m_qmlFieldsValid; } bool getFieldsValid() const { return m_qmlFieldsValid; }
@@ -101,6 +114,8 @@ public slots:
void accept(); void accept();
void reject(); void reject();
void savePresetDialogAccept();
signals: signals:
void projectNameChanged(); void projectNameChanged();
void projectLocationChanged(); void projectLocationChanged();
@@ -111,6 +126,9 @@ signals:
void statusMessageChanged(); void statusMessageChanged();
void statusTypeChanged(); void statusTypeChanged();
void fieldsValidChanged(); void fieldsValidChanged();
void targetQtVersionIndexChanged();
void userPresetSaved();
void lastUserPresetRemoved();
private slots: private slots:
void onStatusMessageChanged(Utils::InfoLabel::InfoType type, const QString &message); void onStatusMessageChanged(Utils::InfoLabel::InfoType type, const QString &message);
@@ -160,6 +178,7 @@ private:
bool m_qmlFieldsValid = false; bool m_qmlFieldsValid = false;
QString m_qmlStatusMessage; QString m_qmlStatusMessage;
QString m_qmlStatusType; QString m_qmlStatusType;
QString m_qmlPresetName;
int m_presetPage = -1; // i.e. the page in the Presets View int m_presetPage = -1; // i.e. the page in the Presets View
@@ -169,10 +188,11 @@ private:
bool m_qmlDetailsLoaded = false; bool m_qmlDetailsLoaded = false;
bool m_qmlStylesLoaded = false; bool m_qmlStylesLoaded = false;
Utils::optional<PresetItem> m_currentPreset; std::shared_ptr<PresetItem> m_currentPreset;
WizardHandler m_wizard; WizardHandler m_wizard;
RecentPresetsStore m_recentsStore; RecentPresetsStore m_recentsStore;
UserPresetsStore m_userPresetsStore;
}; };
} //namespace StudioWelcome } //namespace StudioWelcome

View File

@@ -32,19 +32,27 @@
#include <utils/qtcassert.h> #include <utils/qtcassert.h>
#include <utils/qtcsettings.h> #include <utils/qtcsettings.h>
using Core::ICore;
using Utils::QtcSettings;
using namespace StudioWelcome; using namespace StudioWelcome;
constexpr char GROUP_NAME[] = "RecentPresets"; constexpr char GROUP_NAME[] = "RecentPresets";
constexpr char WIZARDS[] = "Wizards"; 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(); std::vector<RecentPresetData> existing = fetchAll();
QStringList encodedRecents = addRecentToExisting(RecentPreset{categoryId, name, sizeName},
existing); 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->beginGroup(GROUP_NAME);
m_settings->setValue(WIZARDS, encodedRecents); m_settings->setValue(WIZARDS, encodedRecents);
@@ -52,8 +60,26 @@ void RecentPresetsStore::add(const QString &categoryId, const QString &name, con
m_settings->sync(); m_settings->sync();
} }
QStringList RecentPresetsStore::addRecentToExisting(const RecentPreset &preset, std::vector<RecentPresetData> RecentPresetsStore::remove(const QString &categoryId, const QString &presetName)
std::vector<RecentPreset> &recents) {
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::erase_one(recents, preset);
Utils::prepend(recents, preset); Utils::prepend(recents, preset);
@@ -61,48 +87,64 @@ QStringList RecentPresetsStore::addRecentToExisting(const RecentPreset &preset,
if (int(recents.size()) > m_max) if (int(recents.size()) > m_max)
recents.pop_back(); 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); m_settings->beginGroup(GROUP_NAME);
QVariant value = m_settings->value(WIZARDS); QVariant value = m_settings->value(WIZARDS);
m_settings->endGroup(); m_settings->endGroup();
std::vector<RecentPreset> result; std::vector<RecentPresetData> result;
if (value.type() == QVariant::String) if (value.type() == QVariant::String)
result.push_back(decodeOneRecentPreset(value.toString())); result.push_back(decodeOneRecentPreset(value.toString()));
else if (value.type() == QVariant::StringList) else if (value.type() == QVariant::StringList)
Utils::concat(result, decodeRecentPresets(value.toList())); Utils::concat(result, decodeRecentPresets(value.toList()));
const RecentPreset empty; const RecentPresetData empty;
return Utils::filtered(result, [&empty](const RecentPreset &recent) { return recent != 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 Utils::transform<QList>(recents, [](const RecentPresetData &p) -> QString {
return std::get<0>(p) + "/" + std::get<1>(p) + ":" + std::get<2>(p); 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); auto m = pattern.match(encoded);
if (!m.hasMatch()) if (!m.hasMatch())
return RecentPreset{}; return RecentPresetData{};
QString category = m.captured(1); QString category = m.captured(1);
QString name = m.captured(2); QString name = m.captured(2);
QString size = m.captured(3); QString size = m.captured(3);
bool isUserPreset = name.startsWith("[U]");
if (isUserPreset)
name = name.split("[U]")[1];
return std::make_tuple(category, name, size); 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 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 Utils::transform<std::vector>(values, [](const QVariant &value) {
return decodeOneRecentPreset(value.toString()); return decodeOneRecentPreset(value.toString());

View File

@@ -31,8 +31,43 @@
namespace StudioWelcome { namespace StudioWelcome {
// preset category, preset name, size name struct RecentPresetData
using RecentPreset = std::tuple<QString, QString, QString>; {
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 class RecentPresetsStore
{ {
@@ -42,14 +77,21 @@ public:
{} {}
void setMaximum(int n) { m_max = n; } void setMaximum(int n) { m_max = n; }
void add(const QString &categoryId, const QString &name, const QString &sizeName); void add(const QString &categoryId,
std::vector<RecentPreset> fetchAll() const; 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: private:
QStringList addRecentToExisting(const RecentPreset &preset, std::vector<RecentPreset> &recents); std::vector<RecentPresetData> addRecentToExisting(const RecentPresetData &preset,
static QStringList encodeRecentPresets(const std::vector<RecentPreset> &recents); std::vector<RecentPresetData> &recents);
static std::vector<RecentPreset> decodeRecentPresets(const QVariantList &values); static QStringList encodeRecentPresets(const std::vector<RecentPresetData> &recents);
static RecentPreset decodeOneRecentPreset(const QString &encoded); static std::vector<RecentPresetData> decodeRecentPresets(const QVariantList &values);
static RecentPresetData decodeOneRecentPreset(const QString &encoded);
void save(const std::vector<RecentPresetData> &recents);
private: private:
QSettings *m_settings = nullptr; QSettings *m_settings = nullptr;

View File

@@ -38,7 +38,9 @@ QtcPlugin {
"wizardhandler.cpp", "wizardhandler.cpp",
"wizardhandler.h", "wizardhandler.h",
"recentpresets.cpp", "recentpresets.cpp",
"recentpresets.h" "recentpresets.h",
"userpresets.cpp",
"userpresets.h"
] ]
Group { Group {

View File

@@ -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();
}

View File

@@ -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

View File

@@ -76,7 +76,7 @@ void WizardFactories::filter()
m_factories = acceptedFactories; m_factories = acceptedFactories;
} }
PresetItem WizardFactories::makePresetItem(JsonWizardFactory *f, QWidget *parent, std::shared_ptr<PresetItem> WizardFactories::makePresetItem(JsonWizardFactory *f, QWidget *parent,
const Utils::Id &platform) const Utils::Id &platform)
{ {
using namespace std::placeholders; using namespace std::placeholders;
@@ -89,16 +89,17 @@ PresetItem WizardFactories::makePresetItem(JsonWizardFactory *f, QWidget *parent
else else
sizeName = screenSizes[index]; sizeName = screenSizes[index];
return { auto result = std::make_shared<PresetItem>();
/*.name =*/f->displayName(), result->wizardName = f->displayName();
/*.categoryId =*/f->category(), result->categoryId = f->category();
/*.screenSizeName=*/sizeName, result->screenSizeName=sizeName;
/*.description =*/f->description(), result->description = f->description();
/*.qmlPath =*/f->detailsPageQmlPath(), result->qmlPath = f->detailsPageQmlPath();
/*.fontIconCode =*/m_getIconUnicode(f->fontIconName()), result->fontIconCode = m_getIconUnicode(f->fontIconName());
/*.create =*/ std::bind(&JsonWizardFactory::runWizard, f, _1, parent, platform, result->create
QVariantMap(), false), = std::bind(&JsonWizardFactory::runWizard, f, _1, parent, platform, QVariantMap(), false);
};
return result;
} }
std::map<QString, WizardCategory> WizardFactories::makePresetItemsGroupedByCategory() std::map<QString, WizardCategory> WizardFactories::makePresetItemsGroupedByCategory()

View File

@@ -64,7 +64,7 @@ private:
void sortByCategoryAndId(); void sortByCategoryAndId();
void filter(); 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(); std::map<QString, WizardCategory> makePresetItemsGroupedByCategory();
private: private:

View File

@@ -38,7 +38,7 @@
using namespace StudioWelcome; 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_preset = presetInfo;
m_selectedPreset = presetSelection; m_selectedPreset = presetSelection;
@@ -67,7 +67,7 @@ void WizardHandler::destroyWizard()
void WizardHandler::setupWizard() void WizardHandler::setupWizard()
{ {
m_wizard = m_preset.create(m_projectLocation); m_wizard = m_preset->create(m_projectLocation);
if (!m_wizard) { if (!m_wizard) {
emit wizardCreationFailed(); emit wizardCreationFailed();
return; return;
@@ -142,6 +142,11 @@ QStandardItemModel *WizardHandler::getScreenFactorModel(ProjectExplorer::JsonFie
return cbfield->model(); return cbfield->model();
} }
bool WizardHandler::haveStyleModel() const
{
return m_wizard->hasField("ControlsStyle");
}
QStandardItemModel *WizardHandler::getStyleModel(ProjectExplorer::JsonFieldPage *page) QStandardItemModel *WizardHandler::getStyleModel(ProjectExplorer::JsonFieldPage *page)
{ {
auto *field = page->jsonField("ControlsStyle"); auto *field = page->jsonField("ControlsStyle");
@@ -229,6 +234,47 @@ bool WizardHandler::haveTargetQtVersion() const
return m_wizard->hasField("TargetQtVersion"); 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) void WizardHandler::setStyleIndex(int index)
{ {
auto *field = m_detailsPage->jsonField("ControlsStyle"); auto *field = m_detailsPage->jsonField("ControlsStyle");
@@ -247,6 +293,38 @@ int WizardHandler::styleIndex() const
return cbfield->selectedRow(); 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) void WizardHandler::setUseVirtualKeyboard(bool value)
{ {
auto *field = m_detailsPage->jsonField("UseVirtualKeyboard"); auto *field = m_detailsPage->jsonField("UseVirtualKeyboard");

View File

@@ -47,15 +47,23 @@ class WizardHandler: public QObject
Q_OBJECT Q_OBJECT
public: public:
void reset(const PresetItem &presetInfo, int presetSelection); void reset(const std::shared_ptr<PresetItem> &presetInfo, int presetSelection);
void setScreenSizeIndex(int index); void setScreenSizeIndex(int index);
int screenSizeIndex(const QString &sizeName) const; int screenSizeIndex(const QString &sizeName) const;
QString screenSizeName(int index) const; QString screenSizeName(int index) const;
int screenSizeIndex() const; int screenSizeIndex() const;
int targetQtVersionIndex() const;
int targetQtVersionIndex(const QString &qtVersionName) const;
void setTargetQtVersionIndex(int index); void setTargetQtVersionIndex(int index);
bool haveTargetQtVersion() const; bool haveTargetQtVersion() const;
QString targetQtVersionName(int index) const;
void setStyleIndex(int index); void setStyleIndex(int index);
int styleIndex() const; int styleIndex() const;
int styleIndex(const QString &styleName) const;
QString styleName(int index) const;
bool haveStyleModel() const;
void destroyWizard(); void destroyWizard();
void setUseVirtualKeyboard(bool value); void setUseVirtualKeyboard(bool value);
@@ -66,7 +74,7 @@ public:
void run(const std::function<void (QWizardPage *)> &processPage); void run(const std::function<void (QWizardPage *)> &processPage);
PresetItem preset() const { return m_preset; } std::shared_ptr<PresetItem> preset() const { return m_preset; }
signals: signals:
void deletingWizard(); void deletingWizard();
@@ -93,7 +101,7 @@ private:
int m_selectedPreset = -1; int m_selectedPreset = -1;
PresetItem m_preset; std::shared_ptr<PresetItem> m_preset;
Utils::FilePath m_projectLocation; Utils::FilePath m_projectLocation;
}; };

View File

@@ -18,12 +18,14 @@ add_qtc_test(tst_qml_wizard
wizardfactories-test.cpp wizardfactories-test.cpp
stylemodel-test.cpp stylemodel-test.cpp
recentpresets-test.cpp recentpresets-test.cpp
userpresets-test.cpp
presetmodel-test.cpp presetmodel-test.cpp
test-utilities.h test-utilities.h
test-main.cpp test-main.cpp
"${StudioWelcomeDir}/wizardfactories.cpp" "${StudioWelcomeDir}/wizardfactories.cpp"
"${StudioWelcomeDir}/stylemodel.cpp" "${StudioWelcomeDir}/stylemodel.cpp"
"${StudioWelcomeDir}/recentpresets.cpp" "${StudioWelcomeDir}/recentpresets.cpp"
"${StudioWelcomeDir}/userpresets.cpp"
"${StudioWelcomeDir}/presetmodel.cpp" "${StudioWelcomeDir}/presetmodel.cpp"
) )

View File

@@ -33,10 +33,18 @@ using ::testing::ElementsAreArray;
using ::testing::PrintToString; using ::testing::PrintToString;
namespace StudioWelcome { namespace StudioWelcome {
void PrintTo(const UserPresetItem &item, std::ostream *os);
void PrintTo(const PresetItem &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 << ", " *os << "{categId: " << item.categoryId << ", "
<< "name: " << item.name; << "name: " << item.wizardName;
if (!item.screenSizeName.isEmpty()) if (!item.screenSizeName.isEmpty())
*os << ", size: " << item.screenSizeName; *os << ", size: " << item.screenSizeName;
@@ -44,6 +52,26 @@ void PrintTo(const PresetItem &item, std::ostream *os)
*os << "}"; *os << "}";
} }
void PrintTo(const UserPresetItem &item, std::ostream *os)
{
*os << "{categId: " << item.categoryId << ", "
<< "name: " << item.wizardName << ", "
<< "user name: " << item.userName;
if (!item.screenSizeName.isEmpty())
*os << ", size: " << item.screenSizeName;
*os << "}";
}
void PrintTo(const std::shared_ptr<PresetItem> &p, std::ostream *os)
{
if (p)
PrintTo(*p, os);
else
*os << "{null}";
}
} // namespace StudioWelcome } // namespace StudioWelcome
namespace { namespace {
@@ -51,20 +79,47 @@ std::pair<QString, WizardCategory> aCategory(const QString &categId,
const QString &categName, const QString &categName,
const std::vector<QString> &names) const std::vector<QString> &names)
{ {
std::vector<PresetItem> items = Utils::transform(names, [&categId](const QString &name) { std::vector<std::shared_ptr<PresetItem>> items
return PresetItem{name, categId}; = 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}); 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})) 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})) 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 } // namespace
@@ -88,6 +143,7 @@ TEST(QdsPresetModel, haveSameArraySizeForPresetsAndCategories)
aCategory("A.categ", "A", {"item a", "item b"}), aCategory("A.categ", "A", {"item a", "item b"}),
aCategory("B.categ", "B", {"item c", "item d"}), aCategory("B.categ", "B", {"item c", "item d"}),
}, },
{/*user presets*/},
{/*recents*/}); {/*recents*/});
ASSERT_THAT(data.presets(), SizeIs(2)); ASSERT_THAT(data.presets(), SizeIs(2));
@@ -105,6 +161,7 @@ TEST(QdsPresetModel, haveWizardPresetsNoRecents)
aCategory("A.categ", "A", {"item a", "item b"}), aCategory("A.categ", "A", {"item a", "item b"}),
aCategory("B.categ", "B", {"item c", "item d"}), aCategory("B.categ", "B", {"item c", "item d"}),
}, },
{/*user presets*/},
{/*recents*/}); {/*recents*/});
// Then // Then
@@ -115,11 +172,30 @@ TEST(QdsPresetModel, haveWizardPresetsNoRecents)
ElementsAre(PresetIs("B.categ", "item c"), PresetIs("B.categ", "item d"))); 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) TEST(QdsPresetModel, haveRecentsNoWizardPresets)
{ {
PresetData data; PresetData data;
data.setData({/*wizardPresets*/}, data.setData({/*wizardPresets*/},
{/*user presets*/},
{ {
{"A.categ", "Desktop", "640 x 480"}, {"A.categ", "Desktop", "640 x 480"},
{"B.categ", "Mobile", "800 x 600"}, {"B.categ", "Mobile", "800 x 600"},
@@ -129,7 +205,7 @@ TEST(QdsPresetModel, haveRecentsNoWizardPresets)
ASSERT_THAT(data.presets(), IsEmpty()); ASSERT_THAT(data.presets(), IsEmpty());
} }
TEST(QdsPresetModel, recentsAddedBeforeWizardPresets) TEST(QdsPresetModel, recentsAddedWithWizardPresets)
{ {
// Given // Given
PresetData data; PresetData data;
@@ -141,6 +217,7 @@ TEST(QdsPresetModel, recentsAddedBeforeWizardPresets)
aCategory("A.categ", "A", {"Desktop", "item b"}), aCategory("A.categ", "A", {"Desktop", "item b"}),
aCategory("B.categ", "B", {"item c", "Mobile"}), aCategory("B.categ", "B", {"item c", "Mobile"}),
}, },
{/*user presets*/},
/*recents*/ /*recents*/
{ {
{"A.categ", "Desktop", "800 x 600"}, {"A.categ", "Desktop", "800 x 600"},
@@ -158,7 +235,98 @@ TEST(QdsPresetModel, recentsAddedBeforeWizardPresets)
ElementsAre(PresetIs("B.categ", "item c"), PresetIs("B.categ", "Mobile"))})); 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 // Given
PresetData data; PresetData data;
@@ -171,6 +339,7 @@ TEST(QdsPresetModel, recentsShouldNotSorted)
aCategory("B.categ", "B", {"item c", "Mobile"}), aCategory("B.categ", "B", {"item c", "Mobile"}),
aCategory("Z.categ", "Z", {"Z.desktop"}), aCategory("Z.categ", "Z", {"Z.desktop"}),
}, },
{/*user presets*/},
/*recents*/ /*recents*/
{ {
{"Z.categ", "Z.desktop", "200 x 300"}, {"Z.categ", "Z.desktop", "200 x 300"},
@@ -197,6 +366,7 @@ TEST(QdsPresetModel, recentsOfSameWizardProjectButDifferentSizesAreRecognizedAsD
aCategory("A.categ", "A", {"Desktop"}), aCategory("A.categ", "A", {"Desktop"}),
aCategory("B.categ", "B", {"Mobile"}), aCategory("B.categ", "B", {"Mobile"}),
}, },
{/*user presets*/},
/*recents*/ /*recents*/
{ {
{"B.categ", "Mobile", "400 x 400"}, {"B.categ", "Mobile", "400 x 400"},
@@ -223,6 +393,7 @@ TEST(QdsPresetModel, outdatedRecentsAreNotShown)
aCategory("A.categ", "A", {"Desktop"}), aCategory("A.categ", "A", {"Desktop"}),
aCategory("B.categ", "B", {"Mobile"}), aCategory("B.categ", "B", {"Mobile"}),
}, },
{/*user presets*/},
/*recents*/ /*recents*/
{ {
{"B.categ", "NoLongerExists", "400 x 400"}, {"B.categ", "NoLongerExists", "400 x 400"},

View File

@@ -38,6 +38,16 @@ using namespace StudioWelcome;
constexpr char GROUP_NAME[] = "RecentPresets"; constexpr char GROUP_NAME[] = "RecentPresets";
constexpr char ITEMS[] = "Wizards"; 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 class QdsRecentPresets : public ::testing::Test
{ {
protected: protected:
@@ -73,7 +83,7 @@ TEST_F(QdsRecentPresets, readFromEmptyStore)
{ {
RecentPresetsStore store{&settings}; RecentPresetsStore store{&settings};
std::vector<RecentPreset> recents = store.fetchAll(); std::vector<RecentPresetData> recents = store.fetchAll();
ASSERT_THAT(recents, IsEmpty()); ASSERT_THAT(recents, IsEmpty());
} }
@@ -82,7 +92,7 @@ TEST_F(QdsRecentPresets, readEmptyRecentPresets)
{ {
RecentPresetsStore store = aStoreWithOne(""); RecentPresetsStore store = aStoreWithOne("");
std::vector<RecentPreset> recents = store.fetchAll(); std::vector<RecentPresetData> recents = store.fetchAll();
ASSERT_THAT(recents, IsEmpty()); ASSERT_THAT(recents, IsEmpty());
} }
@@ -91,25 +101,34 @@ TEST_F(QdsRecentPresets, readOneRecentPresetAsList)
{ {
RecentPresetsStore store = aStoreWithRecents({"category/preset:640 x 480"}); 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) TEST_F(QdsRecentPresets, readOneRecentPresetAsString)
{ {
RecentPresetsStore store = aStoreWithOne("category/preset:200 x 300"); RecentPresetsStore store = aStoreWithOne("category/preset:200 x 300");
std::vector<RecentPreset> recents = store.fetchAll(); 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")));
}
TEST_F(QdsRecentPresets, readOneRecentUserPresetAsString)
{
RecentPresetsStore store = aStoreWithOne("category/[U]preset:200 x 300");
std::vector<RecentPresetData> recents = store.fetchAll();
ASSERT_THAT(recents, ElementsAre(RecentPresetData("category", "preset", "200 x 300", true)));
} }
TEST_F(QdsRecentPresets, readBadRecentPresetAsString) TEST_F(QdsRecentPresets, readBadRecentPresetAsString)
{ {
RecentPresetsStore store = aStoreWithOne("no_category_only_preset"); RecentPresetsStore store = aStoreWithOne("no_category_only_preset");
std::vector<RecentPreset> recents = store.fetchAll(); std::vector<RecentPresetData> recents = store.fetchAll();
ASSERT_THAT(recents, IsEmpty()); ASSERT_THAT(recents, IsEmpty());
} }
@@ -118,24 +137,32 @@ TEST_F(QdsRecentPresets, readBadRecentPresetAsInt)
{ {
RecentPresetsStore store = aStoreWithOne(32); RecentPresetsStore store = aStoreWithOne(32);
std::vector<RecentPreset> recents = store.fetchAll(); std::vector<RecentPresetData> recents = store.fetchAll();
ASSERT_THAT(recents, IsEmpty()); ASSERT_THAT(recents, IsEmpty());
} }
TEST_F(QdsRecentPresets, readBadRecentPresetsInList) TEST_F(QdsRecentPresets, readBadRecentPresetsInList)
{ {
RecentPresetsStore store = aStoreWithRecents({"bad1", // no category, no size RecentPresetsStore store = aStoreWithRecents({
"categ/name:800 x 600", // good "bad1", // no category, no size
"categ/bad2", //no size "categ/name:800 x 600", // good
"categ/bad3:", //no size "categ/bad2", //no size
"categ 1/bad4:200 x 300", // category has space "categ/bad3:", //no size
"categ/bad5: 400 x 300", // size starts with space "categ 1/bad4:200 x 300", // category has space
"categ/bad6:400"}); // bad size "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<RecentPreset> recents = store.fetchAll(); 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) TEST_F(QdsRecentPresets, readTwoRecentPresets)
@@ -143,11 +170,23 @@ TEST_F(QdsRecentPresets, readTwoRecentPresets)
RecentPresetsStore store = aStoreWithRecents( RecentPresetsStore store = aStoreWithRecents(
{"category_1/preset 1:640 x 480", "category_2/preset 2:320 x 200"}); {"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, ASSERT_THAT(recents,
ElementsAre(RecentPreset("category_1", "preset 1", "640 x 480"), ElementsAre(RecentPresetData("category_1", "preset 1", "640 x 480"),
RecentPreset("category_2", "preset 2", "320 x 200"))); 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) TEST_F(QdsRecentPresets, addFirstRecentPreset)
@@ -155,19 +194,44 @@ TEST_F(QdsRecentPresets, addFirstRecentPreset)
RecentPresetsStore store{&settings}; RecentPresetsStore store{&settings};
store.add("A.Category", "Normal Application", "400 x 600"); 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) 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"); store.add("category", "preset", "200 x 300");
std::vector<RecentPreset> recents = store.fetchAll(); 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")));
}
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) TEST_F(QdsRecentPresets, addSecondRecentPreset)
@@ -175,11 +239,11 @@ TEST_F(QdsRecentPresets, addSecondRecentPreset)
RecentPresetsStore store = aStoreWithRecents({"A.Category/Preset 1:800 x 600"}); RecentPresetsStore store = aStoreWithRecents({"A.Category/Preset 1:800 x 600"});
store.add("A.Category", "Preset 2", "640 x 480"); store.add("A.Category", "Preset 2", "640 x 480");
std::vector<RecentPreset> recents = store.fetchAll(); std::vector<RecentPresetData> recents = store.fetchAll();
ASSERT_THAT(recents, ASSERT_THAT(recents,
ElementsAre(RecentPreset("A.Category", "Preset 2", "640 x 480"), ElementsAre(RecentPresetData("A.Category", "Preset 2", "640 x 480"),
RecentPreset("A.Category", "Preset 1", "800 x 600"))); RecentPresetData("A.Category", "Preset 1", "800 x 600")));
} }
TEST_F(QdsRecentPresets, addSecondRecentPresetSameKindButDifferentSize) TEST_F(QdsRecentPresets, addSecondRecentPresetSameKindButDifferentSize)
@@ -187,11 +251,11 @@ TEST_F(QdsRecentPresets, addSecondRecentPresetSameKindButDifferentSize)
RecentPresetsStore store = aStoreWithRecents({"A.Category/Preset:800 x 600"}); RecentPresetsStore store = aStoreWithRecents({"A.Category/Preset:800 x 600"});
store.add("A.Category", "Preset", "640 x 480"); store.add("A.Category", "Preset", "640 x 480");
std::vector<RecentPreset> recents = store.fetchAll(); std::vector<RecentPresetData> recents = store.fetchAll();
ASSERT_THAT(recents, ASSERT_THAT(recents,
ElementsAre(RecentPreset("A.Category", "Preset", "640 x 480"), ElementsAre(RecentPresetData("A.Category", "Preset", "640 x 480"),
RecentPreset("A.Category", "Preset", "800 x 600"))); RecentPresetData("A.Category", "Preset", "800 x 600")));
} }
TEST_F(QdsRecentPresets, fetchesRecentPresetsInTheReverseOrderTheyWereAdded) 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 1", "640 x 480");
store.add("A.Category", "Preset 2", "640 x 480"); store.add("A.Category", "Preset 2", "640 x 480");
store.add("A.Category", "Preset 3", "800 x 600"); store.add("A.Category", "Preset 3", "800 x 600");
std::vector<RecentPreset> recents = store.fetchAll(); std::vector<RecentPresetData> recents = store.fetchAll();
ASSERT_THAT(recents, ASSERT_THAT(recents,
ElementsAre(RecentPreset("A.Category", "Preset 3", "800 x 600"), ElementsAre(RecentPresetData("A.Category", "Preset 3", "800 x 600"),
RecentPreset("A.Category", "Preset 2", "640 x 480"), RecentPresetData("A.Category", "Preset 2", "640 x 480"),
RecentPreset("A.Category", "Preset 1", "640 x 480"))); RecentPresetData("A.Category", "Preset 1", "640 x 480")));
} }
TEST_F(QdsRecentPresets, addingAnExistingRecentPresetMakesItTheFirst) TEST_F(QdsRecentPresets, addingAnExistingRecentPresetMakesItTheFirst)
@@ -216,12 +280,12 @@ TEST_F(QdsRecentPresets, addingAnExistingRecentPresetMakesItTheFirst)
"A.Category/Preset 3:640 x 480"}); "A.Category/Preset 3:640 x 480"});
store.add("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, ASSERT_THAT(recents,
ElementsAre(RecentPreset("A.Category", "Preset 3", "640 x 480"), ElementsAre(RecentPresetData("A.Category", "Preset 3", "640 x 480"),
RecentPreset("A.Category", "Preset 1", "200 x 300"), RecentPresetData("A.Category", "Preset 1", "200 x 300"),
RecentPreset("A.Category", "Preset 2", "200 x 300"))); RecentPresetData("A.Category", "Preset 2", "200 x 300")));
} }
TEST_F(QdsRecentPresets, addingTooManyRecentPresetsRemovesTheOldestOne) TEST_F(QdsRecentPresets, addingTooManyRecentPresetsRemovesTheOldestOne)
@@ -231,9 +295,9 @@ TEST_F(QdsRecentPresets, addingTooManyRecentPresetsRemovesTheOldestOne)
store.setMaximum(2); store.setMaximum(2);
store.add("A.Category", "Preset 3", "200 x 300"); store.add("A.Category", "Preset 3", "200 x 300");
std::vector<RecentPreset> recents = store.fetchAll(); std::vector<RecentPresetData> recents = store.fetchAll();
ASSERT_THAT(recents, ASSERT_THAT(recents,
ElementsAre(RecentPreset("A.Category", "Preset 3", "200 x 300"), ElementsAre(RecentPresetData("A.Category", "Preset 3", "200 x 300"),
RecentPreset("A.Category", "Preset 2", "200 x 300"))); RecentPresetData("A.Category", "Preset 2", "200 x 300")));
} }

View File

@@ -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]));
}

View File

@@ -151,7 +151,7 @@ private:
QStringList presetNames(const WizardCategory &cat) 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; return result;
} }
@@ -319,9 +319,9 @@ TEST_F(QdsWizardFactories, createsPresetItemAndCategoryCorrectlyFromWizardFactor
ASSERT_EQ("myDisplayCategory", category.name); ASSERT_EQ("myDisplayCategory", category.name);
auto presetItem = presets["myCategoryId"].items[0]; auto presetItem = presets["myCategoryId"].items[0];
ASSERT_EQ("myName", presetItem.name); ASSERT_EQ("myName", presetItem->wizardName);
ASSERT_EQ("myDescription", presetItem.description); ASSERT_EQ("myDescription", presetItem->description);
ASSERT_EQ("qrc:/my/qml/path", presetItem.qmlPath.toString()); ASSERT_EQ("qrc:/my/qml/path", presetItem->qmlPath.toString());
ASSERT_EQ("\uABCD", presetItem.fontIconCode); ASSERT_EQ("\uABCD", presetItem->fontIconCode);
} }