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

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

View File

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

View File

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

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

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

View File

@@ -35,37 +35,26 @@ ScrollBar {
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
implicitContentHeight + topPadding + bottomPadding)
property bool scrollBarVisible: parent.childrenRect.height > parent.height
// viewMouseArea: if set, the scrollbar will be visible only on hover over the view containing
// the mouse area item.
property MouseArea viewMouseArea: null
minimumSize: orientation == Qt.Horizontal ? height / width : width / height
property bool scrollBarVisible: parent.contentHeight > scrollBar.height
minimumSize: scrollBar.width / scrollBar.height
orientation: Qt.Vertical
policy: computePolicy()
x: parent.width - width
y: 0
policy: scrollBar.scrollBarVisible ? ScrollBar.AlwaysOn : ScrollBar.AlwaysOff
height: parent.availableHeight
- (parent.bothVisible ? parent.horizontalThickness : 0)
padding: 0
padding: scrollBar.active ? StudioTheme.Values.scrollBarActivePadding
: StudioTheme.Values.scrollBarInactivePadding
background: Rectangle {
implicitWidth: StudioTheme.Values.scrollBarThickness
implicitHeight: StudioTheme.Values.scrollBarThickness
color: StudioTheme.Values.themeScrollBarTrack
}
contentItem: Rectangle {
implicitWidth: StudioTheme.Values.scrollBarThickness
implicitWidth: StudioTheme.Values.scrollBarThickness - 2 * scrollBar.padding
implicitHeight: StudioTheme.Values.scrollBarThickness - 2 * scrollBar.padding
color: StudioTheme.Values.themeScrollBarHandle
}
function computePolicy() {
if (!scrollBar.scrollBarVisible)
return ScrollBar.AlwaysOff;
if (scrollBar.viewMouseArea)
return scrollBar.viewMouseArea.containsMouse ? ScrollBar.AlwaysOn : ScrollBar.AlwaysOff
else
return ScrollBar.AlwaysOn;
}
}

View File

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

View File

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

View File

@@ -25,22 +25,30 @@
#include "presetmodel.h"
#include <utils/optional.h>
#include <utils/qtcassert.h>
#include "algorithm.h"
using namespace StudioWelcome;
constexpr int NameRole = Qt::UserRole;
constexpr int ScreenSizeRole = Qt::UserRole + 1;
constexpr int IsUserPresetRole = Qt::UserRole + 2;
static const QString RecentsTabName = QObject::tr("Recents");
static const QString CustomTabName = QObject::tr("Custom");
/****************** PresetData ******************/
void PresetData::setData(const PresetsByCategory &presetsByCategory,
const std::vector<RecentPreset> &loadedRecents)
const std::vector<UserPresetData> &userPresetsData,
const std::vector<RecentPresetData> &loadedRecentsData)
{
QTC_ASSERT(!presetsByCategory.empty(), return);
m_recents = loadedRecents;
QTC_ASSERT(!presetsByCategory.empty(), return );
m_recents = loadedRecentsData;
m_userPresets = userPresetsData;
if (!m_recents.empty()) {
m_categories.push_back("Recents");
m_categories.push_back(RecentsTabName);
m_presets.push_back({});
}
@@ -49,28 +57,95 @@ void PresetData::setData(const PresetsByCategory &presetsByCategory,
m_presets.push_back(category.items);
}
PresetItems presets = Utils::flatten(m_presets);
PresetItems wizardPresets = Utils::flatten(m_presets);
std::vector<PresetItem> recentPresets = makeRecentPresets(presets);
if (!m_recents.empty())
PresetItems recentPresets = makeRecentPresets(wizardPresets);
if (!m_recents.empty()) {
m_presets[0] = recentPresets;
}
PresetItems userPresetItems = makeUserPresets(wizardPresets);
if (!userPresetItems.empty()) {
m_categories.push_back(CustomTabName);
m_presets.push_back(userPresetItems);
}
m_presetsByCategory = presetsByCategory;
}
std::vector<PresetItem> PresetData::makeRecentPresets(const PresetItems &wizardPresets)
void PresetData::reload(const std::vector<UserPresetData> &userPresetsData,
const std::vector<RecentPresetData> &loadedRecentsData)
{
static const PresetItem empty;
m_categories.clear();
m_presets.clear();
m_recents.clear();
m_userPresets.clear();
setData(m_presetsByCategory, userPresetsData, loadedRecentsData);
}
std::shared_ptr<PresetItem> PresetData::findPresetItemForUserPreset(const UserPresetData &preset,
const PresetItems &wizardPresets)
{
return Utils::findOrDefault(wizardPresets, [&preset](const std::shared_ptr<PresetItem> &item) {
return item->wizardName == preset.wizardName && item->categoryId == preset.categoryId;
});
}
PresetItems PresetData::makeUserPresets(const PresetItems &wizardPresets)
{
PresetItems result;
for (const RecentPreset &recent : m_recents) {
auto item = Utils::findOptional(wizardPresets, [&recent](const PresetItem &item) {
return item.categoryId == std::get<0>(recent) && item.name == std::get<1>(recent);
});
for (const UserPresetData &userPresetData : m_userPresets) {
std::shared_ptr<PresetItem> foundPreset = findPresetItemForUserPreset(userPresetData,
wizardPresets);
if (!foundPreset)
continue;
if (item) {
item->screenSizeName = std::get<2>(recent);
result.push_back(item.value());
auto presetItem = std::make_shared<UserPresetItem>();
presetItem->categoryId = userPresetData.categoryId;
presetItem->wizardName = userPresetData.wizardName;
presetItem->screenSizeName = userPresetData.screenSize;
presetItem->userName = userPresetData.name;
presetItem->qtVersion = userPresetData.qtVersion;
presetItem->styleName = userPresetData.styleName;
presetItem->useQtVirtualKeyboard = userPresetData.useQtVirtualKeyboard;
presetItem->create = foundPreset->create;
presetItem->description = foundPreset->description;
presetItem->fontIconCode = foundPreset->fontIconCode;
presetItem->qmlPath = foundPreset->qmlPath;
result.push_back(presetItem);
}
return result;
}
std::shared_ptr<PresetItem> PresetData::findPresetItemForRecent(const RecentPresetData &recent, const PresetItems &wizardPresets)
{
return Utils::findOrDefault(wizardPresets, [&recent](const std::shared_ptr<PresetItem> &item) {
bool sameName = item->categoryId == recent.category
&& item->displayName() == recent.presetName;
bool sameType = (recent.isUserPreset ? item->isUserPreset() : !item->isUserPreset());
return sameName && sameType;
});
}
PresetItems PresetData::makeRecentPresets(const PresetItems &wizardPresets)
{
PresetItems result;
for (const RecentPresetData &recent : m_recents) {
std::shared_ptr<PresetItem> preset = findPresetItemForRecent(recent, wizardPresets);
if (preset) {
auto clone = std::shared_ptr<PresetItem>{preset->clone()};
clone->screenSizeName = recent.sizeName;
result.push_back(clone);
}
}
@@ -86,8 +161,8 @@ BasePresetModel::BasePresetModel(const PresetData *data, QObject *parent)
QHash<int, QByteArray> BasePresetModel::roleNames() const
{
QHash<int, QByteArray> roleNames;
roleNames[Qt::UserRole] = "name";
static QHash<int, QByteArray> roleNames{{NameRole, "name"},
{ScreenSizeRole, "resolution"}};
return roleNames;
}
@@ -97,8 +172,9 @@ PresetCategoryModel::PresetCategoryModel(const PresetData *data, QObject *parent
: BasePresetModel(data, parent)
{}
int PresetCategoryModel::rowCount(const QModelIndex &) const
int PresetCategoryModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return static_cast<int>(m_data->categories().size());
}
@@ -116,9 +192,9 @@ PresetModel::PresetModel(const PresetData *data, QObject *parent)
QHash<int, QByteArray> PresetModel::roleNames() const
{
QHash<int, QByteArray> roleNames;
roleNames[Qt::UserRole] = "name";
roleNames[Qt::UserRole + 1] = "size";
static QHash<int, QByteArray> roleNames{{NameRole, "name"},
{ScreenSizeRole, "resolution"},
{IsUserPresetRole, "isUserPreset"}};
return roleNames;
}
@@ -132,9 +208,16 @@ int PresetModel::rowCount(const QModelIndex &) const
QVariant PresetModel::data(const QModelIndex &index, int role) const
{
Q_UNUSED(role)
PresetItem preset = presetsOfCurrentCategory().at(index.row());
return QVariant::fromValue<QString>(preset.name + "\n" + preset.screenSizeName);
std::shared_ptr<PresetItem> preset = presetsOfCurrentCategory().at(index.row());
if (role == NameRole)
return preset->displayName();
else if (role == ScreenSizeRole)
return preset->screenSize();
else if (role == IsUserPresetRole)
return preset->isUserPreset();
else
return {};
}
void PresetModel::setPage(int index)
@@ -148,7 +231,7 @@ void PresetModel::setPage(int index)
QString PresetModel::fontIconCode(int index) const
{
Utils::optional<PresetItem> presetItem = preset(index);
std::shared_ptr<PresetItem> presetItem = preset(index);
if (!presetItem)
return {};

View File

@@ -31,8 +31,10 @@
#include <utils/filepath.h>
#include <utils/optional.h>
#include <utils/qtcassert.h>
#include "recentpresets.h"
#include "userpresets.h"
namespace Utils {
class Wizard;
@@ -40,38 +42,115 @@ class Wizard;
namespace StudioWelcome {
struct UserPresetItem;
struct PresetItem
{
QString name;
PresetItem() = default;
PresetItem(const QString &wizardName, const QString &category, const QString &sizeName = "")
: wizardName{wizardName}
, categoryId{category}
, screenSizeName{sizeName}
{}
virtual ~PresetItem() {}
virtual QString displayName() const { return wizardName; }
virtual QString screenSize() const { return screenSizeName; }
virtual std::unique_ptr<PresetItem> clone() const
{
return std::unique_ptr<PresetItem>{new PresetItem{*this}};
}
virtual bool isUserPreset() const { return false; }
virtual UserPresetItem *asUserPreset() { return nullptr; }
std::function<Utils::Wizard *(const Utils::FilePath &path)> create;
public:
QString wizardName;
QString categoryId;
QString screenSizeName;
QString description;
QUrl qmlPath;
QString fontIconCode;
std::function<Utils::Wizard *(const Utils::FilePath &path)> create;
};
struct UserPresetItem : public PresetItem
{
UserPresetItem() = default;
UserPresetItem(const QString &userName,
const QString &wizardName,
const QString &category,
const QString &sizeName = "")
: PresetItem{wizardName, category, sizeName}
, userName{userName}
{}
QString displayName() const override { return userName; }
std::unique_ptr<PresetItem> clone() const override
{
return std::unique_ptr<PresetItem>{new UserPresetItem{*this}};
}
bool isUserPreset() const override { return true; }
UserPresetItem *asUserPreset() override { return this; }
bool isValid() const
{
return !categoryId.isEmpty() && !wizardName.isEmpty() && !userName.isEmpty();
}
public:
QString userName;
bool useQtVirtualKeyboard;
QString qtVersion;
QString styleName;
};
inline QDebug &operator<<(QDebug &d, const UserPresetItem &item);
inline QDebug &operator<<(QDebug &d, const PresetItem &item)
{
d << "name=" << item.name;
d << "wizardName=" << item.wizardName;
d << "; category = " << item.categoryId;
d << "; size = " << item.screenSizeName;
if (item.isUserPreset())
d << (UserPresetItem &) item;
return d;
}
inline QDebug &operator<<(QDebug &d, const UserPresetItem &item)
{
d << "userName=" << item.userName;
return d;
}
inline bool operator==(const PresetItem &lhs, const PresetItem &rhs)
{
return lhs.categoryId == rhs.categoryId && lhs.name == rhs.name;
return lhs.categoryId == rhs.categoryId && lhs.wizardName == rhs.wizardName;
}
using PresetItems = std::vector<std::shared_ptr<PresetItem>>;
struct WizardCategory
{
QString id;
QString name;
std::vector<PresetItem> items;
PresetItems items;
};
inline QDebug &operator<<(QDebug &d, const std::shared_ptr<PresetItem> &preset)
{
if (preset)
d << *preset;
else
d << "(null)";
return d;
}
inline QDebug &operator<<(QDebug &d, const WizardCategory &cat)
{
d << "id=" << cat.id;
@@ -82,7 +161,6 @@ inline QDebug &operator<<(QDebug &d, const WizardCategory &cat)
}
using PresetsByCategory = std::map<QString, WizardCategory>;
using PresetItems = std::vector<PresetItem>;
using Categories = std::vector<QString>;
/****************** PresetData ******************/
@@ -90,18 +168,28 @@ using Categories = std::vector<QString>;
class PresetData
{
public:
void setData(const PresetsByCategory &presets, const std::vector<RecentPreset> &recents);
void reload(const std::vector<UserPresetData> &userPresets,
const std::vector<RecentPresetData> &loadedRecents);
void setData(const PresetsByCategory &presets,
const std::vector<UserPresetData> &userPresets,
const std::vector<RecentPresetData> &recents);
const std::vector<PresetItems> &presets() const { return m_presets; }
const Categories &categories() const { return m_categories; }
private:
std::vector<PresetItem> makeRecentPresets(const PresetItems &wizardPresets);
PresetItems makeRecentPresets(const PresetItems &wizardPresets);
PresetItems makeUserPresets(const PresetItems &wizardPresets);
std::shared_ptr<PresetItem> findPresetItemForUserPreset(const UserPresetData &preset, const PresetItems &wizardPresets);
std::shared_ptr<PresetItem> findPresetItemForRecent(const RecentPresetData &recent, const PresetItems &wizardPresets);
private:
std::vector<PresetItems> m_presets;
Categories m_categories;
std::vector<RecentPreset> m_recents;
std::vector<RecentPresetData> m_recents;
std::vector<UserPresetData> m_userPresets;
PresetsByCategory m_presetsByCategory;
};
/****************** PresetCategoryModel ******************/
@@ -149,14 +237,14 @@ public:
int page() const { return static_cast<int>(m_page); }
Utils::optional<PresetItem> preset(size_t selection) const
const std::shared_ptr<PresetItem> preset(size_t selection) const
{
auto presets = m_data->presets();
if (presets.empty())
return {};
if (m_page < presets.size()) {
const std::vector<PresetItem> presetsOfCategory = presets.at(m_page);
const PresetItems presetsOfCategory = presets.at(m_page);
if (selection < presetsOfCategory.size())
return presets.at(m_page).at(selection);
}
@@ -166,8 +254,10 @@ public:
bool empty() const { return m_data->presets().empty(); }
private:
const std::vector<PresetItem> presetsOfCurrentCategory() const
const PresetItems presetsOfCurrentCategory() const
{
QTC_ASSERT(m_page < m_data->presets().size(), return {});
return m_data->presets().at(m_page);
}

View File

@@ -76,22 +76,14 @@ QdsNewDialog::QdsNewDialog(QWidget *parent)
{
setParent(m_dialog);
m_dialog->rootContext()->setContextProperties(QVector<QQmlContext::PropertyPair>{
{{"categoryModel"}, QVariant::fromValue(m_categoryModel.data())},
{{"presetModel"}, QVariant::fromValue(m_presetModel.data())},
{{"screenSizeModel"}, QVariant::fromValue(m_screenSizeModel.data())},
{{"styleModel"}, QVariant::fromValue(m_styleModel.data())},
{{"dialogBox"}, QVariant::fromValue(this)},
});
m_dialog->setResizeMode(QQuickWidget::SizeRootObjectToView); // SizeViewToRootObject
m_dialog->engine()->addImageProvider(QStringLiteral("newprojectdialog_library"),
new Internal::NewProjectDialogImageProvider());
QmlDesigner::Theme::setupTheme(m_dialog->engine());
qmlRegisterSingletonInstance<QdsNewDialog>("BackendApi", 1, 0, "BackendApi", this);
m_dialog->engine()->addImportPath(Core::ICore::resourcePath("qmldesigner/propertyEditorQmlSources/imports").toString());
m_dialog->engine()->addImportPath(Core::ICore::resourcePath("qmldesigner/newprojectdialog/imports").toString());
QString sourcesPath = qmlPath();
m_dialog->setSource(QUrl::fromLocalFile(sourcesPath));
m_dialog->setSource(QUrl::fromLocalFile(qmlPath()));
m_dialog->setWindowModality(Qt::ApplicationModal);
m_dialog->setWindowFlags(Qt::Dialog);
@@ -114,7 +106,7 @@ QdsNewDialog::QdsNewDialog(QWidget *parent)
});
QObject::connect(m_styleModel.data(), &StyleModel::modelAboutToBeReset, this, [this]() {
this->m_qmlStyleIndex = -1;
m_qmlStyleIndex = -1;
});
}
@@ -188,18 +180,47 @@ void QdsNewDialog::onWizardCreated(QStandardItemModel *screenSizeModel, QStandar
m_screenSizeModel->setBackendModel(screenSizeModel);
m_styleModel->setBackendModel(styleModel);
auto userPreset = m_currentPreset->asUserPreset();
if (m_qmlDetailsLoaded) {
updateScreenSizes();
if (m_currentPreset->isUserPreset()) {
if (m_wizard.haveVirtualKeyboard())
setUseVirtualKeyboard(userPreset->useQtVirtualKeyboard);
if (m_wizard.haveTargetQtVersion()) {
int index = m_wizard.targetQtVersionIndex(userPreset->qtVersion);
if (index != -1)
setTargetQtVersionIndex(index);
}
} else {
if (m_wizard.haveTargetQtVersion()) {
int index = m_wizard.targetQtVersionIndex();
if (index != -1)
setTargetQtVersionIndex(index);
}
}
emit haveVirtualKeyboardChanged();
emit haveTargetQtVersionChanged();
updateScreenSizes();
setProjectName(m_qmlProjectName);
setProjectLocation(m_qmlProjectLocation.toString());
}
if (m_qmlStylesLoaded)
if (m_qmlStylesLoaded && m_wizard.haveStyleModel()) {
if (m_currentPreset->isUserPreset()) {
int index = m_wizard.styleIndex(userPreset->styleName);
if (index != -1)
setStyleIndex(index);
} else {
/* NOTE: For a builtin preset, we don't need to set style index. That's because defaults
* will be loaded from the backend Wizard.
*/
}
m_styleModel->reset();
}
}
QString QdsNewDialog::currentPresetQmlPath() const
@@ -221,10 +242,19 @@ int QdsNewDialog::screenSizeIndex() const
return m_wizard.screenSizeIndex();
}
void QdsNewDialog::setTargetQtVersion(int index)
void QdsNewDialog::setTargetQtVersionIndex(int index)
{
m_wizard.setTargetQtVersionIndex(index);
m_qmlTargetQtVersionIndex = index;
if (m_qmlTargetQtVersionIndex != index) {
m_wizard.setTargetQtVersionIndex(index);
m_qmlTargetQtVersionIndex = index;
emit targetQtVersionIndexChanged();
}
}
int QdsNewDialog::getTargetQtVersionIndex() const
{
return m_qmlTargetQtVersionIndex;
}
void QdsNewDialog::setStyleIndex(int index)
@@ -267,6 +297,14 @@ int QdsNewDialog::getStyleIndex() const
return m_styleModel->actualIndex(m_qmlStyleIndex);
}
void QdsNewDialog::setUseVirtualKeyboard(bool value)
{
if (m_qmlUseVirtualKeyboard != value) {
m_qmlUseVirtualKeyboard = value;
emit useVirtualKeyboardChanged();
}
}
void QdsNewDialog::setWizardFactories(QList<Core::IWizardFactory *> factories_,
const Utils::FilePath &defaultLocation,
const QVariantMap &)
@@ -275,8 +313,9 @@ void QdsNewDialog::setWizardFactories(QList<Core::IWizardFactory *> factories_,
WizardFactories factories{factories_, m_dialog, platform};
std::vector<RecentPreset> recents = m_recentsStore.fetchAll();
m_presetData.setData(factories.presetsGroupedByCategory(), recents);
std::vector<RecentPresetData> recents = m_recentsStore.fetchAll();
std::vector<UserPresetData> userPresets = m_userPresetsStore.fetchAll();
m_presetData.setData(factories.presetsGroupedByCategory(), userPresets, recents);
m_categoryModel->reset();
m_presetModel->reset();
@@ -296,12 +335,45 @@ void QdsNewDialog::setWizardFactories(QList<Core::IWizardFactory *> factories_,
m_qmlProjectLocation = Utils::FilePath::fromString(QDir::toNativeSeparators(projectLocation.toString()));
emit projectLocationChanged(); // So that QML knows to update the field
/* NOTE:
* Here we expect that details are loaded && that styles are loaded. We use the
* functionality below to update the state of the first item that is selected right when
* the dialog pops up. Otherwise, relying solely on onWizardCreated is not useful, since
* for the dialog popup, that wizard is created before details & styles are loaded.
*
* It might be a better alternative to receive notifications from QML in the cpp file, that
* style is loaded, and that details is loaded, and do updates from there. But, if we handle
* those events, they may be called before the wizard is created - so we would need to make
* sure that all events have occurred before we go ahead and configure the wizard.
*/
auto userPreset = m_currentPreset->asUserPreset();
if (m_qmlDetailsLoaded) {
updateScreenSizes();
if (m_wizard.haveTargetQtVersion()) {
int index = (userPreset ? m_wizard.targetQtVersionIndex(userPreset->qtVersion)
: m_wizard.targetQtVersionIndex());
if (index != -1)
setTargetQtVersionIndex(index);
}
if (m_wizard.haveVirtualKeyboard() && userPreset)
setUseVirtualKeyboard(userPreset->useQtVirtualKeyboard);
emit haveVirtualKeyboardChanged();
emit haveTargetQtVersionChanged();
}
if (m_qmlStylesLoaded)
if (m_qmlStylesLoaded && m_wizard.haveStyleModel()) {
if (userPreset) {
int index = m_wizard.styleIndex(userPreset->styleName);
if (index != -1)
setStyleIndex(index);
}
m_styleModel->reset();
}
}
QString QdsNewDialog::qmlPath() const
@@ -338,10 +410,10 @@ void QdsNewDialog::accept()
.withTargetQtVersion(m_qmlTargetQtVersionIndex)
.execute();
PresetItem item = m_wizard.preset();
std::shared_ptr<PresetItem> item = m_wizard.preset();
QString customSizeName = m_qmlCustomWidth + " x " + m_qmlCustomHeight;
m_recentsStore.add(item.categoryId, item.name, customSizeName);
m_recentsStore.add(item->categoryId, item->displayName(), customSizeName, item->isUserPreset());
m_dialog->close();
m_dialog->deleteLater();
@@ -355,6 +427,7 @@ void QdsNewDialog::reject()
m_wizard.destroyWizard();
m_dialog->close();
m_dialog = nullptr;
}
QString QdsNewDialog::chooseProjectLocation()
@@ -375,7 +448,76 @@ void QdsNewDialog::setSelectedPreset(int selection)
setProjectDescription(m_currentPreset->description);
m_presetPage = m_presetModel->page();
m_wizard.reset(m_currentPreset.value(), m_qmlSelectedPreset);
m_wizard.reset(m_currentPreset, m_qmlSelectedPreset);
}
}
}
void QdsNewDialog::savePresetDialogAccept()
{
QString screenSize = m_qmlCustomWidth + " x " + m_qmlCustomHeight;
QString targetQtVersion = "";
QString styleName = "";
bool useVirtualKeyboard = false;
if (m_wizard.haveTargetQtVersion())
targetQtVersion = m_wizard.targetQtVersionName(m_qmlTargetQtVersionIndex);
if (m_wizard.haveStyleModel())
styleName = m_wizard.styleName(m_qmlStyleIndex);
if (m_wizard.haveVirtualKeyboard())
useVirtualKeyboard = m_qmlUseVirtualKeyboard;
UserPresetData preset = {m_currentPreset->categoryId,
m_currentPreset->wizardName,
m_qmlPresetName,
screenSize,
useVirtualKeyboard,
targetQtVersion,
styleName};
if (!m_userPresetsStore.save(preset)) {
QMessageBox::warning(m_dialog,
tr("Save Preset"),
tr("A preset with this name already exists."));
return;
}
// reload model
std::vector<RecentPresetData> recents = m_recentsStore.fetchAll();
std::vector<UserPresetData> userPresets = m_userPresetsStore.fetchAll();
m_presetData.reload(userPresets, recents);
m_categoryModel->reset();
emit userPresetSaved();
}
void QdsNewDialog::removeCurrentPreset()
{
if (!m_currentPreset->isUserPreset()) {
qWarning() << "Will not attempt to remove non-user preset";
return;
}
// remove preset & reload model
std::vector<RecentPresetData> recents = m_recentsStore.remove(m_currentPreset->categoryId,
m_currentPreset->displayName());
auto userPreset = m_currentPreset->asUserPreset();
m_userPresetsStore.remove(userPreset->categoryId, userPreset->displayName());
std::vector<UserPresetData> userPresets = m_userPresetsStore.fetchAll();
m_presetData.reload(userPresets, recents);
m_qmlSelectedPreset = -1;
m_presetPage = -1;
if (userPresets.size() == 0) {
m_presetModel->setPage(0);
emit lastUserPresetRemoved();
}
m_categoryModel->reset();
m_presetModel->reset();
}

View File

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

View File

@@ -32,19 +32,27 @@
#include <utils/qtcassert.h>
#include <utils/qtcsettings.h>
using Core::ICore;
using Utils::QtcSettings;
using namespace StudioWelcome;
constexpr char GROUP_NAME[] = "RecentPresets";
constexpr char WIZARDS[] = "Wizards";
void RecentPresetsStore::add(const QString &categoryId, const QString &name, const QString &sizeName)
void RecentPresetsStore::add(const QString &categoryId,
const QString &name,
const QString &sizeName,
bool isUserPreset)
{
std::vector<RecentPreset> existing = fetchAll();
QStringList encodedRecents = addRecentToExisting(RecentPreset{categoryId, name, sizeName},
existing);
std::vector<RecentPresetData> existing = fetchAll();
std::vector<RecentPresetData> recents
= addRecentToExisting(RecentPresetData{categoryId, name, sizeName, isUserPreset}, existing);
save(recents);
}
void RecentPresetsStore::save(const std::vector<RecentPresetData> &recents)
{
QStringList encodedRecents = encodeRecentPresets(recents);
m_settings->beginGroup(GROUP_NAME);
m_settings->setValue(WIZARDS, encodedRecents);
@@ -52,8 +60,26 @@ void RecentPresetsStore::add(const QString &categoryId, const QString &name, con
m_settings->sync();
}
QStringList RecentPresetsStore::addRecentToExisting(const RecentPreset &preset,
std::vector<RecentPreset> &recents)
std::vector<RecentPresetData> RecentPresetsStore::remove(const QString &categoryId, const QString &presetName)
{
std::vector<RecentPresetData> recents = fetchAll();
size_t countBefore = recents.size();
/* NOTE: when removing one preset, it may happen that there are more than one recent for that
* preset. In that case, we need to remove all associated recents, for the preset.*/
Utils::erase(recents, [&](const RecentPresetData &p) {
return p.category == categoryId && p.presetName == presetName;
});
if (recents.size() < countBefore)
save(recents);
return recents;
}
std::vector<RecentPresetData> RecentPresetsStore::addRecentToExisting(
const RecentPresetData &preset, std::vector<RecentPresetData> &recents)
{
Utils::erase_one(recents, preset);
Utils::prepend(recents, preset);
@@ -61,48 +87,64 @@ QStringList RecentPresetsStore::addRecentToExisting(const RecentPreset &preset,
if (int(recents.size()) > m_max)
recents.pop_back();
return encodeRecentPresets(recents);
return recents;
}
std::vector<RecentPreset> RecentPresetsStore::fetchAll() const
std::vector<RecentPresetData> RecentPresetsStore::fetchAll() const
{
m_settings->beginGroup(GROUP_NAME);
QVariant value = m_settings->value(WIZARDS);
m_settings->endGroup();
std::vector<RecentPreset> result;
std::vector<RecentPresetData> result;
if (value.type() == QVariant::String)
result.push_back(decodeOneRecentPreset(value.toString()));
else if (value.type() == QVariant::StringList)
Utils::concat(result, decodeRecentPresets(value.toList()));
const RecentPreset empty;
return Utils::filtered(result, [&empty](const RecentPreset &recent) { return recent != empty; });
const RecentPresetData empty;
return Utils::filtered(result, [&empty](const RecentPresetData &recent) { return recent != empty; });
}
QStringList RecentPresetsStore::encodeRecentPresets(const std::vector<RecentPreset> &recents)
QStringList RecentPresetsStore::encodeRecentPresets(const std::vector<RecentPresetData> &recents)
{
return Utils::transform<QList>(recents, [](const RecentPreset &p) -> QString {
return std::get<0>(p) + "/" + std::get<1>(p) + ":" + std::get<2>(p);
return Utils::transform<QList>(recents, [](const RecentPresetData &p) -> QString {
QString name = p.presetName;
if (p.isUserPreset)
name.prepend("[U]");
return p.category + "/" + name + ":" + p.sizeName;
});
}
RecentPreset RecentPresetsStore::decodeOneRecentPreset(const QString &encoded)
RecentPresetData RecentPresetsStore::decodeOneRecentPreset(const QString &encoded)
{
QRegularExpression pattern{R"(^(\S+)/(.+):(\d+ x \d+))"};
QRegularExpression pattern{R"(^(\S+)/(.+):(\d+ x \d+)$)"};
auto m = pattern.match(encoded);
if (!m.hasMatch())
return RecentPreset{};
return RecentPresetData{};
QString category = m.captured(1);
QString name = m.captured(2);
QString size = m.captured(3);
bool isUserPreset = name.startsWith("[U]");
if (isUserPreset)
name = name.split("[U]")[1];
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 decodeOneRecentPreset(value.toString());

View File

@@ -31,8 +31,43 @@
namespace StudioWelcome {
// preset category, preset name, size name
using RecentPreset = std::tuple<QString, QString, QString>;
struct RecentPresetData
{
RecentPresetData() = default;
RecentPresetData(const QString &category,
const QString &name,
const QString &size,
bool isUserPreset = false)
: category{category}
, presetName{name}
, sizeName{size}
, isUserPreset{isUserPreset}
{}
QString category;
QString presetName;
QString sizeName;
bool isUserPreset = false;
};
inline bool operator==(const RecentPresetData &lhs, const RecentPresetData &rhs)
{
return lhs.category == rhs.category && lhs.presetName == rhs.presetName
&& lhs.sizeName == rhs.sizeName && lhs.isUserPreset == rhs.isUserPreset;
}
inline bool operator!=(const RecentPresetData &lhs, const RecentPresetData &rhs)
{
return !(lhs == rhs);
}
inline QDebug &operator<<(QDebug &d, const RecentPresetData &preset)
{
d << "RecentPreset{category=" << preset.category << "; name=" << preset.presetName
<< "; size=" << preset.sizeName << "; isUserPreset=" << preset.isUserPreset << "}";
return d;
}
class RecentPresetsStore
{
@@ -42,14 +77,21 @@ public:
{}
void setMaximum(int n) { m_max = n; }
void add(const QString &categoryId, const QString &name, const QString &sizeName);
std::vector<RecentPreset> fetchAll() const;
void add(const QString &categoryId,
const QString &name,
const QString &sizeName,
bool isUserPreset = false);
std::vector<RecentPresetData> remove(const QString &categoryId, const QString &presetName);
std::vector<RecentPresetData> fetchAll() const;
private:
QStringList addRecentToExisting(const RecentPreset &preset, std::vector<RecentPreset> &recents);
static QStringList encodeRecentPresets(const std::vector<RecentPreset> &recents);
static std::vector<RecentPreset> decodeRecentPresets(const QVariantList &values);
static RecentPreset decodeOneRecentPreset(const QString &encoded);
std::vector<RecentPresetData> addRecentToExisting(const RecentPresetData &preset,
std::vector<RecentPresetData> &recents);
static QStringList encodeRecentPresets(const std::vector<RecentPresetData> &recents);
static std::vector<RecentPresetData> decodeRecentPresets(const QVariantList &values);
static RecentPresetData decodeOneRecentPreset(const QString &encoded);
void save(const std::vector<RecentPresetData> &recents);
private:
QSettings *m_settings = nullptr;

View File

@@ -38,7 +38,9 @@ QtcPlugin {
"wizardhandler.cpp",
"wizardhandler.h",
"recentpresets.cpp",
"recentpresets.h"
"recentpresets.h",
"userpresets.cpp",
"userpresets.h"
]
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;
}
PresetItem WizardFactories::makePresetItem(JsonWizardFactory *f, QWidget *parent,
std::shared_ptr<PresetItem> WizardFactories::makePresetItem(JsonWizardFactory *f, QWidget *parent,
const Utils::Id &platform)
{
using namespace std::placeholders;
@@ -89,16 +89,17 @@ PresetItem WizardFactories::makePresetItem(JsonWizardFactory *f, QWidget *parent
else
sizeName = screenSizes[index];
return {
/*.name =*/f->displayName(),
/*.categoryId =*/f->category(),
/*.screenSizeName=*/sizeName,
/*.description =*/f->description(),
/*.qmlPath =*/f->detailsPageQmlPath(),
/*.fontIconCode =*/m_getIconUnicode(f->fontIconName()),
/*.create =*/ std::bind(&JsonWizardFactory::runWizard, f, _1, parent, platform,
QVariantMap(), false),
};
auto result = std::make_shared<PresetItem>();
result->wizardName = f->displayName();
result->categoryId = f->category();
result->screenSizeName=sizeName;
result->description = f->description();
result->qmlPath = f->detailsPageQmlPath();
result->fontIconCode = m_getIconUnicode(f->fontIconName());
result->create
= std::bind(&JsonWizardFactory::runWizard, f, _1, parent, platform, QVariantMap(), false);
return result;
}
std::map<QString, WizardCategory> WizardFactories::makePresetItemsGroupedByCategory()

View File

@@ -64,7 +64,7 @@ private:
void sortByCategoryAndId();
void filter();
PresetItem makePresetItem(JsonWizardFactory *f, QWidget *parent, const Utils::Id &platform);
std::shared_ptr<PresetItem> makePresetItem(JsonWizardFactory *f, QWidget *parent, const Utils::Id &platform);
std::map<QString, WizardCategory> makePresetItemsGroupedByCategory();
private:

View File

@@ -38,7 +38,7 @@
using namespace StudioWelcome;
void WizardHandler::reset(const PresetItem &presetInfo, int presetSelection)
void WizardHandler::reset(const std::shared_ptr<PresetItem> &presetInfo, int presetSelection)
{
m_preset = presetInfo;
m_selectedPreset = presetSelection;
@@ -67,7 +67,7 @@ void WizardHandler::destroyWizard()
void WizardHandler::setupWizard()
{
m_wizard = m_preset.create(m_projectLocation);
m_wizard = m_preset->create(m_projectLocation);
if (!m_wizard) {
emit wizardCreationFailed();
return;
@@ -142,6 +142,11 @@ QStandardItemModel *WizardHandler::getScreenFactorModel(ProjectExplorer::JsonFie
return cbfield->model();
}
bool WizardHandler::haveStyleModel() const
{
return m_wizard->hasField("ControlsStyle");
}
QStandardItemModel *WizardHandler::getStyleModel(ProjectExplorer::JsonFieldPage *page)
{
auto *field = page->jsonField("ControlsStyle");
@@ -229,6 +234,47 @@ bool WizardHandler::haveTargetQtVersion() const
return m_wizard->hasField("TargetQtVersion");
}
QString WizardHandler::targetQtVersionName(int index) const
{
auto *field = m_detailsPage->jsonField("TargetQtVersion");
auto *cbfield = dynamic_cast<ProjectExplorer::ComboBoxField *>(field);
QTC_ASSERT(cbfield, return "");
QStandardItemModel *model = cbfield->model();
if (index < 0 || index >= model->rowCount())
return {};
QString text = model->item(index)->text();
return text;
}
int WizardHandler::targetQtVersionIndex(const QString &qtVersionName) const
{
auto *field = m_detailsPage->jsonField("TargetQtVersion");
auto *cbfield = dynamic_cast<ProjectExplorer::ComboBoxField *>(field);
QTC_ASSERT(cbfield, return -1);
const QStandardItemModel *model = cbfield->model();
for (int i = 0; i < model->rowCount(); ++i) {
const QStandardItem *item = model->item(i, 0);
const QString text = item->text();
if (text == qtVersionName)
return i;
}
return -1;
}
int WizardHandler::targetQtVersionIndex() const
{
auto *field = m_detailsPage->jsonField("TargetQtVersion");
auto *cbfield = dynamic_cast<ProjectExplorer::ComboBoxField *>(field);
QTC_ASSERT(cbfield, return -1);
return cbfield->selectedRow();
}
void WizardHandler::setStyleIndex(int index)
{
auto *field = m_detailsPage->jsonField("ControlsStyle");
@@ -247,6 +293,38 @@ int WizardHandler::styleIndex() const
return cbfield->selectedRow();
}
int WizardHandler::styleIndex(const QString &styleName) const
{
auto *field = m_detailsPage->jsonField("ControlsStyle");
auto *cbfield = dynamic_cast<ProjectExplorer::ComboBoxField *>(field);
QTC_ASSERT(cbfield, return -1);
const QStandardItemModel *model = cbfield->model();
for (int i = 0; i < model->rowCount(); ++i) {
const QStandardItem *item = model->item(i, 0);
const QString text = item->text();
if (text == styleName)
return i;
}
return -1;
}
QString WizardHandler::styleName(int index) const
{
auto *field = m_detailsPage->jsonField("ControlsStyle");
auto *cbfield = dynamic_cast<ProjectExplorer::ComboBoxField *>(field);
QTC_ASSERT(cbfield, return "");
QStandardItemModel *model = cbfield->model();
if (index < 0 || index >= model->rowCount())
return {};
QString text = model->item(index)->text();
return text;
}
void WizardHandler::setUseVirtualKeyboard(bool value)
{
auto *field = m_detailsPage->jsonField("UseVirtualKeyboard");

View File

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

View File

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

View File

@@ -33,10 +33,18 @@ using ::testing::ElementsAreArray;
using ::testing::PrintToString;
namespace StudioWelcome {
void PrintTo(const UserPresetItem &item, std::ostream *os);
void PrintTo(const PresetItem &item, std::ostream *os)
{
if (typeid(item) == typeid(UserPresetItem)) {
PrintTo((UserPresetItem &) item, os);
return;
}
*os << "{categId: " << item.categoryId << ", "
<< "name: " << item.name;
<< "name: " << item.wizardName;
if (!item.screenSizeName.isEmpty())
*os << ", size: " << item.screenSizeName;
@@ -44,6 +52,26 @@ void PrintTo(const PresetItem &item, std::ostream *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 {
@@ -51,20 +79,47 @@ std::pair<QString, WizardCategory> aCategory(const QString &categId,
const QString &categName,
const std::vector<QString> &names)
{
std::vector<PresetItem> items = Utils::transform(names, [&categId](const QString &name) {
return PresetItem{name, categId};
});
std::vector<std::shared_ptr<PresetItem>> items
= Utils::transform(names, [&categId](const QString &name) {
std::shared_ptr<PresetItem> item{new PresetItem};
item->wizardName = name;
item->categoryId = categId;
return item;
});
return std::make_pair(categId, WizardCategory{categId, categName, items});
}
UserPresetData aUserPreset(const QString &categId, const QString &wizardName, const QString &userName)
{
UserPresetData preset;
preset.categoryId = categId;
preset.wizardName = wizardName;
preset.name = userName;
return preset;
}
MATCHER_P2(PresetIs, category, name, PrintToString(PresetItem{name, category}))
{
return arg.categoryId == category && arg.name == name;
return arg->categoryId == category && arg->wizardName == name;
}
MATCHER_P3(UserPresetIs,
category,
wizardName,
userName,
PrintToString(UserPresetItem{wizardName, userName, category}))
{
auto userPreset = dynamic_cast<UserPresetItem *>(arg.get());
return userPreset->categoryId == category && userPreset->wizardName == wizardName
&& userPreset->userName == userName;
}
MATCHER_P3(PresetIs, category, name, size, PrintToString(PresetItem{name, category, size}))
{
return arg.categoryId == category && arg.name == name && size == arg.screenSizeName;
return arg->categoryId == category && arg->wizardName == name && size == arg->screenSizeName;
}
} // namespace
@@ -88,6 +143,7 @@ TEST(QdsPresetModel, haveSameArraySizeForPresetsAndCategories)
aCategory("A.categ", "A", {"item a", "item b"}),
aCategory("B.categ", "B", {"item c", "item d"}),
},
{/*user presets*/},
{/*recents*/});
ASSERT_THAT(data.presets(), SizeIs(2));
@@ -105,6 +161,7 @@ TEST(QdsPresetModel, haveWizardPresetsNoRecents)
aCategory("A.categ", "A", {"item a", "item b"}),
aCategory("B.categ", "B", {"item c", "item d"}),
},
{/*user presets*/},
{/*recents*/});
// Then
@@ -115,11 +172,30 @@ TEST(QdsPresetModel, haveWizardPresetsNoRecents)
ElementsAre(PresetIs("B.categ", "item c"), PresetIs("B.categ", "item d")));
}
TEST(QdsPresetModel, whenHaveUserPresetsButNoWizardPresetsReturnEmpty)
{
// Given
PresetData data;
// When
data.setData({/*builtin presets*/},
{
aUserPreset("A.Mobile", "Scroll", "iPhone5"),
aUserPreset("B.Desktop", "Launcher", "MacBook"),
},
{/*recents*/});
// Then
ASSERT_THAT(data.categories(), IsEmpty());
ASSERT_THAT(data.presets(), IsEmpty());
}
TEST(QdsPresetModel, haveRecentsNoWizardPresets)
{
PresetData data;
data.setData({/*wizardPresets*/},
{/*user presets*/},
{
{"A.categ", "Desktop", "640 x 480"},
{"B.categ", "Mobile", "800 x 600"},
@@ -129,7 +205,7 @@ TEST(QdsPresetModel, haveRecentsNoWizardPresets)
ASSERT_THAT(data.presets(), IsEmpty());
}
TEST(QdsPresetModel, recentsAddedBeforeWizardPresets)
TEST(QdsPresetModel, recentsAddedWithWizardPresets)
{
// Given
PresetData data;
@@ -141,6 +217,7 @@ TEST(QdsPresetModel, recentsAddedBeforeWizardPresets)
aCategory("A.categ", "A", {"Desktop", "item b"}),
aCategory("B.categ", "B", {"item c", "Mobile"}),
},
{/*user presets*/},
/*recents*/
{
{"A.categ", "Desktop", "800 x 600"},
@@ -158,7 +235,98 @@ TEST(QdsPresetModel, recentsAddedBeforeWizardPresets)
ElementsAre(PresetIs("B.categ", "item c"), PresetIs("B.categ", "Mobile"))}));
}
TEST(QdsPresetModel, recentsShouldNotSorted)
TEST(QdsPresetModel, userPresetsAddedWithWizardPresets)
{
// Given
PresetData data;
// When
data.setData(
/*wizard presets*/
{
aCategory("A.categ", "A", {"Desktop", "item b"}),
aCategory("B.categ", "B", {"Mobile"}),
},
{
aUserPreset("A.categ", "Desktop", "Windows10"),
},
{/*recents*/});
// Then
ASSERT_THAT(data.categories(), ElementsAre("A", "B", "Custom"));
ASSERT_THAT(data.presets(),
ElementsAre(ElementsAre(PresetIs("A.categ", "Desktop"),
PresetIs("A.categ", "item b")),
ElementsAre(PresetIs("B.categ", "Mobile")),
ElementsAre(UserPresetIs("A.categ", "Desktop", "Windows10"))));
}
TEST(QdsPresetModel, doesNotAddUserPresetsOfNonExistingCategory)
{
// Given
PresetData data;
// When
data.setData(
/*wizard presets*/
{
aCategory("A.categ", "A", {"Desktop"}), // Only category "A.categ" exists
},
{
aUserPreset("Bad.Categ", "Desktop", "Windows8"), // Bad.Categ does not exist
},
{/*recents*/});
// Then
ASSERT_THAT(data.categories(), ElementsAre("A"));
ASSERT_THAT(data.presets(), ElementsAre(ElementsAre(PresetIs("A.categ", "Desktop"))));
}
TEST(QdsPresetModel, doesNotAddUserPresetIfWizardPresetItRefersToDoesNotExist)
{
// Given
PresetData data;
// When
data.setData(
/*wizard presets*/
{
aCategory("A.categ", "A", {"Desktop"}),
},
{
aUserPreset("B.categ", "BadWizard", "Tablet"), // BadWizard referenced does not exist
},
{/*recents*/});
// Then
ASSERT_THAT(data.categories(), ElementsAre("A"));
ASSERT_THAT(data.presets(), ElementsAre(ElementsAre(PresetIs("A.categ", "Desktop"))));
}
TEST(QdsPresetModel, userPresetWithSameNameAsWizardPreset)
{
// Given
PresetData data;
// When
data.setData(
/*wizard presets*/
{
aCategory("A.categ", "A", {"Desktop"}),
},
{
aUserPreset("A.categ", "Desktop", "Desktop"),
},
{/*recents*/});
// Then
ASSERT_THAT(data.categories(), ElementsAre("A", "Custom"));
ASSERT_THAT(data.presets(),
ElementsAre(ElementsAre(PresetIs("A.categ", "Desktop")),
ElementsAre(UserPresetIs("A.categ", "Desktop", "Desktop"))));
}
TEST(QdsPresetModel, recentsShouldNotBeSorted)
{
// Given
PresetData data;
@@ -171,6 +339,7 @@ TEST(QdsPresetModel, recentsShouldNotSorted)
aCategory("B.categ", "B", {"item c", "Mobile"}),
aCategory("Z.categ", "Z", {"Z.desktop"}),
},
{/*user presets*/},
/*recents*/
{
{"Z.categ", "Z.desktop", "200 x 300"},
@@ -197,6 +366,7 @@ TEST(QdsPresetModel, recentsOfSameWizardProjectButDifferentSizesAreRecognizedAsD
aCategory("A.categ", "A", {"Desktop"}),
aCategory("B.categ", "B", {"Mobile"}),
},
{/*user presets*/},
/*recents*/
{
{"B.categ", "Mobile", "400 x 400"},
@@ -223,6 +393,7 @@ TEST(QdsPresetModel, outdatedRecentsAreNotShown)
aCategory("A.categ", "A", {"Desktop"}),
aCategory("B.categ", "B", {"Mobile"}),
},
{/*user presets*/},
/*recents*/
{
{"B.categ", "NoLongerExists", "400 x 400"},

View File

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

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