Merge "Merge remote-tracking branch 'origin/8.0'"

This commit is contained in:
The Qt Project
2022-09-21 06:02:18 +00:00
60 changed files with 5208 additions and 38 deletions

View File

@@ -0,0 +1,839 @@
/****************************************************************************
**
** 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 StatesEditor
import StudioControls 1.0 as StudioControls
import StudioTheme as StudioTheme
Rectangle {
id: root
signal createNewState
signal cloneState(int internalNodeId)
signal extendState(int internalNodeId)
signal deleteState(int internalNodeId)
property bool isLandscape: true
color: StudioTheme.Values.themeStatePanelBackground
onWidthChanged: root.responsiveResize(root.width, root.height)
onHeightChanged: root.responsiveResize(root.width, root.height)
Component.onCompleted: root.responsiveResize(root.width, root.height)
function numFit(overall, size, space) {
let tmpNum = Math.floor(overall / size)
let spaceLeft = overall - (tmpNum * size)
return spaceLeft - (space * (tmpNum - 1)) >= 0 ? tmpNum : tmpNum - 1
}
function responsiveResize(width, height) {
height -= toolBar.height + (2 * root.padding)
width -= (2 * root.padding)
var numStates = statesRepeater.count - 1 // Subtract base state
var numRows = 0
var numColumns = 0
// Size extension in case of extend groups are shown
var sizeExtension = root.showExtendGroups ? root.extend : 0
var doubleSizeExtension = root.showExtendGroups ? 2 * root.extend : 0
// Get view orientation (LANDSCAPE, PORTRAIT)
if (width >= height) {
root.isLandscape = true
outerGrid.columns = 3
outerGrid.rows = 1
// Three outer section height (base state, middle, plus button)
baseStateWrapper.height = height
root.scrollViewHeight = height
addWrapper.height = height
height -= doubleSizeExtension
if (height > Constants.maxThumbSize) {
// In this case we want to have a multi row grid in the center
root.thumbSize = Constants.maxThumbSize
let tmpScrollViewWidth = width - root.thumbSize * 1.5 - 2 * root.outerGridSpacing
// Inner grid calculation
numRows = root.numFit(height, Constants.maxThumbSize, root.innerGridSpacing)
numColumns = Math.min(numStates, root.numFit(tmpScrollViewWidth, root.thumbSize,
root.innerGridSpacing))
let tmpRows = Math.ceil(numStates / numColumns)
if (tmpRows <= numRows)
numRows = tmpRows
else
numColumns = Math.ceil(numStates / numRows)
} else {
// This case is for single row layout and small thumb view
root.thumbSize = Math.max(height, Constants.minThumbSize)
// Inner grid calculation
numColumns = numStates
numRows = 1
}
Constants.thumbnailSize = root.thumbSize
let tmpWidth = root.thumbSize * numColumns + root.innerGridSpacing * (numColumns - 1) + doubleSizeExtension
let remainingSpace = width - root.thumbSize - 2 * root.outerGridSpacing
let space = remainingSpace - tmpWidth
if (space >= root.thumbSize) {
root.scrollViewWidth = tmpWidth
addWrapper.width = space
} else {
addWrapper.width = Math.max(space, 0.5 * root.thumbSize)
root.scrollViewWidth = remainingSpace - addWrapper.width
}
root.topMargin = (root.scrollViewHeight - (root.thumbSize * numRows)
- root.innerGridSpacing * (numRows - 1)) * 0.5 - sizeExtension
addCanvas.width = Math.min(addWrapper.width, root.thumbSize)
addCanvas.height = root.thumbSize
baseStateWrapper.width = root.thumbSize
baseStateThumbnail.anchors.verticalCenter = baseStateWrapper.verticalCenter
baseStateThumbnail.anchors.horizontalCenter = undefined
addCanvas.anchors.verticalCenter = addWrapper.verticalCenter
addCanvas.anchors.horizontalCenter = undefined
addCanvas.anchors.top = undefined
addCanvas.anchors.left = addWrapper.left
root.leftMargin = 0 // resetting left margin in case of orientation switch
} else {
root.isLandscape = false
outerGrid.rows = 3
outerGrid.columns = 1
// Three outer section width (base state, middle, plus button)
baseStateWrapper.width = width
root.scrollViewWidth = width
addWrapper.width = width
width -= doubleSizeExtension
if (width > Constants.maxThumbSize) {
// In this case we want to have a multi column grid in the center
root.thumbSize = Constants.maxThumbSize
let tmpScrollViewHeight = height - root.thumbSize * 1.5 - 2 * root.outerGridSpacing
// Inner grid calculation
numRows = Math.min(numStates, root.numFit(tmpScrollViewHeight, root.thumbSize,
root.innerGridSpacing))
numColumns = root.numFit(width, Constants.maxThumbSize, root.innerGridSpacing)
let tmpColumns = Math.ceil(numStates / numRows)
if (tmpColumns <= numColumns)
numColumns = tmpColumns
else
numRows = Math.ceil(numStates / numColumns)
} else {
// This case is for single column layout and small thumb view
root.thumbSize = Math.max(width, Constants.minThumbSize)
// Inner grid calculation
numRows = numStates
numColumns = 1
}
Constants.thumbnailSize = root.thumbSize
let tmpHeight = root.thumbSize * numRows + root.innerGridSpacing * (numRows - 1) + doubleSizeExtension
let remainingSpace = height - root.thumbSize - 2 * root.outerGridSpacing
let space = remainingSpace - tmpHeight
if (space >= root.thumbSize) {
root.scrollViewHeight = tmpHeight
addWrapper.height = space
} else {
addWrapper.height = Math.max(space, 0.5 * root.thumbSize)
root.scrollViewHeight = remainingSpace - addWrapper.height
}
root.leftMargin = (root.scrollViewWidth - (root.thumbSize * numColumns)
- root.innerGridSpacing * (numColumns - 1)) * 0.5 - sizeExtension
addCanvas.width = root.thumbSize
addCanvas.height = Math.min(addWrapper.height, root.thumbSize)
baseStateWrapper.height = root.thumbSize
baseStateThumbnail.anchors.verticalCenter = undefined
baseStateThumbnail.anchors.horizontalCenter = baseStateWrapper.horizontalCenter
addCanvas.anchors.verticalCenter = undefined
addCanvas.anchors.horizontalCenter = addWrapper.horizontalCenter
addCanvas.anchors.top = addWrapper.top
addCanvas.anchors.left = undefined
root.topMargin = 0 // resetting top margin in case of orientation switch
}
// Always assign the bigger one first otherwise there will be console output complaining
if (numRows > innerGrid.rows) {
innerGrid.rows = numRows
innerGrid.columns = numColumns
} else {
innerGrid.columns = numColumns
innerGrid.rows = numRows
}
}
// These function assume that the order of the states is as follows:
// State A, State B (extends State A), ... so the extended state always comes first
function isInRange(i) {
return i >= 0 && i < statesEditorModel.count()
}
function nextStateHasExtend(i) {
let next = i + 1
return root.isInRange(next) ? statesEditorModel.get(next).hasExtend : false
}
function previousStateHasExtend(i) {
let prev = i - 1
return root.isInRange(prev) ? statesEditorModel.get(prev).hasExtend : false
}
property bool showExtendGroups: statesEditorModel.hasExtend
onShowExtendGroupsChanged: root.responsiveResize(root.width, root.height)
property int extend: 16
property int thumbSize: 250
property int padding: 10
property int scrollViewWidth: 640
property int scrollViewHeight: 480
property int outerGridSpacing: 10
property int innerGridSpacing: root.showExtendGroups ? 40 : root.outerGridSpacing
// These margins are used to push the inner grid down or to the left depending on the views
// orientation to align to the outer grid
property int topMargin: 0
property int leftMargin: 0
property bool tinyMode: Constants.thumbnailSize <= Constants.thumbnailBreak
property int currentStateInternalId: 0
// This timer is used to delay the current state animation as it didn't work due to the
// repeaters item not being positioned in time resulting in 0 x and y position if the grids
// row and column were not changed during the layout algorithm .
Timer {
id: layoutTimer
interval: 50
running: false
repeat: false
onTriggered: {
// Move the current state into view if outside
if (root.currentStateInternalId === 0)
// Not for base state
return
var x = 0
var y = 0
for (var i = 0; i < statesRepeater.count; ++i) {
let item = statesRepeater.itemAt(i)
if (item.internalNodeId === root.currentStateInternalId) {
x = item.x
y = item.y
break
}
}
// Check if it is in view
if (x <= frame.contentX
|| x >= (frame.contentX + root.scrollViewWidth - root.thumbSize))
frame.contentX = x - root.scrollViewWidth * 0.5 + root.thumbSize * 0.5
if (y <= frame.contentY
|| y >= (frame.contentY + root.scrollViewHeight - root.thumbSize))
frame.contentY = y - root.scrollViewHeight * 0.5 + root.thumbSize * 0.5
}
}
onCurrentStateInternalIdChanged: layoutTimer.start()
StudioControls.Dialog {
id: editDialog
title: qsTr("Rename state group")
standardButtons: Dialog.Apply | Dialog.Cancel
x: editButton.x - Math.max(0, editButton.x + editDialog.width - root.width)
y: toolBar.height
closePolicy: Popup.NoAutoClose
width: Math.min(300, root.width)
onApplied: {
let renamed = statesEditorModel.renameActiveStateGroup(editTextField.text)
if (renamed)
editDialog.close()
}
StudioControls.TextField {
id: editTextField
text: statesEditorModel.activeStateGroup
actionIndicatorVisible: false
translationIndicatorVisible: false
anchors.fill: parent
}
}
Rectangle {
id: toolBar
property bool doubleRow: root.width < 450
onDoubleRowChanged: {
if (toolBar.doubleRow) {
toolBarGrid.rows = 2
toolBarGrid.columns = 1
} else {
toolBarGrid.columns = 2
toolBarGrid.rows = 1
}
}
color: StudioTheme.Values.themeSectionHeadBackground
width: root.width
height: (toolBar.doubleRow ? 2 : 1) * StudioTheme.Values.toolbarHeight
Grid {
id: toolBarGrid
columns: 2
rows: 1
columnSpacing: StudioTheme.Values.toolbarSpacing
Row {
id: stateGroupSelectionRow
height: StudioTheme.Values.toolbarHeight
spacing: StudioTheme.Values.toolbarSpacing
leftPadding: root.padding
Text {
id: stateGroupLabel
color: StudioTheme.Values.themeTextColor
text: qsTr("State Group")
font.pixelSize: StudioTheme.Values.baseFontSize
horizontalAlignment: Text.AlignRight
verticalAlignment: Text.AlignVCenter
height: StudioTheme.Values.height
anchors.verticalCenter: parent.verticalCenter
visible: root.width > 240
}
StudioControls.ComboBox {
id: stateGroupComboBox
actionIndicatorVisible: false
model: statesEditorModel.stateGroups
currentIndex: statesEditorModel.activeStateGroupIndex
anchors.verticalCenter: parent.verticalCenter
width: stateGroupLabel.visible ? StudioTheme.Values.defaultControlWidth
: root.width - 2 * root.padding
popup.onOpened: editDialog.close()
// currentIndex needs special treatment, because if model is changed, it will be
// reset regardless of binding.
Connections {
target: statesEditorModel
function onActiveStateGroupIndexChanged() {
stateGroupComboBox.currentIndex = statesEditorModel.activeStateGroupIndex
}
}
onModelChanged: {
stateGroupComboBox.currentIndex = statesEditorModel.activeStateGroupIndex
}
onCompressedActivated: function (index, reason) {
statesEditorModel.activeStateGroupIndex = index
root.responsiveResize(root.width, root.height)
}
}
}
Row {
Row {
id: stateGroupEditRow
height: StudioTheme.Values.toolbarHeight
spacing: StudioTheme.Values.toolbarSpacing
leftPadding: toolBar.doubleRow ? root.padding : 0
StudioControls.AbstractButton {
buttonIcon: StudioTheme.Constants.plus
anchors.verticalCenter: parent.verticalCenter
onClicked: statesEditorModel.addStateGroup("stateGroup")
}
StudioControls.AbstractButton {
buttonIcon: StudioTheme.Constants.minus
anchors.verticalCenter: parent.verticalCenter
enabled: statesEditorModel.activeStateGroupIndex !== 0
onClicked: statesEditorModel.removeStateGroup()
}
StudioControls.AbstractButton {
id: editButton
buttonIcon: StudioTheme.Constants.edit
anchors.verticalCenter: parent.verticalCenter
enabled: statesEditorModel.activeStateGroupIndex !== 0
checked: editDialog.visible
onClicked: {
if (editDialog.opened)
editDialog.close()
else
editDialog.open()
}
}
}
Item {
width: Math.max(0, toolBar.width - (toolBar.doubleRow ? 0 : (stateGroupSelectionRow.width
+ toolBarGrid.columnSpacing))
- stateGroupEditRow.width - thumbnailToggleRow.width)
height: 1
}
Row {
id: thumbnailToggleRow
height: StudioTheme.Values.toolbarHeight
spacing: StudioTheme.Values.toolbarSpacing
rightPadding: root.padding
StudioControls.AbstractButton {
buttonIcon: StudioTheme.Constants.gridView
anchors.verticalCenter: parent.verticalCenter
enabled: !root.tinyMode
onClicked: {
for (var i = 0; i < statesRepeater.count; ++i)
statesRepeater.itemAt(i).setPropertyChangesVisible(false)
}
}
StudioControls.AbstractButton {
buttonIcon: StudioTheme.Constants.textFullJustification
anchors.verticalCenter: parent.verticalCenter
enabled: !root.tinyMode
onClicked: {
for (var i = 0; i < statesRepeater.count; ++i)
statesRepeater.itemAt(i).setPropertyChangesVisible(true)
}
}
}
}
}
}
Grid {
id: outerGrid
x: root.padding
y: toolBar.height + root.padding
columns: 3
rows: 1
spacing: root.outerGridSpacing
Item {
id: baseStateWrapper
StateThumbnail {
// Base State
id: baseStateThumbnail
width: Constants.thumbnailSize
height: Constants.thumbnailSize
baseState: true
defaultChecked: !statesEditorModel.baseState.modelHasDefaultState // TODO Make this one a model property
isChecked: root.currentStateInternalId === 0
thumbnailImageSource: statesEditorModel.baseState.stateImageSource // TODO Get rid of the QVariantMap
isTiny: root.tinyMode
onFocusSignal: root.currentStateInternalId = 0
onDefaultClicked: statesEditorModel.resetDefaultState()
}
}
Item {
id: scrollViewWrapper
width: root.isLandscape ? root.scrollViewWidth : root.width - (2 * root.padding)
height: root.isLandscape ? root.height - toolBar.height - (2 * root.padding) : root.scrollViewHeight
clip: true
ScrollView {
id: scrollView
anchors.fill: parent
anchors.topMargin: root.topMargin
anchors.leftMargin: root.leftMargin
ScrollBar.horizontal: StateScrollBar {
parent: scrollView
x: scrollView.leftPadding
y: scrollView.height - height
width: scrollView.availableWidth
orientation: Qt.Horizontal
}
ScrollBar.vertical: StateScrollBar {
parent: scrollView
x: scrollView.mirrored ? 0 : scrollView.width - width
y: scrollView.topPadding
height: scrollView.availableHeight
orientation: Qt.Vertical
}
Flickable {
id: frame
boundsMovement: Flickable.StopAtBounds
boundsBehavior: Flickable.StopAtBounds
interactive: true
contentWidth: {
let ext = root.showExtendGroups ? (2 * root.extend) : 0
return innerGrid.width + ext
}
contentHeight: {
let ext = root.showExtendGroups ? (2 * root.extend) : 0
return innerGrid.height + ext
}
flickableDirection: {
if (frame.contentHeight <= scrollView.height)
return Flickable.HorizontalFlick
if (frame.contentWidth <= scrollView.width)
return Flickable.VerticalFlick
return Flickable.HorizontalAndVerticalFlick
}
Behavior on contentY {
NumberAnimation {
duration: 1000
easing.type: Easing.InOutCubic
}
}
Behavior on contentX {
NumberAnimation {
duration: 1000
easing.type: Easing.InOutCubic
}
}
Grid {
id: innerGrid
x: root.showExtendGroups ? root.extend : 0
y: root.showExtendGroups ? root.extend : 0
rows: 1
spacing: root.innerGridSpacing
move: Transition {
NumberAnimation {
properties: "x,y"
easing.type: Easing.OutQuad
}
}
Repeater {
id: statesRepeater
property int grabIndex: -1
function executeDrop(from, to) {
statesEditorModel.drop(from, to)
statesRepeater.grabIndex = -1
}
model: statesEditorModel
onItemAdded: root.responsiveResize(root.width, root.height)
onItemRemoved: root.responsiveResize(root.width, root.height)
delegate: DropArea {
id: delegateRoot
required property int index
required property string stateName
required property var stateImageSource
required property int internalNodeId
required property var hasWhenCondition
required property var whenConditionString
required property bool isDefault
required property var modelHasDefaultState
required property bool hasExtend
required property var extendString
function setPropertyChangesVisible(value) {
stateThumbnail.propertyChangesVisible = value
}
width: Constants.thumbnailSize
height: Constants.thumbnailSize
visible: delegateRoot.internalNodeId // Skip base state
property int visualIndex: index
onEntered: function (drag) {
let dragSource = (drag.source as StateThumbnail)
if (dragSource === undefined)
return
if (dragSource.extendString !== stateThumbnail.extendString
|| stateThumbnail.extendedState) {
return
}
statesEditorModel.move(
(drag.source as StateThumbnail).visualIndex,
stateThumbnail.visualIndex)
}
onDropped: function (drop) {
let dragSource = (drop.source as StateThumbnail)
if (dragSource === undefined)
return
if (dragSource.extendString !== stateThumbnail.extendString
|| stateThumbnail.extendedState) {
return
}
statesRepeater.executeDrop(statesRepeater.grabIndex,
stateThumbnail.visualIndex)
}
// Extend Groups Visualization
Rectangle {
id: extendBackground
x: -root.extend
y: -root.extend
width: Constants.thumbnailSize + 2 * root.extend
height: Constants.thumbnailSize + 2 * root.extend
color: StudioTheme.Values.themeStateHighlight
radius: {
if (root.nextStateHasExtend(delegateRoot.index))
return delegateRoot.hasExtend ? 0 : root.extend
return root.extend
}
visible: (delegateRoot.hasExtend
|| stateThumbnail.extendedState)
}
// Fill the gap between extend group states and also cover up radius
// of start and end states of an extend group in case of line break
Rectangle {
id: extendGap
property bool portraitOneColumn: !root.isLandscape
&& innerGrid.columns === 1
property bool leftOrTop: {
if (delegateRoot.hasExtend)
return true
if (root.previousStateHasExtend(delegateRoot.index))
return true
return false
}
property bool rightOrBottom: {
if (stateThumbnail.extendedState)
return true
if (root.nextStateHasExtend(delegateRoot.index))
return true
return false
}
property bool firstInRow: ((delegateRoot.index - 1) % innerGrid.columns) === 0
property bool lastInRow: ((delegateRoot.index - 1) % innerGrid.columns)
=== (innerGrid.columns - 1)
x: {
if (!extendGap.portraitOneColumn) {
if (extendGap.rightOrBottom)
return extendGap.lastInRow ? Constants.thumbnailSize
- (root.innerGridSpacing
- root.extend) : Constants.thumbnailSize
if (extendGap.leftOrTop)
return extendGap.firstInRow ? -root.extend : -root.innerGridSpacing
return 0
}
return -root.extend
}
y: {
if (extendGap.portraitOneColumn) {
if (extendGap.rightOrBottom)
return Constants.thumbnailSize
if (extendGap.leftOrTop)
return -root.innerGridSpacing
return 0
}
return -root.extend
}
width: extendGap.portraitOneColumn ? Constants.thumbnailSize + 2
* root.extend : root.innerGridSpacing
height: extendGap.portraitOneColumn ? root.innerGridSpacing : Constants.thumbnailSize
+ 2 * root.extend
color: StudioTheme.Values.themeStateHighlight
visible: extendBackground.radius !== 0
&& extendBackground.visible
}
StateThumbnail {
id: stateThumbnail
width: Constants.thumbnailSize
height: Constants.thumbnailSize
visualIndex: delegateRoot.visualIndex
internalNodeId: delegateRoot.internalNodeId
isTiny: root.tinyMode
hasExtend: delegateRoot.hasExtend
extendString: delegateRoot.extendString
extendedState: statesEditorModel.extendedStates.includes(
delegateRoot.stateName)
hasWhenCondition: delegateRoot.hasWhenCondition
// Fix ScrollView taking over the dragging event
onGrabbing: {
frame.interactive = false
statesRepeater.grabIndex = stateThumbnail.visualIndex
}
onLetGo: frame.interactive = true
// Fix for ScrollView clipping while dragging of StateThumbnail
onDragActiveChanged: {
if (stateThumbnail.dragActive)
parent = scrollViewWrapper
else
parent = delegateRoot
}
stateName: delegateRoot.stateName
thumbnailImageSource: delegateRoot.stateImageSource
whenCondition: delegateRoot.whenConditionString
baseState: !delegateRoot.internalNodeId
defaultChecked: delegateRoot.isDefault
isChecked: root.currentStateInternalId === delegateRoot.internalNodeId
onFocusSignal: root.currentStateInternalId = delegateRoot.internalNodeId
onDefaultClicked: statesEditorModel.setStateAsDefault(
delegateRoot.internalNodeId)
onClone: root.cloneState(delegateRoot.internalNodeId)
onExtend: root.extendState(delegateRoot.internalNodeId)
onRemove: root.deleteState(delegateRoot.internalNodeId)
onStateNameFinished: statesEditorModel.renameState(
delegateRoot.internalNodeId,
stateThumbnail.stateName)
onWhenConditionFinished: statesEditorModel.setWhenCondition(
delegateRoot.internalNodeId,
stateThumbnail.whenCondition)
}
}
}
}
}
}
}
Item {
id: addWrapper
Canvas {
id: addCanvas
width: root.thumbWidth
height: root.thumbHeight
onPaint: {
var ctx = getContext("2d")
ctx.strokeStyle = StudioTheme.Values.themeStateHighlight
ctx.lineWidth = 6
var plusExtend = 20
var halfWidth = addCanvas.width / 2
var halfHeight = addCanvas.height / 2
ctx.beginPath()
ctx.moveTo(halfWidth, halfHeight - plusExtend)
ctx.lineTo(halfWidth, halfHeight + plusExtend)
ctx.moveTo(halfWidth - plusExtend, halfHeight)
ctx.lineTo(halfWidth + plusExtend, halfHeight)
ctx.stroke()
ctx.save()
ctx.setLineDash([2, 2])
ctx.strokeRect(0, 0, addCanvas.width, addCanvas.height)
ctx.restore()
}
MouseArea {
id: addMouseArea
anchors.fill: parent
hoverEnabled: true
onClicked: root.createNewState()
}
Rectangle {
// temporary hover indicator for add button
anchors.fill: parent
opacity: 0.1
color: addMouseArea.containsMouse ? "#ffffff" : "#000000"
}
}
}
}
}

View File

@@ -0,0 +1,134 @@
/****************************************************************************
**
** 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 1.0 as StudioTheme
import StudioControls 1.0 as StudioControls
Item {
id: root
width: 25
height: 25
property bool hovered: mouseArea.containsMouse
property bool checked: false
signal pressed()
Rectangle {
id: background
color: "transparent"
anchors.fill: parent
}
// Burger menu icon
Column {
id: menuIcon
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
spacing: 3
property color iconColor: StudioTheme.Values.themeTextColor
Rectangle {
id: rectangle
width: 19
height: 3
color: menuIcon.iconColor
}
Rectangle {
id: rectangle1
width: 19
height: 3
color: menuIcon.iconColor
}
Rectangle {
id: rectangle2
width: 19
height: 3
color: menuIcon.iconColor
}
}
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
onPressed: root.pressed()
}
states: [
State {
name: "default"
when: !root.hovered && !root.checked
PropertyChanges {
target: background
color: "transparent"
}
PropertyChanges {
target: menuIcon
iconColor: StudioTheme.Values.themeTextColor
}
},
State {
name: "hover"
when: root.hovered && !root.checked
PropertyChanges {
target: background
color: StudioTheme.Values.themeControlBackgroundHover
}
PropertyChanges {
target: menuIcon
iconColor: StudioTheme.Values.themeTextColor
}
},
State {
name: "checked"
when: !root.hovered && root.checked
PropertyChanges {
target: menuIcon
iconColor: StudioTheme.Values.themeInteraction
}
},
State {
name: "hoverChecked"
when: root.hovered && root.checked
PropertyChanges {
target: background
color: StudioTheme.Values.themeControlBackgroundHover
}
PropertyChanges {
target: menuIcon
iconColor: StudioTheme.Values.themeInteraction
}
}
]
}

View File

@@ -0,0 +1,108 @@
/****************************************************************************
**
** 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
import StudioControls as StudioControls
import QtQuick.Layouts
StudioControls.Menu {
id: root
property bool isBaseState: false
property bool isTiny: false
property bool hasExtend: false
property bool propertyChangesVisible: false
property bool hasAnnotation: false
property bool hasWhenCondition: false
signal clone()
signal extend()
signal remove()
signal toggle()
signal resetWhenCondition()
signal editAnnotation()
signal removeAnnotation()
closePolicy: Popup.CloseOnReleaseOutside | Popup.CloseOnEscape
StudioControls.MenuItem {
id: clone
visible: !root.isBaseState
text: qsTr("Clone")
height: clone.visible ? clone.implicitHeight : 0
onTriggered: root.clone()
}
StudioControls.MenuItem {
id: deleteState
visible: !root.isBaseState
text: qsTr("Delete")
height: deleteState.visible ? deleteState.implicitHeight : 0
onTriggered: root.remove()
}
StudioControls.MenuItem {
id: showChanges
visible: !root.isBaseState
enabled: !root.isTiny
text: root.propertyChangesVisible ? qsTr("Show Thumbnail") : qsTr("Show Changes")
height: showChanges.visible ? showChanges.implicitHeight : 0
onTriggered: root.toggle()
}
StudioControls.MenuItem {
id: extend
visible: !root.isBaseState && !root.hasExtend
text: qsTr("Extend")
height: extend.visible ? extend.implicitHeight : 0
onTriggered: root.extend()
}
StudioControls.MenuSeparator {}
StudioControls.MenuItem {
enabled: !root.isBaseState && root.hasWhenCondition
text: qsTr("Reset when Condition")
onTriggered: {
statesEditorModel.resetWhenCondition(internalNodeId)
}
}
StudioControls.MenuSeparator {}
StudioControls.MenuItem {
enabled: !root.isBaseState
text: root.hasAnnotation ? qsTr("Edit Annotation") : qsTr("Add Annotation")
onTriggered: root.editAnnotation()
}
StudioControls.MenuItem {
enabled: !isBaseState && hasAnnotation
text: qsTr("Remove Annotation")
onTriggered: root.removeAnnotation()
}
}

View File

@@ -0,0 +1,70 @@
/****************************************************************************
**
** 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.Templates as T
import StudioTheme 1.0 as StudioTheme
T.ScrollBar {
id: scrollBar
implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
implicitContentWidth + leftPadding + rightPadding)
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
implicitContentHeight + topPadding + bottomPadding)
contentItem: Rectangle {
implicitWidth: scrollBar.interactive ? 6 : 2
implicitHeight: scrollBar.interactive ? 6 : 2
radius: width / 2
opacity: 0.0
color: scrollBar.pressed ? StudioTheme.Values.themeSliderActiveTrackHover
: StudioTheme.Values.themeSliderHandle
states: State {
name: "active"
when: scrollBar.active && scrollBar.size < 1.0
PropertyChanges {
target: scrollBar.contentItem
opacity: 0.75
}
}
transitions: Transition {
from: "active"
SequentialAnimation {
PauseAnimation {
duration: 450
}
NumberAnimation {
target: scrollBar.contentItem
duration: 200
property: "opacity"
to: 0.0
}
}
}
}
}

View File

@@ -0,0 +1,756 @@
/****************************************************************************
**
** 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 1.0 as StudioTheme
import StudioControls 1.0 as StudioControls
import QtQuick.Layouts 6.0
Item {
id: root
clip: true
anchors {
horizontalCenter: parent.horizontalCenter
verticalCenter: parent.verticalCenter
}
property alias thumbnailImageSource: thumbnailImage.source
property alias stateName: stateNameField.text
property alias whenCondition: whenCondition.text
property alias defaultChecked: defaultButton.checked
property alias menuChecked: menuButton.checked
property bool baseState: false
property bool isTiny: false
property bool propertyChangesVisible: false
property bool isChecked: false
property bool hasExtend: false
property string extendString: ""
property bool extendedState: false
property bool hasWhenCondition: false
property int visualIndex: 0
property int internalNodeId
signal focusSignal
signal defaultClicked
signal clone
signal extend
signal remove
signal stateNameFinished
signal whenConditionFinished
signal grabbing
signal letGo
property alias dragActive: dragHandler.active
function checkAnnotation() {
return statesEditorModel.hasAnnotation(root.internalNodeId)
}
onIsTinyChanged: {
if (root.isTiny) {
buttonGrid.rows = 2
buttonGrid.columns = 1
} else {
buttonGrid.columns = 2
buttonGrid.rows = 1
}
}
DragHandler {
id: dragHandler
enabled: !root.baseState && !root.extendedState
onGrabChanged: function (transition, point) {
if (transition === PointerDevice.GrabPassive
|| transition === PointerDevice.GrabExclusive)
root.grabbing()
if (transition === PointerDevice.UngrabPassive
|| transition === PointerDevice.CancelGrabPassive
|| transition === PointerDevice.UngrabExclusive
|| transition === PointerDevice.CancelGrabExclusive)
root.letGo()
}
}
onDragActiveChanged: {
if (root.dragActive)
Drag.start()
else
Drag.drop()
}
Drag.active: dragHandler.active
Drag.source: root
Drag.hotSpot.x: root.width * 0.5
Drag.hotSpot.y: root.height * 0.5
Rectangle {
id: stateBackground
color: StudioTheme.Values.themeControlBackground
border.color: StudioTheme.Values.themeInteraction
border.width: root.isChecked ? 4 : 0
anchors.fill: parent
readonly property int controlHeight: 25
readonly property int thumbPadding: 10
readonly property int thumbSpacing: 7
property int innerWidth: root.width - 2 * stateBackground.thumbPadding
property int innerHeight: root.height - 2 * stateBackground.thumbPadding - 2
* stateBackground.thumbSpacing - 2 * stateBackground.controlHeight
MouseArea {
id: mouseArea
anchors.fill: parent
enabled: true
hoverEnabled: true
propagateComposedEvents: true
onClicked: root.focusSignal()
}
Column {
padding: stateBackground.thumbPadding
spacing: stateBackground.thumbSpacing
Grid {
id: buttonGrid
columns: 2
rows: 1
spacing: stateBackground.thumbSpacing
StudioControls.AbstractButton {
id: defaultButton
width: 50
height: stateBackground.controlHeight
checkedInverted: true
buttonIcon: qsTr("Default")
iconFont: StudioTheme.Constants.font
onClicked: {
root.defaultClicked()
root.focusSignal()
}
}
StudioControls.TextField {
id: stateNameField
property string previousText
// This is the width for the "big" state
property int bigWidth: stateBackground.innerWidth - 2 * stateBackground.thumbSpacing
- defaultButton.width - menuButton.width
width: root.isTiny ? stateBackground.innerWidth : stateNameField.bigWidth
height: stateBackground.controlHeight
actionIndicatorVisible: false
translationIndicatorVisible: false
placeholderText: qsTr("State Name")
visible: !root.baseState
onActiveFocusChanged: {
if (stateNameField.activeFocus)
root.focusSignal()
}
onEditingFinished: {
if (stateNameField.previousText === stateNameField.text)
return
stateNameField.previousText = stateNameField.text
root.stateNameFinished()
}
Component.onCompleted: stateNameField.previousText = stateNameField.text
}
Text {
id: baseStateLabel
width: root.isTiny ? stateBackground.innerWidth : stateNameField.bigWidth
height: stateBackground.controlHeight
visible: root.baseState
color: StudioTheme.Values.themeTextColor
text: qsTr("Base State")
font.pixelSize: StudioTheme.Values.baseFontSize
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
leftPadding: 5
elide: Text.ElideRight
}
}
Item {
visible: !root.isTiny && !root.propertyChangesVisible
width: stateBackground.innerWidth
height: stateBackground.innerHeight
Image {
anchors.fill: stateImageBackground
source: "images/checkers.png"
fillMode: Image.Tile
}
Rectangle {
id: stateImageBackground
x: Math.floor(
(stateBackground.innerWidth - thumbnailImage.paintedWidth) / 2) - StudioTheme.Values.border
y: Math.floor(
(stateBackground.innerHeight - thumbnailImage.paintedHeight) / 2) - StudioTheme.Values.border
width: Math.round(thumbnailImage.paintedWidth) + 2 * StudioTheme.Values.border
height: Math.round(thumbnailImage.paintedHeight) + 2 * StudioTheme.Values.border
color: "transparent"
border.width: StudioTheme.Values.border
border.color: StudioTheme.Values.themeStatePreviewOutline
}
Image {
id: thumbnailImage
anchors.centerIn: parent
anchors.fill: parent
fillMode: Image.PreserveAspectFit
mipmap: true
}
}
PropertyChangesModel {
id: propertyChangesModel
modelNodeBackendProperty: statesEditorModel.stateModelNode(root.internalNodeId)
}
Text {
visible: !root.isTiny && root.propertyChangesVisible && !propertyChangesModel.count
width: stateBackground.innerWidth
height: stateBackground.innerHeight
text: qsTr("No Property Changes Available")
color: StudioTheme.Values.themeTextColor
font.pixelSize: StudioTheme.Values.baseFontSize
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
}
ScrollView {
id: scrollView
visible: !root.isTiny && root.propertyChangesVisible && propertyChangesModel.count
width: stateBackground.innerWidth
height: stateBackground.innerHeight
clip: true
ScrollBar.horizontal: StateScrollBar {
parent: scrollView
x: scrollView.leftPadding
y: scrollView.height - height
width: scrollView.availableWidth
active: scrollView.ScrollBar.vertical.active
orientation: Qt.Horizontal
onPressedChanged: root.focusSignal()
}
ScrollBar.vertical: StateScrollBar {
parent: scrollView
x: scrollView.mirrored ? 0 : scrollView.width - width
y: scrollView.topPadding
height: scrollView.availableHeight
active: scrollView.ScrollBar.horizontal.active
orientation: Qt.Vertical
onPressedChanged: root.focusSignal()
}
Flickable {
id: frame
boundsMovement: Flickable.StopAtBounds
boundsBehavior: Flickable.StopAtBounds
interactive: true
contentWidth: column.width
contentHeight: column.height
flickableDirection: {
if (frame.contentHeight <= scrollView.height)
return Flickable.HorizontalFlick
if (frame.contentWidth <= scrollView.width)
return Flickable.VerticalFlick
return Flickable.HorizontalAndVerticalFlick
}
// ScrollView needs an extra TapHandler on top in order to receive click
// events. MouseAreas below ScrollView do not let clicks through.
TapHandler {
id: tapHandler
onTapped: root.focusSignal()
}
Column {
id: column
// Grid sizes
property int gridSpacing: 20
property int gridRowSpacing: 5
property int gridPadding: 5
property int col1Width: 100 // labels
property int col2Width: stateBackground.innerWidth - column.gridSpacing - 2
* column.gridPadding - column.col1Width // controls
width: stateBackground.innerWidth
spacing: stateBackground.thumbSpacing
Repeater {
model: propertyChangesModel
delegate: Rectangle {
id: propertyChanges
required property int index
required property string target
required property bool explicit
required property bool restoreEntryValues
required property var propertyModelNode
width: column.width
height: propertyChangesColumn.height
color: StudioTheme.Values.themeBackgroundColorAlternate
PropertyModel {
id: propertyModel
modelNodeBackendProperty: propertyChanges.propertyModelNode
}
Column {
id: propertyChangesColumn
Item {
id: section
property int animationDuration: 120
property bool expanded: false
clip: true
width: stateBackground.innerWidth
height: Math.round(sectionColumn.height + header.height)
Rectangle {
id: header
anchors.left: parent.left
anchors.right: parent.right
width: stateBackground.innerWidth
height: StudioTheme.Values.sectionHeadHeight
color: StudioTheme.Values.themeSectionHeadBackground
Row {
x: column.gridPadding
anchors.verticalCenter: parent.verticalCenter
spacing: column.gridSpacing
Text {
color: StudioTheme.Values.themeTextColor
text: qsTr("Target")
font.pixelSize: StudioTheme.Values.baseFontSize
horizontalAlignment: Text.AlignRight
width: column.col1Width
}
Text {
color: StudioTheme.Values.themeTextColor
text: propertyChanges.target
font.pixelSize: StudioTheme.Values.baseFontSize
horizontalAlignment: Text.AlignLeft
elide: Text.ElideRight
width: column.col2Width
}
}
Text {
id: arrow
width: StudioTheme.Values.spinControlIconSizeMulti
height: StudioTheme.Values.spinControlIconSizeMulti
text: StudioTheme.Constants.sectionToggle
color: StudioTheme.Values.themeTextColor
renderType: Text.NativeRendering
anchors.left: parent.left
anchors.leftMargin: 4
anchors.verticalCenter: parent.verticalCenter
font.pixelSize: StudioTheme.Values.spinControlIconSizeMulti
font.family: StudioTheme.Constants.iconFont.family
Behavior on rotation {
NumberAnimation {
easing.type: Easing.OutCubic
duration: section.animationDuration
}
}
}
MouseArea {
anchors.fill: parent
onClicked: {
section.expanded = !section.expanded
if (!section.expanded)
section.forceActiveFocus()
root.focusSignal()
}
}
}
Column {
id: sectionColumn
y: header.height
padding: column.gridPadding
spacing: column.gridRowSpacing
Row {
spacing: column.gridSpacing
Text {
color: StudioTheme.Values.themeTextColor
text: qsTr("Explicit")
font.pixelSize: StudioTheme.Values.baseFontSize
horizontalAlignment: Text.AlignRight
verticalAlignment: Text.AlignVCenter
width: column.col1Width
height: explicitSwitch.height
}
StudioControls.Switch {
id: explicitSwitch
actionIndicatorVisible: false
checked: propertyChanges.explicit
onToggled: {
root.focusSignal()
propertyModel.setExplicit(
explicitSwitch.checked)
}
}
}
Row {
spacing: column.gridSpacing
Text {
color: StudioTheme.Values.themeTextColor
text: qsTr("Restore values")
font.pixelSize: StudioTheme.Values.baseFontSize
horizontalAlignment: Text.AlignRight
verticalAlignment: Text.AlignVCenter
width: column.col1Width
height: restoreSwitch.height
}
StudioControls.Switch {
id: restoreSwitch
actionIndicatorVisible: false
checked: propertyChanges.restoreEntryValues
onToggled: {
root.focusSignal()
propertyModel.setRestoreEntryValues(
restoreSwitch.checked)
}
}
}
}
Behavior on height {
NumberAnimation {
easing.type: Easing.OutCubic
duration: section.animationDuration
}
}
states: [
State {
name: "Expanded"
when: section.expanded
PropertyChanges {
target: arrow
rotation: 0
}
},
State {
name: "Collapsed"
when: !section.expanded
PropertyChanges {
target: section
height: header.height
}
PropertyChanges {
target: arrow
rotation: -90
}
}
]
}
Column {
id: propertyColumn
padding: column.gridPadding
spacing: column.gridRowSpacing
Repeater {
model: propertyModel
delegate: ItemDelegate {
id: propertyDelegate
required property string name
required property var value
required property string type
width: stateBackground.innerWidth - 2 * column.gridPadding
height: 26
hoverEnabled: true
onClicked: root.focusSignal()
background: Rectangle {
color: "transparent"
border.color: StudioTheme.Values.themeInteraction
border.width: propertyDelegate.hovered ? StudioTheme.Values.border : 0
}
Item {
id: removeItem
visible: propertyDelegate.hovered
x: propertyDelegate.width - removeItem.width
z: 10
width: removeItem.visible ? propertyDelegate.height : 0
height: removeItem.visible ? propertyDelegate.height : 0
Label {
id: removeIcon
anchors.fill: parent
text: StudioTheme.Constants.closeCross
color: StudioTheme.Values.themeTextColor
font.family: StudioTheme.Constants.iconFont.family
font.pixelSize: StudioTheme.Values.myIconFontSize
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
scale: propertyDelegateMouseArea.containsMouse ? 1.2 : 1.0
}
MouseArea {
id: propertyDelegateMouseArea
anchors.fill: parent
hoverEnabled: true
onClicked: {
root.focusSignal()
propertyModel.removeProperty(
propertyDelegate.name)
}
}
}
Row {
anchors.verticalCenter: parent.verticalCenter
spacing: column.gridSpacing
Text {
color: StudioTheme.Values.themeTextColor
text: propertyDelegate.name
font.pixelSize: StudioTheme.Values.baseFontSize
horizontalAlignment: Text.AlignRight
elide: Text.ElideRight
width: column.col1Width
}
Text {
color: StudioTheme.Values.themeTextColor
text: propertyDelegate.value
font.pixelSize: StudioTheme.Values.baseFontSize
horizontalAlignment: Text.AlignLeft
elide: Text.ElideRight
width: column.col2Width - removeItem.width
}
}
}
}
}
}
}
}
}
}
}
BindingEditor {
id: bindingEditor
property string newWhenCondition
property Timer timer: Timer {
id: timer
running: false
interval: 50
repeat: false
onTriggered: statesEditorModel.setWhenCondition(root.internalNodeId,
bindingEditor.newWhenCondition)
}
stateModelNodeProperty: statesEditorModel.stateModelNode(root.internalNodeId)
stateNameProperty: root.stateName
onRejected: bindingEditor.hideWidget()
onAccepted: {
bindingEditor.newWhenCondition = bindingEditor.text.trim()
timer.start()
bindingEditor.hideWidget()
}
}
StudioControls.TextField {
id: whenCondition
property string previousCondition
width: stateBackground.innerWidth
height: stateBackground.controlHeight
visible: !root.baseState
indicatorVisible: true
indicator.icon.text: StudioTheme.Constants.edit
indicator.onClicked: {
bindingEditor.showWidget()
bindingEditor.text = whenCondition.text
bindingEditor.prepareBindings()
bindingEditor.updateWindowName()
}
actionIndicatorVisible: false
translationIndicatorVisible: false
placeholderText: qsTr("When Condition")
onActiveFocusChanged: {
if (whenCondition.activeFocus)
root.focusSignal()
}
onEditingFinished: {
if (whenCondition.previousCondition === whenCondition.text)
return
whenCondition.previousCondition = whenCondition.text
root.whenConditionFinished()
}
Component.onCompleted: whenCondition.previousCondition = whenCondition.text
}
}
MenuButton {
id: menuButton
anchors.top: parent.top
anchors.topMargin: 10
anchors.right: parent.right
anchors.rightMargin: 10
visible: !root.baseState
checked: stateMenu.opened
onPressed: {
if (!stateMenu.opened)
stateMenu.popup()
root.focusSignal()
}
}
}
StateMenu {
id: stateMenu
x: 56
isBaseState: root.baseState
isTiny: root.isTiny
hasExtend: root.hasExtend
propertyChangesVisible: root.propertyChangesVisible
hasAnnotation: root.checkAnnotation()
hasWhenCondition: root.hasWhenCondition
onClone: root.clone()
onExtend: root.extend()
onRemove: root.remove()
onToggle: root.propertyChangesVisible = !root.propertyChangesVisible
onEditAnnotation: {
statesEditorModel.setAnnotation(root.internalNodeId)
stateMenu.hasAnnotation = root.checkAnnotation()
}
onRemoveAnnotation: {
statesEditorModel.removeAnnotation(root.internalNodeId)
stateMenu.hasAnnotation = root.checkAnnotation()
}
onOpened: stateMenu.hasAnnotation = root.checkAnnotation()
}
property bool anyControlHovered: defaultButton.hovered || menuButton.hovered
|| scrollView.hovered || stateNameField.hover
|| whenCondition.hover
states: [
State {
name: "default"
when: !mouseArea.containsMouse && !root.anyControlHovered && !dragHandler.active
&& !root.baseState
PropertyChanges {
target: stateBackground
color: StudioTheme.Values.themeControlBackground
}
},
State {
name: "hover"
when: (mouseArea.containsMouse || root.anyControlHovered) && !dragHandler.active
PropertyChanges {
target: stateBackground
color: StudioTheme.Values.themeControlBackgroundHover
}
},
State {
name: "baseState"
when: root.baseState && !mouseArea.containsMouse && !root.anyControlHovered
&& !dragHandler.active
PropertyChanges {
target: stateBackground
color: StudioTheme.Values.themeStateHighlight
}
},
State {
name: "drag"
when: dragHandler.active
AnchorChanges {
target: root
anchors.horizontalCenter: undefined
anchors.verticalCenter: undefined
}
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 B

View File

@@ -0,0 +1,39 @@
/****************************************************************************
**
** 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 Singleton
import QtQuick
QtObject {
readonly property int width: 1920
readonly property int height: 1080
property int thumbnailSize: 250
// Breakpoint to control when the state thumbnail view toggles from normal to tiny
readonly property int thumbnailBreak: 150
readonly property int minThumbSize: 100
readonly property int maxThumbSize: 350
}

View File

@@ -0,0 +1 @@
singleton Constants 1.0 Constants.qml

View File

@@ -17,6 +17,7 @@ PropertyEditorPane {
DynamicPropertiesSection {
propertiesModel: SelectionDynamicPropertiesModel {}
visible: !hasMultiSelection
}
GeometrySection {}

View File

@@ -20,6 +20,7 @@ PropertyEditorPane {
DynamicPropertiesSection {
propertiesModel: SelectionDynamicPropertiesModel {}
visible: !hasMultiSelection
}
Loader {

View File

@@ -677,6 +677,7 @@ Section {
translationIndicatorVisible: false
width: cePopup.itemWidth
rightPadding: 8
validator: RegExpValidator { regExp: /[a-z]+[0-9A-Za-z]*/ }
}
}
RowLayout {

View File

@@ -21,6 +21,9 @@ T.AbstractButton {
property alias backgroundVisible: buttonBackground.visible
property alias backgroundRadius: buttonBackground.radius
// Inverts the checked style
property bool checkedInverted: false
implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
implicitContentWidth + leftPadding + rightPadding)
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
@@ -76,11 +79,12 @@ T.AbstractButton {
}
},
State {
name: "select"
name: "check"
when: myButton.enabled && !myButton.pressed && myButton.checked
PropertyChanges {
target: buttonIcon
color: StudioTheme.Values.themeIconColorSelected
color: myButton.checkedInverted ? StudioTheme.Values.themeTextSelectedTextColor
: StudioTheme.Values.themeIconColorSelected
}
},
State {
@@ -103,6 +107,7 @@ T.AbstractButton {
PropertyChanges {
target: buttonBackground
color: StudioTheme.Values.themeControlBackground
border.color: StudioTheme.Values.themeControlOutline
}
PropertyChanges {
target: myButton
@@ -115,19 +120,32 @@ T.AbstractButton {
PropertyChanges {
target: buttonBackground
color: StudioTheme.Values.themeControlBackground
border.color: StudioTheme.Values.themeControlOutline
}
},
State {
name: "hover"
when: myButton.hover && !myButton.pressed && myButton.enabled
when: !myButton.checked && myButton.hover && !myButton.pressed && myButton.enabled
PropertyChanges {
target: buttonBackground
color: StudioTheme.Values.themeControlBackgroundHover
border.color: StudioTheme.Values.themeControlOutline
}
},
State {
name: "hoverCheck"
when: myButton.checked && myButton.hover && !myButton.pressed && myButton.enabled
PropertyChanges {
target: buttonBackground
color: myButton.checkedInverted ? StudioTheme.Values.themeInteractionHover
: StudioTheme.Values.themeControlBackgroundHover
border.color: myButton.checkedInverted ? StudioTheme.Values.themeInteractionHover
: StudioTheme.Values.themeControlOutline
}
},
State {
name: "press"
when: myButton.hover && myButton.pressed
when: myButton.hover && myButton.pressed && myButton.enabled
PropertyChanges {
target: buttonBackground
color: StudioTheme.Values.themeInteraction
@@ -138,6 +156,17 @@ T.AbstractButton {
z: 100
}
},
State {
name: "check"
when: myButton.enabled && !myButton.pressed && myButton.checked
PropertyChanges {
target: buttonBackground
color: myButton.checkedInverted ? StudioTheme.Values.themeInteraction
: StudioTheme.Values.themeControlBackground
border.color: myButton.checkedInverted ? StudioTheme.Values.themeInteraction
: StudioTheme.Values.themeControlOutline
}
},
State {
name: "disable"
when: !myButton.enabled

View File

@@ -0,0 +1,74 @@
/****************************************************************************
**
** 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.Templates as T
import StudioTheme 1.0 as StudioTheme
T.Dialog {
id: root
implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
contentWidth + leftPadding + rightPadding,
implicitHeaderWidth,
implicitFooterWidth)
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
contentHeight + topPadding + bottomPadding
+ (implicitHeaderHeight > 0 ? implicitHeaderHeight + spacing : 0)
+ (implicitFooterHeight > 0 ? implicitFooterHeight + spacing : 0))
padding: StudioTheme.Values.dialogPadding
background: Rectangle {
color: StudioTheme.Values.themeDialogBackground
border.color: StudioTheme.Values.themeDialogOutline
border.width: StudioTheme.Values.border
}
header: T.Label {
text: root.title
visible: root.title
elide: T.Label.ElideRight
font.bold: true
padding: StudioTheme.Values.dialogPadding
color: StudioTheme.Values.themeTextColor
background: Rectangle {
x: StudioTheme.Values.border
y: StudioTheme.Values.border
width: parent.width - (2 * StudioTheme.Values.border)
height: parent.height - (2 * StudioTheme.Values.border)
color: StudioTheme.Values.themeDialogBackground
}
}
footer: DialogButtonBox {
visible: count > 0
}
T.Overlay.modal: Rectangle {
color: Qt.alpha(StudioTheme.Values.themeDialogBackground, 0.5)
}
}

View File

@@ -0,0 +1,105 @@
/****************************************************************************
**
** 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.Templates as T
import StudioTheme 1.0 as StudioTheme
T.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: StudioTheme.Values.dialogButtonPadding
rightPadding: StudioTheme.Values.dialogButtonPadding
background: Rectangle {
id: background
implicitWidth: 70
implicitHeight: 20
color: StudioTheme.Values.themeControlBackground
border.color: StudioTheme.Values.themeControlOutline
anchors.fill: parent
}
contentItem: Text {
id: textItem
text: root.text
font.pixelSize: StudioTheme.Values.baseFontSize
color: StudioTheme.Values.themeTextColor
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
states: [
State {
name: "default"
when: !root.down && !root.hovered && !root.checked
PropertyChanges {
target: background
color: root.highlighted ? StudioTheme.Values.themeInteraction
: StudioTheme.Values.themeControlBackground
border.color: StudioTheme.Values.themeControlOutline
}
PropertyChanges {
target: textItem
color: StudioTheme.Values.themeTextColor
}
},
State {
name: "hover"
when: root.hovered && !root.checked && !root.down
PropertyChanges {
target: background
color: StudioTheme.Values.themeControlBackgroundHover
border.color: StudioTheme.Values.themeControlOutline
}
PropertyChanges {
target: textItem
color: StudioTheme.Values.themeTextColor
}
},
State {
name: "pressed"
when: root.checked || root.down
PropertyChanges {
target: background
color: StudioTheme.Values.themeControlBackgroundInteraction
border.color: StudioTheme.Values.themeControlOutlineInteraction
}
PropertyChanges {
target: textItem
color: StudioTheme.Values.themeTextColor
}
}
]
}

View File

@@ -0,0 +1,68 @@
/****************************************************************************
**
** 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.Templates as T
import StudioTheme 1.0 as StudioTheme
T.DialogButtonBox {
id: control
implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
(control.count === 1 ? implicitContentWidth * 2 : implicitContentWidth) + leftPadding + rightPadding)
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
implicitContentHeight + topPadding + bottomPadding)
contentWidth: contentItem.contentWidth
spacing: StudioTheme.Values.dialogButtonSpacing
padding: StudioTheme.Values.dialogPadding
alignment: Qt.AlignRight | Qt.AlignBottom
delegate: DialogButton {
width: control.count === 1 ? control.availableWidth / 2 : undefined
implicitHeight: StudioTheme.Values.height
highlighted: DialogButtonBox.buttonRole === DialogButtonBox.AcceptRole
|| DialogButtonBox.buttonRole === DialogButtonBox.ApplyRole
}
contentItem: ListView {
implicitWidth: contentWidth
model: control.contentModel
spacing: control.spacing
orientation: ListView.Horizontal
boundsBehavior: Flickable.StopAtBounds
snapMode: ListView.SnapToItem
}
background: Rectangle {
implicitHeight: 30
x: StudioTheme.Values.border
y: StudioTheme.Values.border
width: parent.width - (2 * StudioTheme.Values.border)
height: parent.height - (2 * StudioTheme.Values.border)
color: StudioTheme.Values.themeDialogBackground
}
}

View File

@@ -0,0 +1,80 @@
/****************************************************************************
**
** 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 2.15
import QtQuick.Templates 2.15 as T
import StudioTheme 1.0 as StudioTheme
Item {
id: root
property alias icon: icon
property bool hover: mouseArea.containsMouse
property bool pressed: mouseArea.pressed
implicitWidth: StudioTheme.Values.height
implicitHeight: StudioTheme.Values.height
signal clicked
z: 10
T.Label {
id: icon
anchors.fill: parent
text: StudioTheme.Constants.actionIcon
color: StudioTheme.Values.themeTextColor
font.family: StudioTheme.Constants.iconFont.family
font.pixelSize: StudioTheme.Values.myIconFontSize
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
states: [
State {
name: "hover"
when: root.hover && !root.pressed && root.enabled
PropertyChanges {
target: icon
scale: 1.2
visible: true
}
},
State {
name: "disable"
when: !root.enabled
PropertyChanges {
target: icon
color: StudioTheme.Values.themeTextColorDisabled
}
}
]
}
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
onClicked: root.clicked()
}
}

View File

@@ -0,0 +1,54 @@
/****************************************************************************
**
** 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.Templates as T
import StudioTheme 1.0 as StudioTheme
T.ProgressBar {
id: control
implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
implicitContentWidth + leftPadding + rightPadding)
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
implicitContentHeight + topPadding + bottomPadding)
contentItem: Item {
implicitWidth: 200
implicitHeight: 6
Rectangle {
width: control.visualPosition * parent.width
height: parent.height
color: StudioTheme.Values.themeInteraction
}
}
background: Rectangle {
implicitWidth: 200
implicitHeight: 6
color: StudioTheme.Values.themeThumbnailLabelBackground
}
}

View File

@@ -8,22 +8,24 @@ import StudioTheme 1.0 as StudioTheme
T.TextField {
id: root
property alias actionIndicator: actionIndicator
property alias translationIndicator: translationIndicator
// This property is used to indicate the global hover state
property bool hover: (actionIndicator.hover || mouseArea.containsMouse
property bool hover: (actionIndicator.hover || mouseArea.containsMouse || indicator.hover
|| translationIndicator.hover) && root.enabled
property bool edit: root.activeFocus
property alias actionIndicator: actionIndicator
property alias actionIndicatorVisible: actionIndicator.visible
property real __actionIndicatorWidth: StudioTheme.Values.actionIndicatorWidth
property real __actionIndicatorHeight: StudioTheme.Values.actionIndicatorHeight
property alias translationIndicator: translationIndicator
property alias translationIndicatorVisible: translationIndicator.visible
property real __translationIndicatorWidth: StudioTheme.Values.translationIndicatorWidth
property real __translationIndicatorHeight: StudioTheme.Values.translationIndicatorHeight
property alias indicator: indicator
property alias indicatorVisible: indicator.visible
property string preFocusText: ""
horizontalAlignment: Qt.AlignLeft
@@ -46,7 +48,7 @@ T.TextField {
implicitHeight: StudioTheme.Values.defaultControlHeight
leftPadding: StudioTheme.Values.inputHorizontalPadding + actionIndicator.width
rightPadding: StudioTheme.Values.inputHorizontalPadding + translationIndicator.width
rightPadding: StudioTheme.Values.inputHorizontalPadding + translationIndicator.width + indicator.width
MouseArea {
id: mouseArea
@@ -122,6 +124,14 @@ T.TextField {
height: root.height
}
Indicator {
id: indicator
visible: false
x: root.width - translationIndicator.width - indicator.width
width: indicator.visible ? root.height : 0
height: indicator.visible ? root.height : 0
}
TranslationIndicator {
id: translationIndicator
myControl: root
@@ -151,8 +161,8 @@ T.TextField {
},
State {
name: "globalHover"
when: (actionIndicator.hover || translationIndicator.hover) && !root.edit
&& root.enabled
when: (actionIndicator.hover || translationIndicator.hover || indicator.hover)
&& !root.edit && root.enabled
PropertyChanges {
target: textFieldBackground
color: StudioTheme.Values.themeControlBackgroundGlobalHover
@@ -167,7 +177,7 @@ T.TextField {
State {
name: "hover"
when: mouseArea.containsMouse && !actionIndicator.hover && !translationIndicator.hover
&& !root.edit && root.enabled
&& !indicator.hover && !root.edit && root.enabled
PropertyChanges {
target: textFieldBackground
color: StudioTheme.Values.themeControlBackgroundHover

View File

@@ -8,7 +8,11 @@ CheckIndicator 1.0 CheckIndicator.qml
ComboBox 1.0 ComboBox.qml
ComboBoxInput 1.0 ComboBoxInput.qml
ContextMenu 1.0 ContextMenu.qml
Dialog 1.0 Dialog.qml
DialogButton 1.0 DialogButton.qml
DialogButtonBox 1.0 DialogButtonBox.qml
FilterComboBox 1.0 FilterComboBox.qml
Indicator 1.0 Indicator.qml
InfinityLoopIndicator 1.0 InfinityLoopIndicator.qml
ItemDelegate 1.0 ItemDelegate.qml
LinkIndicator2D 1.0 LinkIndicator2D.qml
@@ -18,6 +22,7 @@ Menu 1.0 Menu.qml
MenuItem 1.0 MenuItem.qml
MenuItemWithIcon 1.0 MenuItemWithIcon.qml
MenuSeparator 1.0 MenuSeparator.qml
ProgressBar 1.0 ProgressBar.qml
RadioButton 1.0 RadioButton.qml
RealSliderPopup 1.0 RealSliderPopup.qml
RealSpinBox 1.0 RealSpinBox.qml

View File

@@ -194,6 +194,15 @@ QtObject {
property real colorEditorPopupCmoboBoxWidth: 110
property real colorEditorPopupSpinBoxWidth: 54
// Toolbar
property real toolbarHeight: 35
property real toolbarSpacing: 8
// Dialog
property real dialogPadding: 12
property real dialogButtonSpacing: 10
property real dialogButtonPadding: 4
// Theme Colors
property bool isLightTheme: themeControlBackground.hsvValue > themeTextColor.hsvValue
@@ -266,7 +275,7 @@ QtObject {
// Slider colors
property string themeSliderActiveTrack: Theme.color(Theme.DSsliderActiveTrack)
property string themeSliderActiveTrackHover: Theme.color(Theme.DSactiveTrackHover)
property string themeSliderActiveTrackHover: Theme.color(Theme.DSsliderActiveTrackHover)
property string themeSliderActiveTrackFocus: Theme.color(Theme.DSsliderActiveTrackFocus)
property string themeSliderInactiveTrack: Theme.color(Theme.DSsliderInactiveTrack)
property string themeSliderInactiveTrackHover: Theme.color(Theme.DSsliderInactiveTrackHover)
@@ -286,10 +295,15 @@ QtObject {
property string themeTabInactiveBackground: Theme.color(Theme.DStabInactiveBackground)
property string themeTabInactiveText: Theme.color(Theme.DStabInactiveText)
// State Editor
property string themeStateSeparator: Theme.color(Theme.DSstateSeparatorColor)
property string themeStateBackground: Theme.color(Theme.DSstateBackgroundColor)
property string themeStatePreviewOutline: Theme.color(Theme.DSstatePreviewOutline)
// State Editor *new*
property color themeStatePanelBackground: Theme.color(Theme.DSstatePanelBackground)
property color themeStateHighlight: Theme.color(Theme.DSstateHighlight)
property string themeUnimportedModuleColor: Theme.color(Theme.DSUnimportedModuleColor)
// Taken out of Constants.js
@@ -312,9 +326,13 @@ QtObject {
property string themeListItemTextHover: Theme.color(Theme.DSnavigatorTextHover)
property string themeListItemTextPress: Theme.color(Theme.DSnavigatorTextSelected)
//Welcome Page
// Welcome Page
property string welcomeScreenBackground: Theme.color(Theme.DSwelcomeScreenBackground)
property string themeSubPanelBackground: Theme.color(Theme.DSsubPanelBackground)
property string themeThumbnailBackground: Theme.color(Theme.DSthumbnailBackground)
property string themeThumbnailLabelBackground: Theme.color(Theme.DSthumbnailLabelBackground)
// Dialog
property color themeDialogBackground: values.themeThumbnailBackground
property color themeDialogOutline: values.themeInteraction
}

View File

@@ -94,6 +94,9 @@ DSstateSeparatorColor=ff7c7b7b
DSstateBackgroundColor=ff383838
DSstatePreviewOutline=ffaaaaaa
DSstatePanelBackground=ff252525
DSstateHighlight=ff727272
DSchangedStateText=ff99ccff
DS3DAxisXColor=ffd00000

View File

@@ -85,6 +85,9 @@ DSstateSeparatorColor=ffadadad
DSstateBackgroundColor=ffe0e0e0
DSstatePreviewOutline=ff363636
DSstatePanelBackground=ffdadada
DSstateHighlight=ff8d8d8d
DSchangedStateText=ff99ccff
DS3DAxisXColor=ffd00000

View File

@@ -99,6 +99,9 @@ DSstateSeparatorColor=ffadadad
DSstateBackgroundColor=ffe0e0e0
DSstatePreviewOutline=ff363636
DSstatePanelBackground=ffdadada
DSstateHighlight=ff8d8d8d
DSchangedStateText=ff99ccff
DS3DAxisXColor=ffd00000

View File

@@ -96,6 +96,9 @@ DSstateSeparatorColor=ff7c7b7b
DSstateBackgroundColor=ff383838
DSstatePreviewOutline=ffaaaaaa
DSstatePanelBackground=ff252525
DSstateHighlight=ff727272
DSchangedStateText=ff99ccff
DS3DAxisXColor=ffd00000

View File

@@ -98,6 +98,9 @@ DSstateSeparatorColor=ff7c7b7b
DSstateBackgroundColor=ff383838
DSstatePreviewOutline=ffaaaaaa
DSstatePanelBackground=ff252525
DSstateHighlight=ff727272
DSchangedStateText=ff99ccff
DS3DAxisXColor=ffd00000

View File

@@ -94,6 +94,9 @@ DSstateSeparatorColor=ffadadad
DSstateBackgroundColor=ffe0e0e0
DSstatePreviewOutline=ff363636
DSstatePanelBackground=ffdadada
DSstateHighlight=ff8d8d8d
DSchangedStateText=ff99ccff
DS3DAxisXColor=ffd00000

View File

@@ -92,6 +92,9 @@ DSstateSeparatorColor=ff7c7b7b
DSstateBackgroundColor=ff383838
DSstatePreviewOutline=ffaaaaaa
DSstatePanelBackground=ff252525
DSstateHighlight=ff727272
DSchangedStateText=ff99ccff
DS3DAxisXColor=ffd00000

View File

@@ -237,7 +237,7 @@ DeviceShell::RunResult DeviceShell::run(const CommandLine &cmd, const QByteArray
const int id = ++m_currentId;
const auto it = m_commandOutput.insert(id, CommandRun{{-1, {}, {}}, &waiter});
QMetaObject::invokeMethod(m_shellProcess, [this, id, &cmd, &stdInData]() {
QMetaObject::invokeMethod(m_shellProcess, [this, id, cmd, stdInData]() {
const QString command = QString("%1 \"%2\" %3\n")
.arg(id)
.arg(QString::fromLatin1(stdInData.toBase64()))

View File

@@ -416,6 +416,9 @@ public:
DSgreenLight,
DSamberLight,
DSredLight,
DSstatePanelBackground,
DSstateHighlight,
};
enum ImageFile {

View File

@@ -424,6 +424,17 @@ extend_qtc_plugin(QmlDesigner
stateseditorwidget.cpp stateseditorwidget.h
)
extend_qtc_plugin(QmlDesigner
SOURCES_PREFIX components/stateseditornew
SOURCES
propertychangesmodel.cpp propertychangesmodel.h
propertymodel.cpp propertymodel.h
stateseditorimageprovider.cpp stateseditorimageprovider.h
stateseditormodel.cpp stateseditormodel.h
stateseditorview.cpp stateseditorview.h
stateseditorwidget.cpp stateseditorwidget.h
)
extend_qtc_plugin(QmlDesigner
SOURCES_PREFIX components/texteditor
SOURCES

View File

@@ -410,6 +410,20 @@ QQmlComponent *PropertyEditorContextObject::specificQmlComponent()
return m_qmlComponent;
}
bool PropertyEditorContextObject::hasMultiSelection() const
{
return m_hasMultiSelection;
}
void PropertyEditorContextObject::setHasMultiSelection(bool b)
{
if (b == m_hasMultiSelection)
return;
m_hasMultiSelection = b;
emit hasMultiSelectionChanged();
}
void PropertyEditorContextObject::setSpecificsUrl(const QUrl &newSpecificsUrl)
{
if (newSpecificsUrl == m_specificsUrl)

View File

@@ -46,6 +46,9 @@ class PropertyEditorContextObject : public QObject
Q_PROPERTY(QQmlComponent* specificQmlComponent READ specificQmlComponent NOTIFY specificQmlComponentChanged)
Q_PROPERTY(bool hasMultiSelection READ hasMultiSelection WRITE setHasMultiSelection NOTIFY
hasMultiSelectionChanged)
public:
PropertyEditorContextObject(QObject *parent = nullptr);
@@ -104,6 +107,10 @@ public:
bool hasAliasExport() const { return m_aliasExport; }
bool hasMultiSelection() const;
void setHasMultiSelection(bool);
signals:
void specificsUrlChanged();
void specificQmlDataChanged();
@@ -120,6 +127,7 @@ signals:
void hasAliasExportChanged();
void hasActiveTimelineChanged();
void activeDragSuffixChanged();
void hasMultiSelectionChanged();
public slots:
@@ -170,6 +178,8 @@ private:
bool m_setHasActiveTimeline = false;
QString m_activeDragSuffix;
bool m_hasMultiSelection = false;
};
class EasingCurveEditor : public QObject

View File

@@ -75,7 +75,6 @@ PropertyEditorView::PropertyEditorView(AsynchronousImageCache &imageCache)
m_stackedWidget->insertWidget(0, new QWidget(m_stackedWidget));
Quick2PropertyEditorView::registerQmlTypes();
m_stackedWidget->setWindowTitle(tr("Properties"));
}

View File

@@ -14,10 +14,12 @@
#include "gradientpresetcustomlistmodel.h"
#include "gradientpresetdefaultlistmodel.h"
#include "itemfiltermodel.h"
#include "propertychangesmodel.h"
#include "propertyeditorcontextobject.h"
#include "propertyeditorimageprovider.h"
#include "propertyeditorqmlbackend.h"
#include "propertyeditorvalue.h"
#include "propertymodel.h"
#include "qmlanchorbindingproxy.h"
#include "richtexteditor/richtexteditorproxy.h"
#include "theme.h"
@@ -56,6 +58,8 @@ void Quick2PropertyEditorView::registerQmlTypes()
RichTextEditorProxy::registerDeclarativeType();
SelectionDynamicPropertiesProxyModel::registerDeclarativeType();
DynamicPropertyRow::registerDeclarativeType();
Experimental::PropertyChangesModel::registerDeclarativeType();
Experimental::PropertyModel::registerDeclarativeType();
const QString resourcePath = PropertyEditorQmlBackend::propertyEditorResourcesPath();

View File

@@ -0,0 +1,152 @@
/****************************************************************************
**
** 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 "propertychangesmodel.h"
#include "stateseditorview.h"
#include <qmlmodelnodeproxy.h>
#include <utils/qtcassert.h>
#include <QDebug>
#include <QWidget>
#include <QtQml>
enum {
debug = false
};
namespace QmlDesigner {
namespace Experimental {
PropertyChangesModel::PropertyChangesModel(QObject *parent)
: QAbstractListModel(parent)
{}
PropertyChangesModel::~PropertyChangesModel()
{
if (m_view)
m_view->deregisterPropertyChangesModel(this);
}
int PropertyChangesModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid())
return 0;
QmlModelState modelState(m_modelNode);
if (!modelState.isValid() || modelState.isBaseState())
return 0;
return modelState.propertyChanges().size();
}
QVariant PropertyChangesModel::data(const QModelIndex &index, int role) const
{
if (index.parent().isValid() || index.column() != 0)
return {};
QmlModelState modelState(m_modelNode);
if (!modelState.isValid() || modelState.isBaseState())
return {};
QList<QmlPropertyChanges> propertyChanges = modelState.propertyChanges();
switch (role) {
case Target: {
const ModelNode target = propertyChanges.at(index.row()).target();
if (target.isValid())
return target.displayName();
return {};
}
case Explicit: {
return propertyChanges.at(index.row()).explicitValue();
}
case RestoreEntryValues: {
return propertyChanges.at(index.row()).restoreEntryValues();
}
case PropertyModelNode: {
return propertyChanges.at(index.row()).modelNode().toVariant();
}
}
return {};
}
QHash<int, QByteArray> PropertyChangesModel::roleNames() const
{
static QHash<int, QByteArray> roleNames{{Target, "target"},
{Explicit, "explicit"},
{RestoreEntryValues, "restoreEntryValues"},
{PropertyModelNode, "propertyModelNode"}};
return roleNames;
}
void PropertyChangesModel::setModelNodeBackend(const QVariant &modelNodeBackend)
{
ModelNode modelNode = modelNodeBackend.value<ModelNode>();
if (!modelNode.isValid() || modelNode.isRootNode())
return;
m_modelNode = modelNode;
QTC_ASSERT(m_modelNode.simplifiedTypeName() == "State", return );
m_view = qobject_cast<StatesEditorView *>(m_modelNode.view());
if (m_view)
m_view->registerPropertyChangesModel(this);
emit modelNodeBackendChanged();
}
void PropertyChangesModel::reset()
{
QAbstractListModel::beginResetModel();
QAbstractListModel::endResetModel();
emit countChanged();
}
int PropertyChangesModel::count() const
{
return rowCount();
}
void PropertyChangesModel::registerDeclarativeType()
{
qmlRegisterType<PropertyChangesModel>("HelperWidgets", 2, 0, "PropertyChangesModel");
}
QVariant PropertyChangesModel::modelNodeBackend() const
{
return QVariant();
}
} // namespace Experimental
} // namespace QmlDesigner

View File

@@ -0,0 +1,80 @@
/****************************************************************************
**
** 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 <QAbstractListModel>
#include <QPointer>
#include <modelnode.h>
namespace QmlDesigner {
namespace Experimental {
class StatesEditorView;
class PropertyChangesModel : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(int count READ count NOTIFY countChanged)
Q_PROPERTY(QVariant modelNodeBackendProperty READ modelNodeBackend WRITE setModelNodeBackend
NOTIFY modelNodeBackendChanged)
enum {
Target = Qt::DisplayRole,
Explicit = Qt::UserRole,
RestoreEntryValues,
PropertyModelNode
};
public:
PropertyChangesModel(QObject *parent = nullptr);
~PropertyChangesModel();
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QHash<int, QByteArray> roleNames() const override;
void setModelNodeBackend(const QVariant &modelNodeBackend);
void reset();
int count() const;
static void registerDeclarativeType();
signals:
void modelNodeBackendChanged();
void countChanged();
private:
QVariant modelNodeBackend() const;
private:
ModelNode m_modelNode;
QPointer<StatesEditorView> m_view;
};
} // namespace Experimental
} // namespace QmlDesigner

View File

@@ -0,0 +1,173 @@
/****************************************************************************
**
** 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 "propertymodel.h"
#include "abstractproperty.h"
#include "abstractview.h"
#include "bindingproperty.h"
#include "nodemetainfo.h"
#include "variantproperty.h"
#include <qmlmodelnodeproxy.h>
#include <utils/qtcassert.h>
#include <QDebug>
#include <QWidget>
#include <QtQml>
enum {
debug = false
};
namespace QmlDesigner {
namespace Experimental {
PropertyModel::PropertyModel(QObject *parent)
: QAbstractListModel(parent)
{}
int PropertyModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid())
return 0;
return m_properties.size();
}
QVariant PropertyModel::data(const QModelIndex &index, int role) const
{
if (index.parent().isValid() || index.column() != 0)
return QVariant();
switch (role) {
case Name: {
return m_properties.at(index.row()).name();
}
case Value: {
AbstractProperty property = m_properties.at(index.row());
if (property.isBindingProperty())
return property.toBindingProperty().expression();
if (property.isVariantProperty())
return property.toVariantProperty().value();
return {};
}
case Type: {
QmlPropertyChanges propertyChanges(m_modelNode);
if (!propertyChanges.isValid())
return {};
if (!propertyChanges.target().isValid())
return {};
return {};
// return propertyChanges.target().metaInfo().propertyType(
// m_properties.at(index.row()).name());
}
}
return {};
}
QHash<int, QByteArray> PropertyModel::roleNames() const
{
static QHash<int, QByteArray> roleNames{{Name, "name"}, {Value, "value"}, {Type, "type"}};
return roleNames;
}
void PropertyModel::setModelNodeBackend(const QVariant &modelNodeBackend)
{
ModelNode modelNode = modelNodeBackend.value<ModelNode>();
if (!modelNode.isValid())
return;
m_modelNode = modelNode;
QTC_ASSERT(m_modelNode.simplifiedTypeName() == "PropertyChanges", return );
setupModel();
emit modelNodeBackendChanged();
}
void PropertyModel::setExplicit(bool value)
{
if (!m_modelNode.isValid() || !m_modelNode.view()->isAttached())
return;
QmlPropertyChanges propertyChanges(m_modelNode);
if (propertyChanges.isValid())
propertyChanges.setExplicitValue(value);
}
void PropertyModel::setRestoreEntryValues(bool value)
{
if (!m_modelNode.isValid() || !m_modelNode.view()->isAttached())
return;
QmlPropertyChanges propertyChanges(m_modelNode);
if (propertyChanges.isValid())
propertyChanges.setRestoreEntryValues(value);
}
void PropertyModel::removeProperty(const QString &name)
{
if (!m_modelNode.isValid() || !m_modelNode.view()->isAttached())
return;
m_modelNode.removeProperty(name.toUtf8());
}
void PropertyModel::registerDeclarativeType()
{
qmlRegisterType<PropertyModel>("HelperWidgets", 2, 0, "PropertyModel");
}
QVariant PropertyModel::modelNodeBackend() const
{
return QVariant();
}
void PropertyModel::setupModel()
{
if (!m_modelNode.isValid() || !m_modelNode.view()->isAttached())
return;
QmlPropertyChanges propertyChanges(m_modelNode);
if (!propertyChanges.isValid())
return;
m_properties = propertyChanges.targetProperties();
}
} // namespace Experimental
} // namespace QmlDesigner

View File

@@ -0,0 +1,73 @@
/****************************************************************************
**
** 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 <QAbstractListModel>
#include <QPointer>
#include <modelnode.h>
namespace QmlDesigner {
namespace Experimental {
class PropertyModel : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(QVariant modelNodeBackendProperty READ modelNodeBackend WRITE setModelNodeBackend
NOTIFY modelNodeBackendChanged)
enum { Name = Qt::DisplayRole, Value = Qt::UserRole, Type };
public:
PropertyModel(QObject *parent = nullptr);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QHash<int, QByteArray> roleNames() const override;
void setModelNodeBackend(const QVariant &modelNodeBackend);
Q_INVOKABLE void setExplicit(bool value);
Q_INVOKABLE void setRestoreEntryValues(bool value);
Q_INVOKABLE void removeProperty(const QString &name);
static void registerDeclarativeType();
signals:
void modelNodeBackendChanged();
private:
QVariant modelNodeBackend() const;
void setupModel();
private:
ModelNode m_modelNode;
QList<AbstractProperty> m_properties;
};
} // namespace Experimental
} // namespace QmlDesigner

View File

@@ -0,0 +1,82 @@
/****************************************************************************
**
** 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 "stateseditorimageprovider.h"
#include "nodeinstanceview.h"
#include <QDebug>
namespace QmlDesigner {
namespace Experimental {
namespace Internal {
StatesEditorImageProvider::StatesEditorImageProvider()
: QQuickImageProvider(QQuickImageProvider::Image)
{
}
QImage StatesEditorImageProvider::requestImage(const QString &id, QSize *size, const QSize &requestedSize)
{
QImage image;
bool nodeInstanceViewIsDetached = m_nodeInstanceView.isNull() || !m_nodeInstanceView->model();
if (!nodeInstanceViewIsDetached) {
QString imageId = id.split(QLatin1Char('-')).constFirst();
if (imageId == QLatin1String("baseState")) {
image = m_nodeInstanceView->statePreviewImage(m_nodeInstanceView->rootModelNode());
} else {
bool canBeConverted;
int instanceId = imageId.toInt(&canBeConverted);
if (canBeConverted && m_nodeInstanceView->hasModelNodeForInternalId(instanceId)) {
image = m_nodeInstanceView->statePreviewImage(m_nodeInstanceView->modelNodeForInternalId(instanceId));
}
}
}
if (image.isNull()) {
//creating white QImage
QSize newSize = requestedSize;
if (newSize.isEmpty())
newSize = QSize (100, 100);
QImage image(newSize, QImage::Format_ARGB32);
image.fill(0xFFFFFFFF);
return image;
}
*size = image.size();
return image;
}
void StatesEditorImageProvider::setNodeInstanceView(NodeInstanceView *nodeInstanceView)
{
m_nodeInstanceView = nodeInstanceView;
}
} // namespace Internal
} // namespace Experimental
} // namespace QmlDesigner

View File

@@ -0,0 +1,54 @@
/****************************************************************************
**
** 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"abstractview.h"
#include <QQuickImageProvider>
#include <QPointer>
namespace QmlDesigner {
namespace Experimental {
namespace Internal {
class StatesEditorView;
class StatesEditorImageProvider : public QQuickImageProvider
{
public:
StatesEditorImageProvider();
QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize) override;
void setNodeInstanceView(NodeInstanceView *nodeInstanceView);
private:
QPointer<NodeInstanceView> m_nodeInstanceView;
};
} // namespace Internal
} // namespace Experimental
} // namespace QmlDesigner

View File

@@ -0,0 +1,457 @@
/****************************************************************************
**
** 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 "stateseditormodel.h"
#include "stateseditorview.h"
#include <bindingproperty.h>
#include <modelnode.h>
#include <nodelistproperty.h>
#include <nodemetainfo.h>
#include <rewriterview.h>
#include <variantproperty.h>
#include <coreplugin/icore.h>
#include <coreplugin/messagebox.h>
#include <utils/algorithm.h>
#include <utils/qtcassert.h>
#include <QDebug>
#include <QWidget>
enum {
debug = false
};
namespace QmlDesigner {
namespace Experimental {
StatesEditorModel::StatesEditorModel(StatesEditorView *view)
: QAbstractListModel(view)
, m_statesEditorView(view)
, m_hasExtend(false)
, m_extendedStates()
{
QObject::connect(this, &StatesEditorModel::dataChanged, [this]() { emit baseStateChanged(); });
}
int StatesEditorModel::count() const
{
return rowCount();
}
QModelIndex StatesEditorModel::index(int row, int column, const QModelIndex &parent) const
{
if (m_statesEditorView.isNull())
return {};
int internalNodeId = 0;
if (row > 0)
internalNodeId = m_statesEditorView->activeStatesGroupNode()
.nodeListProperty("states")
.at(row - 1)
.internalId();
return hasIndex(row, column, parent) ? createIndex(row, column, internalNodeId) : QModelIndex();
}
int StatesEditorModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid() || m_statesEditorView.isNull() || !m_statesEditorView->model())
return 0;
if (!m_statesEditorView->activeStatesGroupNode().hasNodeListProperty("states"))
return 1; // base state
return m_statesEditorView->activeStatesGroupNode().nodeListProperty("states").count() + 1;
}
void StatesEditorModel::reset()
{
QAbstractListModel::beginResetModel();
QAbstractListModel::endResetModel();
evaluateExtend();
}
QVariant StatesEditorModel::data(const QModelIndex &index, int role) const
{
if (index.parent().isValid() || index.column() != 0 || m_statesEditorView.isNull()
|| !m_statesEditorView->hasModelNodeForInternalId(index.internalId()))
return QVariant();
ModelNode stateNode;
if (index.internalId() > 0)
stateNode = m_statesEditorView->modelNodeForInternalId(index.internalId());
switch (role) {
case StateNameRole: {
if (index.row() == 0) {
return tr("base state", "Implicit default state");
} else {
if (stateNode.hasVariantProperty("name"))
return stateNode.variantProperty("name").value();
else
return QVariant();
}
}
case StateImageSourceRole: {
static int randomNumber = 0;
randomNumber++;
if (index.row() == 0)
return QString("image://qmldesigner_stateseditor/baseState-%1").arg(randomNumber);
else
return QString("image://qmldesigner_stateseditor/%1-%2").arg(index.internalId()).arg(randomNumber);
}
case InternalNodeId:
return index.internalId();
case HasWhenCondition:
return stateNode.isValid() && stateNode.hasProperty("when");
case WhenConditionString: {
if (stateNode.isValid() && stateNode.hasBindingProperty("when"))
return stateNode.bindingProperty("when").expression();
else
return QString();
}
case IsDefault: {
QmlModelState modelState(stateNode);
if (modelState.isValid())
return modelState.isDefault();
return false;
}
case ModelHasDefaultState:
return hasDefaultState();
case HasExtend:
return stateNode.isValid() && stateNode.hasProperty("extend");
case ExtendString: {
if (stateNode.isValid() && stateNode.hasVariantProperty("extend"))
return stateNode.variantProperty("extend").value();
else
return QString();
}
}
return QVariant();
}
QHash<int, QByteArray> StatesEditorModel::roleNames() const
{
static QHash<int, QByteArray> roleNames{{StateNameRole, "stateName"},
{StateImageSourceRole, "stateImageSource"},
{InternalNodeId, "internalNodeId"},
{HasWhenCondition, "hasWhenCondition"},
{WhenConditionString, "whenConditionString"},
{IsDefault, "isDefault"},
{ModelHasDefaultState, "modelHasDefaultState"},
{HasExtend, "hasExtend"},
{ExtendString, "extendString"}};
return roleNames;
}
void StatesEditorModel::insertState(int stateIndex)
{
if (stateIndex >= 0) {
const int updateIndex = stateIndex + 1;
beginInsertRows(QModelIndex(), updateIndex, updateIndex);
endInsertRows();
emit dataChanged(index(updateIndex, 0), index(updateIndex, 0));
}
}
void StatesEditorModel::updateState(int beginIndex, int endIndex)
{
if (beginIndex >= 0 && endIndex >= 0)
emit dataChanged(index(beginIndex, 0), index(endIndex, 0));
}
void StatesEditorModel::removeState(int stateIndex)
{
if (stateIndex >= 0) {
beginRemoveRows(QModelIndex(), 0, stateIndex);
endRemoveRows();
}
}
void StatesEditorModel::renameState(int internalNodeId, const QString &newName)
{
if (newName == m_statesEditorView->currentStateName())
return;
if (newName.isEmpty() ||! m_statesEditorView->validStateName(newName)) {
QTimer::singleShot(0, this, [newName] {
Core::AsynchronousMessageBox::warning(
tr("Invalid state name"),
newName.isEmpty() ?
tr("The empty string as a name is reserved for the base state.") :
tr("Name already used in another state"));
});
reset();
} else {
m_statesEditorView->renameState(internalNodeId, newName);
}
}
void StatesEditorModel::setWhenCondition(int internalNodeId, const QString &condition)
{
m_statesEditorView->setWhenCondition(internalNodeId, condition);
}
void StatesEditorModel::resetWhenCondition(int internalNodeId)
{
m_statesEditorView->resetWhenCondition(internalNodeId);
}
QStringList StatesEditorModel::autoComplete(const QString &text, int pos, bool explicitComplete)
{
Model *model = m_statesEditorView->model();
if (model && model->rewriterView())
return model->rewriterView()->autoComplete(text, pos, explicitComplete);
return QStringList();
}
QVariant StatesEditorModel::stateModelNode(int internalNodeId)
{
if (!m_statesEditorView->model())
return QVariant();
ModelNode node = m_statesEditorView->modelNodeForInternalId(internalNodeId);
return QVariant::fromValue(m_statesEditorView->modelNodeForInternalId(internalNodeId));
}
void StatesEditorModel::setStateAsDefault(int internalNodeId)
{
m_statesEditorView->setStateAsDefault(internalNodeId);
}
void StatesEditorModel::resetDefaultState()
{
m_statesEditorView->resetDefaultState();
}
bool StatesEditorModel::hasDefaultState() const
{
return m_statesEditorView->hasDefaultState();
}
void StatesEditorModel::setAnnotation(int internalNodeId)
{
m_statesEditorView->setAnnotation(internalNodeId);
}
void StatesEditorModel::removeAnnotation(int internalNodeId)
{
m_statesEditorView->removeAnnotation(internalNodeId);
}
bool StatesEditorModel::hasAnnotation(int internalNodeId) const
{
return m_statesEditorView->hasAnnotation(internalNodeId);
}
QStringList StatesEditorModel::stateGroups() const
{
return {};
// auto stateGroups = Utils::transform(m_statesEditorView->allModelNodesOfType(
// "QtQuick.StateGroup"),
// [](const ModelNode &node) { return node.displayName(); });
// stateGroups.prepend(tr("Root"));
// return stateGroups;
}
QString StatesEditorModel::activeStateGroup() const
{
return {};
// auto stateGroup = m_statesEditorView->activeStatesGroupNode();
// if (!stateGroup.isValid())
// return QString();
// return stateGroup.displayName();
}
void StatesEditorModel::setActiveStateGroup(const QString &name)
{
// auto modelNode = Utils::findOrDefault(m_statesEditorView->allModelNodesOfType(
// "QtQuick.StateGroup"),
// [&name](const ModelNode &node) {
// return node.displayName() == name;
// });
// QTC_ASSERT(!modelNode.isValid(), return );
// if (modelNode.isValid())
// m_statesEditorView->setActiveStatesGroupNode(modelNode);
}
int StatesEditorModel::activeStateGroupIndex() const
{
return m_statesEditorView->activeStatesGroupIndex();
}
void StatesEditorModel::setActiveStateGroupIndex(int index)
{
m_statesEditorView->setActiveStatesGroupIndex(index);
}
bool StatesEditorModel::renameActiveStateGroup(const QString &name)
{
auto stateGroup = m_statesEditorView->activeStatesGroupNode();
if (!stateGroup.isValid() || stateGroup.isRootNode())
return false;
if (!QmlDesigner::ModelNode::isValidId(name) || m_statesEditorView->hasId(name)) {
QString errMsg = QmlDesigner::ModelNode::getIdValidityErrorMessage(name);
if (!errMsg.isEmpty())
Core::AsynchronousMessageBox::warning(tr("Invalid ID"), errMsg);
else
Core::AsynchronousMessageBox::warning(tr("Invalid ID"),
tr("%1 already exists.").arg(name));
return false;
}
stateGroup.setIdWithRefactoring(name);
emit stateGroupsChanged();
return true;
}
void StatesEditorModel::addStateGroup(const QString &name)
{
m_statesEditorView->executeInTransaction("createStateGroup", [this, name]() {
const TypeName typeName = "QtQuick.StateGroup";
auto metaInfo = m_statesEditorView->model()->metaInfo(typeName);
int minorVersion = metaInfo.minorVersion();
int majorVersion = metaInfo.majorVersion();
auto stateGroupNode = m_statesEditorView->createModelNode(typeName,
majorVersion,
minorVersion);
stateGroupNode.setIdWithoutRefactoring(m_statesEditorView->model()->generateNewId(name));
m_statesEditorView->rootModelNode().defaultNodeAbstractProperty().reparentHere(
stateGroupNode);
m_statesEditorView->setActiveStatesGroupNode(stateGroupNode);
});
}
void StatesEditorModel::removeStateGroup()
{
if (m_statesEditorView->activeStatesGroupNode().isRootNode())
return;
m_statesEditorView->executeInTransaction("removeStateGroup", [this]() {
m_statesEditorView->activeStatesGroupNode().destroy();
});
}
QVariantMap StatesEditorModel::get(int idx) const
{
const QHash<int, QByteArray> &names = roleNames();
QHash<int, QByteArray>::const_iterator i = names.constBegin();
QVariantMap res;
QModelIndex modelIndex = index(idx);
while (i != names.constEnd()) {
QVariant data = modelIndex.data(i.key());
res[QString::fromUtf8(i.value())] = data;
++i;
}
return res;
}
QVariantMap StatesEditorModel::baseState() const
{
return get(0);
}
bool StatesEditorModel::hasExtend() const
{
return m_hasExtend;
}
QStringList StatesEditorModel::extendedStates() const
{
return m_extendedStates;
}
void StatesEditorModel::move(int from, int to)
{
// This does not alter the code (rewriter) which means the reordering is not presistent
if (from == to)
return;
int specialIndex = (from < to ? to + 1 : to);
beginMoveRows(QModelIndex(), from, from, QModelIndex(), specialIndex);
endMoveRows();
}
void StatesEditorModel::drop(int from, int to)
{
m_statesEditorView->moveStates(from, to);
}
void StatesEditorModel::evaluateExtend()
{
bool hasExtend = m_statesEditorView->hasExtend();
if (m_hasExtend != hasExtend) {
m_hasExtend = hasExtend;
emit hasExtendChanged();
}
auto extendedStates = m_statesEditorView->extendedStates();
if (extendedStates.size() != m_extendedStates.size()) {
m_extendedStates = extendedStates;
emit extendedStatesChanged();
return;
}
for (int i = 0; i != m_extendedStates.size(); ++i) {
if (extendedStates[i] != m_extendedStates[i]) {
m_extendedStates = extendedStates;
emit extendedStatesChanged();
return;
}
}
}
} // namespace Experimental
} // namespace QmlDesigner

View File

@@ -0,0 +1,127 @@
/****************************************************************************
**
** 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 <QAbstractListModel>
#include <QPointer>
namespace QmlDesigner {
namespace Experimental {
class StatesEditorView;
class StatesEditorModel : public QAbstractListModel
{
Q_OBJECT
enum {
StateNameRole = Qt::DisplayRole,
StateImageSourceRole = Qt::UserRole,
InternalNodeId,
HasWhenCondition,
WhenConditionString,
IsDefault,
ModelHasDefaultState,
HasExtend,
ExtendString
};
public:
StatesEditorModel(StatesEditorView *view);
Q_INVOKABLE int count() const;
QModelIndex index(int row, int column = 0, const QModelIndex &parent = QModelIndex()) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QHash<int, QByteArray> roleNames() const override;
void insertState(int stateIndex);
void removeState(int stateIndex);
void updateState(int beginIndex, int endIndex);
Q_INVOKABLE void renameState(int internalNodeId, const QString &newName);
Q_INVOKABLE void setWhenCondition(int internalNodeId, const QString &condition);
Q_INVOKABLE void resetWhenCondition(int internalNodeId);
Q_INVOKABLE QStringList autoComplete(const QString &text, int pos, bool explicitComplete);
Q_INVOKABLE QVariant stateModelNode(int internalNodeId);
Q_INVOKABLE void setStateAsDefault(int internalNodeId);
Q_INVOKABLE void resetDefaultState();
Q_INVOKABLE bool hasDefaultState() const;
Q_INVOKABLE void setAnnotation(int internalNodeId);
Q_INVOKABLE void removeAnnotation(int internalNodeId);
Q_INVOKABLE bool hasAnnotation(int internalNodeId) const;
QStringList stateGroups() const;
QString activeStateGroup() const;
void setActiveStateGroup(const QString &name);
int activeStateGroupIndex() const;
void setActiveStateGroupIndex(int index);
Q_INVOKABLE bool renameActiveStateGroup(const QString &name);
Q_INVOKABLE void addStateGroup(const QString &name);
Q_INVOKABLE void removeStateGroup();
Q_INVOKABLE QVariantMap get(int idx) const;
QVariantMap baseState() const;
bool hasExtend() const;
QStringList extendedStates() const;
Q_PROPERTY(QVariantMap baseState READ baseState NOTIFY baseStateChanged)
Q_PROPERTY(QStringList extendedStates READ extendedStates NOTIFY extendedStatesChanged)
Q_PROPERTY(bool hasExtend READ hasExtend NOTIFY hasExtendChanged)
Q_PROPERTY(QString activeStateGroup READ activeStateGroup WRITE setActiveStateGroup NOTIFY
activeStateGroupChanged)
Q_PROPERTY(int activeStateGroupIndex READ activeStateGroupIndex WRITE setActiveStateGroupIndex
NOTIFY activeStateGroupIndexChanged)
Q_PROPERTY(QStringList stateGroups READ stateGroups NOTIFY stateGroupsChanged)
Q_INVOKABLE void move(int from, int to);
Q_INVOKABLE void drop(int from, int to);
void reset();
void evaluateExtend();
signals:
void changedToState(int n);
void baseStateChanged();
void hasExtendChanged();
void extendedStatesChanged();
void activeStateGroupChanged();
void activeStateGroupIndexChanged();
void stateGroupsChanged();
private:
QPointer<StatesEditorView> m_statesEditorView;
bool m_hasExtend;
QStringList m_extendedStates;
};
} // namespace Experimental
} // namespace QmlDesigner

View File

@@ -0,0 +1,937 @@
/****************************************************************************
**
** 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 "stateseditorview.h"
#include "propertychangesmodel.h"
#include "stateseditormodel.h"
#include "stateseditorwidget.h"
#include <QDebug>
#include <QRegularExpression>
#include <QMessageBox>
#include <cmath>
#include <memory>
#include <nodemetainfo.h>
#include <bindingproperty.h>
#include <customnotifications.h>
#include <nodelistproperty.h>
#include <rewritingexception.h>
#include <variantproperty.h>
#include <qmldesignerconstants.h>
#include <qmldesignerplugin.h>
#include <qmlitemnode.h>
#include <qmlstate.h>
#include <annotationeditor/annotationeditor.h>
#include <utils/algorithm.h>
#include <utils/qtcassert.h>
namespace QmlDesigner {
namespace Experimental {
/**
We always have 'one' current state, where we get updates from (see sceneChanged()). In case
the current state is the base state, we render the base state + all other states.
*/
StatesEditorView::StatesEditorView(QObject *parent) :
AbstractView(parent),
m_statesEditorModel(new StatesEditorModel(this)),
m_lastIndex(-1),
m_editor(nullptr)
{
Q_ASSERT(m_statesEditorModel);
// base state
}
StatesEditorView::~StatesEditorView()
{
if (m_editor)
delete m_editor;
delete m_statesEditorWidget.data();
}
WidgetInfo StatesEditorView::widgetInfo()
{
if (!m_statesEditorWidget)
m_statesEditorWidget = new StatesEditorWidget(this, m_statesEditorModel.data());
return createWidgetInfo(m_statesEditorWidget.data(),
"StatesEditor",
WidgetInfo::BottomPane,
0,
tr("States"));
}
void StatesEditorView::rootNodeTypeChanged(const QString &/*type*/, int /*majorVersion*/, int /*minorVersion*/)
{
checkForStatesAvailability();
}
ModelNode StatesEditorView::activeStatesGroupNode() const
{
return m_activeStatesGroupNode;
}
void StatesEditorView::setActiveStatesGroupNode(const ModelNode &modelNode)
{
if (m_activeStatesGroupNode == modelNode)
return;
m_activeStatesGroupNode = modelNode;
resetModel();
emit m_statesEditorModel->activeStateGroupChanged();
emit m_statesEditorModel->activeStateGroupIndexChanged();
}
int StatesEditorView::activeStatesGroupIndex() const
{
return 1;
// return Utils::indexOf(allModelNodesOfType("QtQuick.StateGroup"),
// [this](const ModelNode &node) { return node == m_activeStatesGroupNode; })
// + 1;
}
void StatesEditorView::setActiveStatesGroupIndex(int index)
{
if (index > 0) {
// const ModelNode statesGroup = allModelNodesOfType("QtQuick.StateGroup").at(index - 1);
// if (statesGroup.isValid())
// setActiveStatesGroupNode(statesGroup);
} else {
setActiveStatesGroupNode(rootModelNode());
}
}
void StatesEditorView::registerPropertyChangesModel(PropertyChangesModel *model)
{
m_propertyChangedModels.insert(model);
}
void StatesEditorView::deregisterPropertyChangesModel(PropertyChangesModel *model)
{
m_propertyChangedModels.remove(model);
}
void StatesEditorView::synchonizeCurrentStateFromWidget()
{
if (!model())
return;
if (m_block)
return;
int internalId = m_statesEditorWidget->currentStateInternalId();
if (internalId > 0 && hasModelNodeForInternalId(internalId)) {
ModelNode node = modelNodeForInternalId(internalId);
QmlModelState modelState(node);
if (modelState.isValid() && modelState != currentState())
setCurrentState(modelState);
} else {
setCurrentState(baseState());
}
}
void StatesEditorView::createNewState()
{
// can happen when root node is e.g. a ListModel
if (!QmlVisualNode::isValidQmlVisualNode(activeStatesGroupNode())
&& m_activeStatesGroupNode.type() != "QtQuick.StateGroup")
return;
QmlDesignerPlugin::emitUsageStatistics(Constants::EVENT_STATE_ADDED);
QStringList modelStateNames = activeStateGroup().names();
QString newStateName;
int index = 1;
while (true) {
newStateName = QString(QStringLiteral("State%1")).arg(index++);
if (!modelStateNames.contains(newStateName))
break;
}
executeInTransaction("createNewState", [this, newStateName]() {
activeStatesGroupNode().validId();
ModelNode newState = activeStateGroup().addState(newStateName);
setCurrentState(newState);
});
}
void StatesEditorView::cloneState(int nodeId)
{
if (!(nodeId > 0 && hasModelNodeForInternalId(nodeId)))
return;
ModelNode stateNode(modelNodeForInternalId(nodeId));
QTC_ASSERT(stateNode.simplifiedTypeName() == "State", return );
QmlModelState modelState(stateNode);
if (!modelState.isValid() || modelState.isBaseState())
return;
QmlDesignerPlugin::emitUsageStatistics(Constants::EVENT_STATE_CLONED);
QString newName = modelState.name();
// Strip out numbers at the end of the string
QRegularExpression regEx(QLatin1String("[0-9]+$"));
const QRegularExpressionMatch match = regEx.match(newName);
if (match.hasMatch() && (match.capturedStart() + match.capturedLength() == newName.length()))
newName = newName.left(match.capturedStart());
int i = 1;
QStringList stateNames = activeStateGroup().names();
while (stateNames.contains(newName + QString::number(i)))
i++;
const QString newStateName = newName + QString::number(i);
QmlModelState newState;
executeInTransaction("cloneState", [newStateName, modelState, &newState]() {
newState = modelState.duplicate(newStateName);
});
ModelNode newNode = newState.modelNode();
int from = newNode.parentProperty().indexOf(newNode);
int to = stateNode.parentProperty().indexOf(stateNode) + 1;
executeInTransaction("moveState", [this, &newState, from, to]() {
activeStatesGroupNode().nodeListProperty("states").slide(from, to);
setCurrentState(newState);
});
}
void StatesEditorView::extendState(int nodeId)
{
if (!(nodeId > 0 && hasModelNodeForInternalId(nodeId)))
return;
ModelNode stateNode(modelNodeForInternalId(nodeId));
QTC_ASSERT(stateNode.simplifiedTypeName() == "State", return );
QmlModelState modelState(stateNode);
if (!modelState.isValid() || modelState.isBaseState())
return;
QmlDesignerPlugin::emitUsageStatistics(Constants::EVENT_STATE_EXTENDED);
QString newName = modelState.name();
// Strip out numbers at the end of the string
QRegularExpression regEx(QLatin1String("[0-9]+$"));
const QRegularExpressionMatch match = regEx.match(newName);
if (match.hasMatch() && (match.capturedStart() + match.capturedLength() == newName.length()))
newName = newName.left(match.capturedStart());
int i = 1;
QStringList stateNames = activeStateGroup().names();
while (stateNames.contains(newName + QString::number(i)))
i++;
const QString newStateName = newName + QString::number(i);
QmlModelState newState;
executeInTransaction("extendState", [this, newStateName, modelState, &newState]() {
newState = activeStateGroup().addState(newStateName);
newState.setExtend(modelState.name());
});
ModelNode newNode = newState.modelNode();
int from = newNode.parentProperty().indexOf(newNode);
int to = stateNode.parentProperty().indexOf(stateNode) + 1;
executeInTransaction("moveState", [this, &newState, from, to]() {
activeStatesGroupNode().nodeListProperty("states").slide(from, to);
setCurrentState(newState);
});
}
void StatesEditorView::removeState(int nodeId)
{
try {
if (nodeId > 0 && hasModelNodeForInternalId(nodeId)) {
ModelNode stateNode(modelNodeForInternalId(nodeId));
QTC_ASSERT(stateNode.simplifiedTypeName() == "State", return );
QmlModelState modelState(stateNode);
if (modelState.isValid()) {
QStringList lockedTargets;
const auto propertyChanges = modelState.propertyChanges();
// confirm removing not empty states
if (!propertyChanges.isEmpty()) {
QMessageBox msgBox;
msgBox.setTextFormat(Qt::RichText);
msgBox.setIcon(QMessageBox::Question);
msgBox.setWindowTitle(tr("Remove State"));
msgBox.setText(
tr("This state is not empty. Are you sure you want to remove it?"));
msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::Cancel);
msgBox.setDefaultButton(QMessageBox::Yes);
if (msgBox.exec() == QMessageBox::Cancel)
return;
}
// confirm removing states with locked targets
for (const QmlPropertyChanges &change : propertyChanges) {
const ModelNode target = change.target();
QTC_ASSERT(target.isValid(), continue);
if (target.locked())
lockedTargets.push_back(target.id());
}
if (!lockedTargets.empty()) {
Utils::sort(lockedTargets);
QString detailedText = QString("<b>" + tr("Locked components:") + "</b><br>");
for (const auto &id : qAsConst(lockedTargets))
detailedText.append("- " + id + "<br>");
detailedText.chop(QString("<br>").size());
QMessageBox msgBox;
msgBox.setTextFormat(Qt::RichText);
msgBox.setIcon(QMessageBox::Question);
msgBox.setWindowTitle(tr("Remove State"));
msgBox.setText(QString(tr("Removing this state will modify locked components.")
+ "<br><br>%1")
.arg(detailedText));
msgBox.setInformativeText(tr("Continue by removing the state?"));
msgBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
msgBox.setDefaultButton(QMessageBox::Ok);
if (msgBox.exec() == QMessageBox::Cancel)
return;
}
}
NodeListProperty parentProperty = stateNode.parentProperty().toNodeListProperty();
if (parentProperty.count() <= 1) {
setCurrentState(baseState());
} else if (parentProperty.isValid()) {
int index = parentProperty.indexOf(stateNode);
if (index == 0)
setCurrentState(parentProperty.at(1));
else
setCurrentState(parentProperty.at(index - 1));
}
stateNode.destroy();
}
} catch (const RewritingException &e) {
e.showException();
}
}
void StatesEditorView::resetModel()
{
if (m_bulkChange) {
m_modelDirty = true;
return;
}
if (m_statesEditorModel)
m_statesEditorModel->reset();
if (m_statesEditorWidget) {
if (currentState().isBaseState())
m_statesEditorWidget->setCurrentStateInternalId(0);
else
m_statesEditorWidget->setCurrentStateInternalId(currentState().modelNode().internalId());
}
m_modelDirty = false;
}
void StatesEditorView::resetPropertyChangesModels()
{
if (m_bulkChange) {
m_propertyChangesDirty = true;
return;
}
std::for_each(m_propertyChangedModels.begin(),
m_propertyChangedModels.end(),
[](PropertyChangesModel *model) { model->reset(); });
m_propertyChangesDirty = false;
}
void StatesEditorView::resetExtend()
{
if (m_bulkChange) {
m_extendDirty = true;
return;
}
m_statesEditorModel->evaluateExtend();
m_extendDirty = false;
}
void StatesEditorView::resetStateGroups()
{
if (m_bulkChange) {
m_stateGroupsDirty = true;
return;
}
emit m_statesEditorModel->stateGroupsChanged();
m_stateGroupsDirty = false;
}
void StatesEditorView::checkForStatesAvailability()
{
if (m_statesEditorWidget) {
const bool isVisual = QmlVisualNode::isValidQmlVisualNode(activeStatesGroupNode());
m_statesEditorWidget->showAddNewStatesButton(isVisual);
}
}
void StatesEditorView::beginBulkChange()
{
m_bulkChange = true;
}
void StatesEditorView::endBulkChange()
{
if (!m_bulkChange)
return;
m_bulkChange = false;
if (m_modelDirty)
resetModel();
if (m_propertyChangesDirty)
resetPropertyChangesModels();
if (m_extendDirty)
resetExtend();
if (m_stateGroupsDirty)
resetStateGroups();
}
void StatesEditorView::setCurrentState(const QmlModelState &state)
{
if (!model() && !state.isValid())
return;
if (currentStateNode() != state.modelNode())
setCurrentStateNode(state.modelNode());
}
QmlModelState StatesEditorView::baseState() const
{
return QmlModelState::createBaseState(this);
}
QmlModelStateGroup StatesEditorView::activeStateGroup() const
{
return QmlModelStateGroup(activeStatesGroupNode());
}
bool StatesEditorView::validStateName(const QString &name) const
{
if (name == tr("base state"))
return false;
const QList<QmlModelState> modelStates = activeStateGroup().allStates();
for (const QmlModelState &state : modelStates) {
if (state.name() == name)
return false;
}
return true;
}
bool StatesEditorView::hasExtend() const
{
if (!model())
return false;
const QList<QmlModelState> modelStates = activeStateGroup().allStates();
for (const QmlModelState &state : modelStates) {
if (state.hasExtend())
return true;
}
return false;
}
QStringList StatesEditorView::extendedStates() const
{
if (!model())
return QStringList();
QStringList states;
const QList<QmlModelState> modelStates = activeStateGroup().allStates();
for (const QmlModelState &state : modelStates) {
if (state.hasExtend())
states.append(state.extend());
}
states.removeDuplicates();
return states;
}
QString StatesEditorView::currentStateName() const
{
return currentState().isValid() ? currentState().name() : QString();
}
void StatesEditorView::renameState(int internalNodeId, const QString &newName)
{
if (hasModelNodeForInternalId(internalNodeId)) {
QmlModelState renamedState(modelNodeForInternalId(internalNodeId));
try {
if (renamedState.isValid() && renamedState.name() != newName) {
executeInTransaction("renameState", [this, &renamedState, &newName]() {
// Jump to base state for the change
QmlModelState oldState = currentState();
setCurrentState(baseState());
const bool updateDefault = renamedState.isDefault();
// If state is extended rename all references
QList<QmlModelState> states;
const QList<QmlModelState> modelStates = activeStateGroup().allStates();
for (const QmlModelState &state : modelStates) {
if (state.hasExtend() && state.extend() == renamedState.name())
states.append(state);
}
renamedState.setName(newName.trimmed());
for (QmlModelState &state : states)
state.setExtend(newName.trimmed());
if (updateDefault)
renamedState.setAsDefault();
setCurrentState(oldState);
});
}
} catch (const RewritingException &e) {
e.showException();
}
}
}
void StatesEditorView::setWhenCondition(int internalNodeId, const QString &condition)
{
if (m_block)
return;
m_block = true;
auto guard = qScopeGuard([&]() { m_block = false; });
if (hasModelNodeForInternalId(internalNodeId)) {
QmlModelState state(modelNodeForInternalId(internalNodeId));
try {
if (state.isValid())
state.modelNode().bindingProperty("when").setExpression(condition);
} catch (const Exception &e) {
e.showException();
}
}
}
void StatesEditorView::resetWhenCondition(int internalNodeId)
{
if (m_block)
return;
m_block = true;
auto guard = qScopeGuard([&]() { m_block = false; });
if (hasModelNodeForInternalId(internalNodeId)) {
QmlModelState state(modelNodeForInternalId(internalNodeId));
try {
if (state.isValid() && state.modelNode().hasProperty("when"))
state.modelNode().removeProperty("when");
} catch (const RewritingException &e) {
e.showException();
}
}
}
void StatesEditorView::setStateAsDefault(int internalNodeId)
{
if (m_block)
return;
m_block = true;
auto guard = qScopeGuard([&]() { m_block = false; });
if (hasModelNodeForInternalId(internalNodeId)) {
QmlModelState state(modelNodeForInternalId(internalNodeId));
try {
if (state.isValid())
state.setAsDefault();
} catch (const RewritingException &e) {
e.showException();
}
}
}
void StatesEditorView::resetDefaultState()
{
if (m_block)
return;
m_block = true;
auto guard = qScopeGuard([&]() { m_block = false; });
try {
if (activeStatesGroupNode().hasProperty("state"))
activeStatesGroupNode().removeProperty("state");
} catch (const RewritingException &e) {
e.showException();
}
}
bool StatesEditorView::hasDefaultState() const
{
return activeStatesGroupNode().hasProperty("state");
}
void StatesEditorView::setAnnotation(int internalNodeId)
{
if (m_block)
return;
m_block = true;
auto guard = qScopeGuard([&]() { m_block = false; });
if (hasModelNodeForInternalId(internalNodeId)) {
QmlModelState state(modelNodeForInternalId(internalNodeId));
try {
if (state.isValid()) {
ModelNode modelNode = state.modelNode();
if (modelNode.isValid()) {
if (!m_editor)
m_editor = new AnnotationEditor(this);
m_editor->setModelNode(modelNode);
m_editor->showWidget();
}
}
} catch (const RewritingException &e) {
e.showException();
}
}
}
void StatesEditorView::removeAnnotation(int internalNodeId)
{
if (m_block)
return;
m_block = true;
auto guard = qScopeGuard([&]() { m_block = false; });
if (hasModelNodeForInternalId(internalNodeId)) {
QmlModelState state(modelNodeForInternalId(internalNodeId));
try {
if (state.isValid())
state.removeAnnotation();
} catch (const RewritingException &e) {
e.showException();
}
}
}
bool StatesEditorView::hasAnnotation(int internalNodeId) const
{
if (!model())
return false;
if (hasModelNodeForInternalId(internalNodeId)) {
QmlModelState state(modelNodeForInternalId(internalNodeId));
if (state.isValid())
return state.hasAnnotation();
}
return false;
}
void StatesEditorView::modelAttached(Model *model)
{
if (model == AbstractView::model())
return;
QTC_ASSERT(model, return );
AbstractView::modelAttached(model);
m_activeStatesGroupNode = rootModelNode();
if (m_statesEditorWidget)
m_statesEditorWidget->setNodeInstanceView(nodeInstanceView());
checkForStatesAvailability();
resetModel();
resetStateGroups();
}
void StatesEditorView::modelAboutToBeDetached(Model *model)
{
AbstractView::modelAboutToBeDetached(model);
resetModel();
}
void StatesEditorView::propertiesRemoved(const QList<AbstractProperty>& propertyList)
{
for (const AbstractProperty &property : propertyList) {
if (property.name() == "states" && property.parentModelNode() == activeStateGroup().modelNode())
resetModel();
if (property.name() == "when"
&& QmlModelState::isValidQmlModelState(property.parentModelNode()))
resetModel();
if (property.name() == "extend")
resetExtend();
if (property.parentModelNode().simplifiedTypeName() == "PropertyChanges"
|| (property.name() == "changes"
&& property.parentModelNode().simplifiedTypeName() == "State"))
resetPropertyChangesModels();
}
}
void StatesEditorView::nodeAboutToBeRemoved(const ModelNode &removedNode)
{
if (removedNode.hasParentProperty()) {
const NodeAbstractProperty propertyParent = removedNode.parentProperty();
if (propertyParent.parentModelNode() == activeStateGroup().modelNode()
&& propertyParent.name() == "states")
m_lastIndex = propertyParent.indexOf(removedNode);
}
if (currentState().isValid() && removedNode == currentState())
setCurrentState(baseState());
if (removedNode.simplifiedTypeName() == "PropertyChanges")
m_propertyChangesRemoved = true;
if (removedNode.simplifiedTypeName() == "StateGroup") {
if (removedNode == activeStatesGroupNode())
setActiveStatesGroupNode(rootModelNode());
m_stateGroupRemoved = true;
}
}
void StatesEditorView::nodeRemoved(const ModelNode & /*removedNode*/,
const NodeAbstractProperty &parentProperty,
PropertyChangeFlags /*propertyChange*/)
{
if (parentProperty.isValid()
&& parentProperty.parentModelNode() == activeStateGroup().modelNode()
&& parentProperty.name() == "states") {
m_statesEditorModel->removeState(m_lastIndex);
m_lastIndex = -1;
resetModel();
}
if (m_propertyChangesRemoved) {
m_propertyChangesRemoved = false;
resetPropertyChangesModels();
}
if (m_stateGroupRemoved) {
m_stateGroupRemoved = false;
resetStateGroups();
}
}
void StatesEditorView::nodeAboutToBeReparented(const ModelNode &node,
const NodeAbstractProperty & /*newPropertyParent*/,
const NodeAbstractProperty &oldPropertyParent,
AbstractView::PropertyChangeFlags /*propertyChange*/)
{
if (oldPropertyParent.isValid()
&& oldPropertyParent.parentModelNode() == activeStateGroup().modelNode()
&& oldPropertyParent.name() == "states")
m_lastIndex = oldPropertyParent.indexOf(node);
if (node.simplifiedTypeName() == "StateGroup")
resetStateGroups();
}
void StatesEditorView::nodeReparented(const ModelNode &node,
const NodeAbstractProperty &newPropertyParent,
const NodeAbstractProperty &oldPropertyParent,
AbstractView::PropertyChangeFlags /*propertyChange*/)
{
if (oldPropertyParent.isValid()
&& oldPropertyParent.parentModelNode() == activeStateGroup().modelNode()
&& oldPropertyParent.name() == "states") {
m_statesEditorModel->removeState(m_lastIndex);
resetModel();
m_lastIndex = -1;
}
if (newPropertyParent.isValid()
&& newPropertyParent.parentModelNode() == activeStateGroup().modelNode()
&& newPropertyParent.name() == "states") {
int index = newPropertyParent.indexOf(node);
m_statesEditorModel->insertState(index);
}
if (node.simplifiedTypeName() == "PropertyChanges")
resetPropertyChangesModels();
}
void StatesEditorView::nodeOrderChanged(const NodeListProperty &listProperty)
{
if (m_block)
return;
if (listProperty.isValid() && listProperty.parentModelNode() == activeStateGroup().modelNode()
&& listProperty.name() == "states")
resetModel();
}
void StatesEditorView::bindingPropertiesChanged(const QList<BindingProperty> &propertyList, AbstractView::PropertyChangeFlags propertyChange)
{
Q_UNUSED(propertyChange)
for (const BindingProperty &property : propertyList) {
if (property.name() == "when"
&& QmlModelState::isValidQmlModelState(property.parentModelNode()))
resetModel();
if (property.parentModelNode().simplifiedTypeName() == "PropertyChanges")
resetPropertyChangesModels();
}
}
void StatesEditorView::variantPropertiesChanged(const QList<VariantProperty> &propertyList,
AbstractView::PropertyChangeFlags /*propertyChange*/)
{
if (m_block)
return;
m_block = true;
auto guard = qScopeGuard([&]() { m_block = false; });
for (const VariantProperty &property : propertyList) {
if (property.name() == "name" && QmlModelState::isValidQmlModelState(property.parentModelNode()))
resetModel();
else if (property.name() == "state"
&& property.parentModelNode() == activeStateGroup().modelNode())
resetModel();
else if (property.name() == "extend")
resetExtend();
if (property.parentModelNode().simplifiedTypeName() == "PropertyChanges")
resetPropertyChangesModels();
}
}
void StatesEditorView::customNotification(const AbstractView * /*view*/,
const QString &identifier,
const QList<ModelNode> & /*nodeList*/,
const QList<QVariant> & /*data*/)
{
if (identifier == StartRewriterAmend)
beginBulkChange();
if (identifier == EndRewriterAmend)
endBulkChange();
}
void StatesEditorView::rewriterBeginTransaction()
{
beginBulkChange();
}
void StatesEditorView::rewriterEndTransaction()
{
endBulkChange();
}
void StatesEditorView::currentStateChanged(const ModelNode &node)
{
QmlModelState newQmlModelState(node);
if (newQmlModelState.isBaseState())
m_statesEditorWidget->setCurrentStateInternalId(0);
else
m_statesEditorWidget->setCurrentStateInternalId(newQmlModelState.modelNode().internalId());
}
void StatesEditorView::instancesPreviewImageChanged(const QVector<ModelNode> &nodeList)
{
if (!model())
return;
int minimumIndex = 10000;
int maximumIndex = -1;
for (const ModelNode &node : nodeList) {
if (node.isRootNode()) {
minimumIndex = qMin(minimumIndex, 0);
maximumIndex = qMax(maximumIndex, 0);
} else {
int index = activeStateGroup().allStates().indexOf(QmlModelState(node)) + 1;
if (index > 0) {
minimumIndex = qMin(minimumIndex, index);
maximumIndex = qMax(maximumIndex, index);
}
}
}
if (maximumIndex >= 0)
m_statesEditorModel->updateState(minimumIndex, maximumIndex);
}
void StatesEditorView::moveStates(int from, int to)
{
if (m_block)
return;
m_block = true;
auto guard = qScopeGuard([&]() { m_block = false; });
if (!activeStatesGroupNode().hasNodeListProperty("states"))
return;
executeInTransaction("moveState", [this, from, to]() {
activeStatesGroupNode().nodeListProperty("states").slide(from - 1, to - 1);
});
}
} // namespace Experimental
} // namespace QmlDesigner

View File

@@ -0,0 +1,158 @@
/****************************************************************************
**
** 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 <abstractview.h>
#include <qmlstate.h>
#include <QSet>
namespace QmlDesigner {
class AnnotationEditor;
namespace Experimental {
class StatesEditorModel;
class StatesEditorWidget;
class PropertyChangesModel;
class StatesEditorView : public AbstractView {
Q_OBJECT
public:
explicit StatesEditorView(QObject *parent = nullptr);
~StatesEditorView() override;
void renameState(int internalNodeId,const QString &newName);
void setWhenCondition(int internalNodeId, const QString &condition);
void resetWhenCondition(int internalNodeId);
void setStateAsDefault(int internalNodeId);
void resetDefaultState();
bool hasDefaultState() const;
void setAnnotation(int internalNodeId);
void removeAnnotation(int internalNodeId);
bool hasAnnotation(int internalNodeId) const;
bool validStateName(const QString &name) const;
bool hasExtend() const;
QStringList extendedStates() const;
QString currentStateName() const;
void setCurrentState(const QmlModelState &state);
QmlModelState baseState() const;
QmlModelStateGroup activeStateGroup() const;
void moveStates(int from, int to);
// AbstractView
void modelAttached(Model *model) override;
void modelAboutToBeDetached(Model *model) override;
void propertiesRemoved(const QList<AbstractProperty>& propertyList) override;
void nodeAboutToBeRemoved(const ModelNode &removedNode) override;
void nodeRemoved(const ModelNode &removedNode,
const NodeAbstractProperty &parentProperty,
PropertyChangeFlags propertyChange) override;
void nodeAboutToBeReparented(const ModelNode &node,
const NodeAbstractProperty &newPropertyParent,
const NodeAbstractProperty &oldPropertyParent,
AbstractView::PropertyChangeFlags propertyChange) override;
void nodeReparented(const ModelNode &node,
const NodeAbstractProperty &newPropertyParent,
const NodeAbstractProperty &oldPropertyParent,
AbstractView::PropertyChangeFlags propertyChange) override;
void nodeOrderChanged(const NodeListProperty &listProperty) override;
void bindingPropertiesChanged(const QList<BindingProperty> &propertyList,
PropertyChangeFlags propertyChange) override;
void variantPropertiesChanged(const QList<VariantProperty> &propertyList,
PropertyChangeFlags propertyChange) override;
void customNotification(const AbstractView *view,
const QString &identifier,
const QList<ModelNode> &nodeList,
const QList<QVariant> &data) override;
void rewriterBeginTransaction() override;
void rewriterEndTransaction() override;
// AbstractView
void currentStateChanged(const ModelNode &node) override;
void instancesPreviewImageChanged(const QVector<ModelNode> &nodeList) override;
WidgetInfo widgetInfo() override;
void rootNodeTypeChanged(const QString &type, int majorVersion, int minorVersion) override;
ModelNode activeStatesGroupNode() const;
void setActiveStatesGroupNode(const ModelNode &modelNode);
int activeStatesGroupIndex() const;
void setActiveStatesGroupIndex(int index);
void registerPropertyChangesModel(PropertyChangesModel *model);
void deregisterPropertyChangesModel(PropertyChangesModel *model);
public slots:
void synchonizeCurrentStateFromWidget();
void createNewState();
void cloneState(int nodeId);
void extendState(int nodeId);
void removeState(int nodeId);
private:
StatesEditorWidget *statesEditorWidget() const;
void resetModel();
void resetPropertyChangesModels();
void resetExtend();
void resetStateGroups();
void checkForStatesAvailability();
void beginBulkChange();
void endBulkChange();
private:
QPointer<StatesEditorModel> m_statesEditorModel;
QPointer<StatesEditorWidget> m_statesEditorWidget;
int m_lastIndex;
bool m_block = false;
QPointer<AnnotationEditor> m_editor;
ModelNode m_activeStatesGroupNode;
bool m_propertyChangesRemoved = false;
bool m_stateGroupRemoved = false;
bool m_bulkChange = false;
bool m_modelDirty = false;
bool m_extendDirty = false;
bool m_propertyChangesDirty = false;
bool m_stateGroupsDirty = false;
QSet<PropertyChangesModel *> m_propertyChangedModels;
};
} // namespace Experimental
} // namespace QmlDesigner

View File

@@ -0,0 +1,199 @@
/****************************************************************************
**
** 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 "stateseditorwidget.h"
#include "stateseditormodel.h"
#include "stateseditorview.h"
#include "stateseditorimageprovider.h"
#include <designersettings.h>
#include <theme.h>
#include <qmldesignerconstants.h>
#include <qmldesignerplugin.h>
#include <invalidqmlsourceexception.h>
#include <coreplugin/messagebox.h>
#include <coreplugin/icore.h>
#include <utils/qtcassert.h>
#include <utils/stylehelper.h>
#include <QApplication>
#include <QFileInfo>
#include <QShortcut>
#include <QBoxLayout>
#include <QKeySequence>
#include <QQmlContext>
#include <QQmlEngine>
#include <QQuickItem>
enum {
debug = false
};
namespace QmlDesigner {
namespace Experimental {
static QString propertyEditorResourcesPath()
{
#ifdef SHARE_QML_PATH
if (qEnvironmentVariableIsSet("LOAD_QML_FROM_SOURCE"))
return QLatin1String(SHARE_QML_PATH) + "/propertyEditorQmlSources";
#endif
return Core::ICore::resourcePath("qmldesigner/propertyEditorQmlSources").toString();
}
int StatesEditorWidget::currentStateInternalId() const
{
QTC_ASSERT(rootObject(), return -1);
QTC_ASSERT(rootObject()->property("currentStateInternalId").isValid(), return -1);
return rootObject()->property("currentStateInternalId").toInt();
}
void StatesEditorWidget::setCurrentStateInternalId(int internalId)
{
QTC_ASSERT(rootObject(), return);
rootObject()->setProperty("currentStateInternalId", internalId);
}
void StatesEditorWidget::setNodeInstanceView(NodeInstanceView *nodeInstanceView)
{
m_imageProvider->setNodeInstanceView(nodeInstanceView);
}
void StatesEditorWidget::showAddNewStatesButton(bool showAddNewStatesButton)
{
rootContext()->setContextProperty(QLatin1String("canAddNewStates"), showAddNewStatesButton);
}
StatesEditorWidget::StatesEditorWidget(StatesEditorView *statesEditorView,
StatesEditorModel *statesEditorModel)
: m_statesEditorView(statesEditorView)
, m_imageProvider(nullptr)
, m_qmlSourceUpdateShortcut(nullptr)
{
m_imageProvider = new Internal::StatesEditorImageProvider;
m_imageProvider->setNodeInstanceView(statesEditorView->nodeInstanceView());
engine()->addImageProvider(QStringLiteral("qmldesigner_stateseditor"), m_imageProvider);
engine()->addImportPath(qmlSourcesPath());
engine()->addImportPath(propertyEditorResourcesPath() + "/imports");
engine()->addImportPath(qmlSourcesPath() + "/imports");
m_qmlSourceUpdateShortcut = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_F10), this);
connect(m_qmlSourceUpdateShortcut, &QShortcut::activated, this, &StatesEditorWidget::reloadQmlSource);
setResizeMode(QQuickWidget::SizeRootObjectToView);
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
rootContext()->setContextProperties(
QVector<QQmlContext::PropertyPair>{{{"statesEditorModel"},
QVariant::fromValue(statesEditorModel)},
{{"canAddNewStates"}, true}});
Theme::setupTheme(engine());
setWindowTitle(tr("States New", "Title of Editor widget"));
setMinimumWidth(195);
setMinimumHeight(195);
// init the first load of the QML UI elements
reloadQmlSource();
}
StatesEditorWidget::~StatesEditorWidget() = default;
QString StatesEditorWidget::qmlSourcesPath()
{
#ifdef SHARE_QML_PATH
if (qEnvironmentVariableIsSet("LOAD_QML_FROM_SOURCE"))
return QLatin1String(SHARE_QML_PATH) + "/newstateseditor";
#endif
return Core::ICore::resourcePath("qmldesigner/newstateseditor").toString();
}
void StatesEditorWidget::showEvent(QShowEvent *event)
{
QQuickWidget::showEvent(event);
update();
}
void StatesEditorWidget::focusOutEvent(QFocusEvent *focusEvent)
{
QmlDesignerPlugin::emitUsageStatisticsTime(Constants::EVENT_STATESEDITOR_TIME,
m_usageTimer.elapsed());
QQuickWidget::focusOutEvent(focusEvent);
}
void StatesEditorWidget::focusInEvent(QFocusEvent *focusEvent)
{
m_usageTimer.restart();
QQuickWidget::focusInEvent(focusEvent);
}
void StatesEditorWidget::reloadQmlSource()
{
QString statesListQmlFilePath = qmlSourcesPath() + QStringLiteral("/Main.qml");
QTC_ASSERT(QFileInfo::exists(statesListQmlFilePath), return );
engine()->clearComponentCache();
setSource(QUrl::fromLocalFile(statesListQmlFilePath));
if (!rootObject()) {
QString errorString;
for (const QQmlError &error : errors())
errorString += "\n" + error.toString();
Core::AsynchronousMessageBox::warning(tr("Cannot Create QtQuick View"),
tr("StatesEditorWidget: %1 cannot be created.%2")
.arg(qmlSourcesPath(), errorString));
return;
}
connect(rootObject(),
SIGNAL(currentStateInternalIdChanged()),
m_statesEditorView.data(),
SLOT(synchonizeCurrentStateFromWidget()));
connect(rootObject(),
SIGNAL(createNewState()),
m_statesEditorView.data(),
SLOT(createNewState()));
connect(rootObject(), SIGNAL(cloneState(int)), m_statesEditorView.data(), SLOT(cloneState(int)));
connect(rootObject(),
SIGNAL(extendState(int)),
m_statesEditorView.data(),
SLOT(extendState(int)));
connect(rootObject(),
SIGNAL(deleteState(int)),
m_statesEditorView.data(),
SLOT(removeState(int)));
m_statesEditorView.data()->synchonizeCurrentStateFromWidget();
}
} // namespace Experimental
} // namespace QmlDesigner

View File

@@ -0,0 +1,80 @@
/****************************************************************************
**
** 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 <QElapsedTimer>
#include <QPointer>
#include <QQmlPropertyMap>
#include <QQuickWidget>
QT_BEGIN_NAMESPACE
class QShortcut;
QT_END_NAMESPACE
namespace QmlDesigner {
class NodeInstanceView;
namespace Experimental {
class StatesEditorModel;
class StatesEditorView;
namespace Internal { class StatesEditorImageProvider; }
class StatesEditorWidget : public QQuickWidget
{
Q_OBJECT
public:
StatesEditorWidget(StatesEditorView *m_statesEditorView, StatesEditorModel *statesEditorModel);
~StatesEditorWidget() override;
int currentStateInternalId() const;
void setCurrentStateInternalId(int internalId);
void setNodeInstanceView(NodeInstanceView *nodeInstanceView);
void showAddNewStatesButton(bool showAddNewStatesButton);
static QString qmlSourcesPath();
protected:
void showEvent(QShowEvent *) override;
void focusOutEvent(QFocusEvent *focusEvent) override;
void focusInEvent(QFocusEvent *focusEvent) override;
private:
void reloadQmlSource();
private:
QPointer<StatesEditorView> m_statesEditorView;
Internal::StatesEditorImageProvider *m_imageProvider;
QShortcut *m_qmlSourceUpdateShortcut;
QElapsedTimer m_usageTimer;
};
} // namespace Experimental
} // namespace QmlDesigner

View File

@@ -24,6 +24,7 @@ QtcProduct {
"../components/navigator",
"../components/propertyeditor",
"../components/stateseditor",
"../components/stateseditornew",
"../designercore",
"../designercore/include",
"../../../../share/qtcreator/qml/qmlpuppet/interfaces",

View File

@@ -9,20 +9,24 @@
namespace QmlDesigner {
class QMLDESIGNERCORE_EXPORT QmlModelStateOperation : public QmlModelNodeFacade
class QMLDESIGNERCORE_EXPORT QmlModelStateOperation : public QmlModelNodeFacade
{
public:
QmlModelStateOperation() : QmlModelNodeFacade() {}
QmlModelStateOperation(const ModelNode &modelNode) : QmlModelNodeFacade(modelNode) {}
ModelNode target() const;
void setTarget(const ModelNode &target);
bool explicitValue() const;
void setExplicitValue(bool value);
bool restoreEntryValues() const;
void setRestoreEntryValues(bool value);
QList<AbstractProperty> targetProperties() const;
bool isValid() const override;
explicit operator bool() const { return isValid(); }
static bool isValidQmlModelStateOperation(const ModelNode &modelNode);
};
class QMLDESIGNERCORE_EXPORT QmlPropertyChanges : public QmlModelStateOperation
class QMLDESIGNERCORE_EXPORT QmlPropertyChanges : public QmlModelStateOperation
{
public:
QmlPropertyChanges() : QmlModelStateOperation() {}

View File

@@ -121,6 +121,8 @@ public:
QList<ModelNode> allTimelines() const;
QList<ModelNode> getAllConnections() const;
protected:
NodeInstance nodeInstance() const;
QmlObjectNode nodeForInstance(const NodeInstance &instance) const;

View File

@@ -16,9 +16,14 @@ class Annotation;
class AnnotationEditor;
class StatesEditorView;
namespace Experimental {
class StatesEditorView;
}
class QMLDESIGNERCORE_EXPORT QmlModelState final : public QmlModelNodeFacade
{
friend StatesEditorView;
friend Experimental::StatesEditorView;
public:
QmlModelState();
@@ -61,6 +66,10 @@ public:
bool hasAnnotation() const;
void removeAnnotation();
QString extend() const;
void setExtend(const QString &name);
bool hasExtend() const;
protected:
void addChangeSetIfNotExists(const ModelNode &node);
static QmlModelState createBaseState(const AbstractView *view);

View File

@@ -103,6 +103,7 @@ class QMLDESIGNERCORE_EXPORT QmlModelStateGroup
{
friend class QmlObjectNode;
friend class StatesEditorView;
friend class Experimental::StatesEditorView;
public:
QmlModelStateGroup() = default;

View File

@@ -7,6 +7,8 @@
#include "abstractview.h"
#include <metainfo.h>
#include <utils/algorithm.h>
namespace QmlDesigner {
ModelNode QmlModelStateOperation::target() const
@@ -22,6 +24,40 @@ void QmlModelStateOperation::setTarget(const ModelNode &target)
modelNode().bindingProperty("target").setExpression(target.id());
}
bool QmlModelStateOperation::explicitValue() const
{
if (modelNode().property("explicit").isVariantProperty())
return modelNode().variantProperty("explicit").value().toBool();
return false;
}
void QmlModelStateOperation::setExplicitValue(bool value)
{
modelNode().variantProperty("explicit").setValue(value);
}
bool QmlModelStateOperation::restoreEntryValues() const
{
if (modelNode().property("restoreEntryValues").isVariantProperty())
return modelNode().variantProperty("restoreEntryValues").value().toBool();
return false;
}
void QmlModelStateOperation::setRestoreEntryValues(bool value)
{
modelNode().variantProperty("restoreEntryValues").setValue(value);
}
QList<AbstractProperty> QmlModelStateOperation::targetProperties() const
{
return Utils::filtered(modelNode().properties(), [](const AbstractProperty &property) {
const QList<PropertyName> ignore = {"target", "explicit", "restoreEntryValues"};
return !ignore.contains(property.name());
});
}
bool QmlPropertyChanges::isValid() const
{
return isValidQmlPropertyChanges(modelNode());

View File

@@ -25,6 +25,7 @@
#include <qmldesignerplugin.h>
#endif
#include <utils/algorithm.h>
#include <utils/qtcassert.h>
#include <QRegularExpression>
@@ -593,6 +594,18 @@ QList<ModelNode> QmlObjectNode::allTimelines() const
return timelineNodes;
}
QList<ModelNode> QmlObjectNode::getAllConnections() const
{
// if (!isValid())
return {};
// auto list = view()->allModelNodesOfType("QtQuick.Connections");
// return Utils::filtered(list, [this](const ModelNode &connection) {
// return connection.hasBindingProperty("target")
// && connection.bindingProperty("target").resolveToModelNode() == modelNode();
// });
}
/*!
Removes a variant property of the object specified by \a name from the
model.

View File

@@ -22,7 +22,7 @@ QmlModelState::QmlModelState()
}
QmlModelState::QmlModelState(const ModelNode &modelNode)
: QmlModelNodeFacade(modelNode)
: QmlModelNodeFacade(modelNode)
{
}
@@ -48,7 +48,7 @@ QList<QmlModelStateOperation> QmlModelState::stateOperations(const ModelNode &no
{
QList<QmlModelStateOperation> returnList;
if (!isBaseState() && modelNode().hasNodeListProperty("changes")) {
if (!isBaseState() && modelNode().hasNodeListProperty("changes")) {
const QList<ModelNode> nodes = modelNode().nodeListProperty("changes").toModelNodeList();
for (const ModelNode &childNode : nodes) {
if (QmlModelStateOperation::isValidQmlModelStateOperation(childNode)) {
@@ -67,7 +67,7 @@ QList<QmlPropertyChanges> QmlModelState::propertyChanges() const
{
QList<QmlPropertyChanges> returnList;
if (!isBaseState() && modelNode().hasNodeListProperty("changes")) {
if (!isBaseState() && modelNode().hasNodeListProperty("changes")) {
const QList<ModelNode> nodes = modelNode().nodeListProperty("changes").toModelNodeList();
for (const ModelNode &childNode : nodes) {
//### exception if not valid QmlModelStateOperation
@@ -82,7 +82,7 @@ QList<QmlPropertyChanges> QmlModelState::propertyChanges() const
bool QmlModelState::hasPropertyChanges(const ModelNode &node) const
{
if (!isBaseState() && modelNode().hasNodeListProperty("changes")) {
if (!isBaseState() && modelNode().hasNodeListProperty("changes")) {
const QList<QmlPropertyChanges> changes = propertyChanges();
for (const QmlPropertyChanges &changeSet : changes) {
if (changeSet.target().isValid() && changeSet.target() == node)
@@ -110,7 +110,7 @@ QList<QmlModelStateOperation> QmlModelState::stateOperations() const
//### exception if not valid
QList<QmlModelStateOperation> returnList;
if (!isBaseState() && modelNode().hasNodeListProperty("changes")) {
if (!isBaseState() && modelNode().hasNodeListProperty("changes")) {
const QList<ModelNode> nodes = modelNode().nodeListProperty("changes").toModelNodeList();
for (const ModelNode &childNode : nodes) {
//### exception if not valid QmlModelStateOperation
@@ -347,6 +347,28 @@ void QmlModelState::removeAnnotation()
}
}
QString QmlModelState::extend() const
{
if (isBaseState())
return QString();
return modelNode().variantProperty("extend").value().toString();
}
void QmlModelState::setExtend(const QString &name)
{
if ((!isBaseState()) && (modelNode().isValid()))
modelNode().variantProperty("extend").setValue(name);
}
bool QmlModelState::hasExtend() const
{
if (!isBaseState() && modelNode().isValid())
return modelNode().hasVariantProperty("extend");
return false;
}
QmlModelState QmlModelState::createBaseState(const AbstractView *view)
{
QmlModelState qmlModelState(view->rootModelNode());

View File

@@ -6,6 +6,7 @@
#ifndef QMLDESIGNER_TEST
#include <abstractview.h>
#include <assetslibraryview.h>
#include <capturingconnectionmanager.h>
#include <componentaction.h>
#include <componentview.h>
@@ -16,13 +17,13 @@
#include <edit3dview.h>
#include <formeditorview.h>
#include <itemlibraryview.h>
#include <assetslibraryview.h>
#include <materialbrowserview.h>
#include <materialeditorview.h>
#include <navigatorview.h>
#include <nodeinstanceview.h>
#include <propertyeditorview.h>
#include <materialeditorview.h>
#include <materialbrowserview.h>
#include <rewriterview.h>
#include <stateseditornew/stateseditorview.h>
#include <stateseditorview.h>
#include <texteditorview.h>
#include <qmldesignerplugin.h>
@@ -35,6 +36,14 @@
namespace QmlDesigner {
static bool useOldStatesEditor()
{
return QmlDesignerPlugin::instance()
->settings()
.value(DesignerSettingsKey::OLD_STATES_EDITOR)
.toBool();
}
static Q_LOGGING_CATEGORY(viewBenchmark, "qtc.viewmanager.attach", QtWarningMsg)
class ViewManagerData
@@ -64,6 +73,7 @@ public:
MaterialEditorView materialEditorView;
MaterialBrowserView materialBrowserView;
StatesEditorView statesEditorView;
Experimental::StatesEditorView newStatesEditorView;
std::vector<std::unique_ptr<AbstractView>> additionalViews;
bool disableStandardViews = false;
@@ -142,16 +152,30 @@ void ViewManager::detachRewriterView()
void ViewManager::switchStateEditorViewToBaseState()
{
if (d->statesEditorView.isAttached()) {
d->savedState = d->statesEditorView.currentState();
d->statesEditorView.setCurrentState(d->statesEditorView.baseState());
if (useOldStatesEditor()) {
if (d->statesEditorView.isAttached()) {
d->savedState = d->statesEditorView.currentState();
d->statesEditorView.setCurrentState(d->statesEditorView.baseState());
}
} else {
// TODO remove old statesview
if (d->newStatesEditorView.isAttached()) {
d->savedState = d->newStatesEditorView.currentState();
d->newStatesEditorView.setCurrentState(d->newStatesEditorView.baseState());
}
}
}
void ViewManager::switchStateEditorViewToSavedState()
{
if (d->savedState.isValid() && d->statesEditorView.isAttached())
d->statesEditorView.setCurrentState(d->savedState);
if (useOldStatesEditor()) {
if (d->savedState.isValid() && d->statesEditorView.isAttached())
d->statesEditorView.setCurrentState(d->savedState);
} else {
// TODO remove old statesview
if (d->savedState.isValid() && d->newStatesEditorView.isAttached())
d->newStatesEditorView.setCurrentState(d->savedState);
}
}
QList<AbstractView *> ViewManager::views() const
@@ -174,11 +198,19 @@ QList<AbstractView *> ViewManager::standardViews() const
&d->materialEditorView,
&d->materialBrowserView,
&d->statesEditorView,
&d->newStatesEditorView, // TODO
&d->designerActionManagerView};
if (QmlDesignerPlugin::instance()->settings().value(
DesignerSettingsKey::ENABLE_DEBUGVIEW).toBool())
list.append(&d->debugView);
if (useOldStatesEditor())
list.removeAll(&d->newStatesEditorView);
else
list.removeAll(&d->statesEditorView);
if (QmlDesignerPlugin::instance()
->settings()
.value(DesignerSettingsKey::ENABLE_DEBUGVIEW)
.toBool())
list.append(&d->debugView);
return list;
}
@@ -308,7 +340,11 @@ QList<WidgetInfo> ViewManager::widgetInfos() const
widgetInfoList.append(d->propertyEditorView.widgetInfo());
widgetInfoList.append(d->materialEditorView.widgetInfo());
widgetInfoList.append(d->materialBrowserView.widgetInfo());
widgetInfoList.append(d->statesEditorView.widgetInfo());
if (useOldStatesEditor())
widgetInfoList.append(d->statesEditorView.widgetInfo());
else
widgetInfoList.append(d->newStatesEditorView.widgetInfo());
if (d->debugView.hasWidget())
widgetInfoList.append(d->debugView.widgetInfo());

View File

@@ -63,6 +63,7 @@ void DesignerSettings::fromSettings(QSettings *settings)
restoreValue(settings, DesignerSettingsKey::EDIT3DVIEW_GRID_COLOR, "#aaaaaa");
restoreValue(settings, DesignerSettingsKey::SMOOTH_RENDERING, false);
restoreValue(settings, DesignerSettingsKey::SHOW_DEBUG_SETTINGS, false);
restoreValue(settings, DesignerSettingsKey::OLD_STATES_EDITOR, true);
settings->endGroup();
settings->endGroup();

View File

@@ -52,6 +52,7 @@ const char ALWAYS_DESIGN_MODE[] = "AlwaysDesignMode";
const char DISABLE_ITEM_LIBRARY_UPDATE_TIMER[] = "DisableItemLibraryUpdateTimer";
const char ASK_BEFORE_DELETING_ASSET[] = "AskBeforeDeletingAsset";
const char SMOOTH_RENDERING[] = "SmoothRendering";
const char OLD_STATES_EDITOR[] = "OldStatesEditor";
}
class QMLDESIGNERCORE_EXPORT DesignerSettings : public QHash<QByteArray, QVariant>

View File

@@ -90,6 +90,8 @@ const int MODELNODE_PREVIEW_IMAGE_DIMENSIONS = 150;
const char EVENT_TIMELINE_ADDED[] = "timelineAdded";
const char EVENT_TRANSITION_ADDED[] = "transitionAdded";
const char EVENT_STATE_ADDED[] = "stateAdded";
const char EVENT_STATE_CLONED[] = "stateCloned";
const char EVENT_STATE_EXTENDED[] = "stateExtended";
const char EVENT_CONNECTION_ADDED[] = "connectionAdded";
const char EVENT_PROPERTY_ADDED[] = "propertyAdded";
const char EVENT_ANNOTATION_ADDED[] = "annotationAdded";

View File

@@ -4,14 +4,15 @@
#include "qmldesignerplugin.h"
#include "designmodecontext.h"
#include "designmodewidget.h"
#include "dynamiclicensecheck.h"
#include "exception.h"
#include "generateresource.h"
#include "nodeinstanceview.h"
#include "openuiqmlfiledialog.h"
#include "qmldesignerconstants.h"
#include "qmldesignerprojectmanager.h"
#include "quick2propertyeditorview.h"
#include "settingspage.h"
#include "dynamiclicensecheck.h"
#include <metainfo.h>
#include <connectionview.h>
@@ -258,7 +259,8 @@ bool QmlDesignerPlugin::initialize(const QStringList & /*arguments*/, QString *e
designerActionManager().addDesignerAction(shutDownNanotraceAction);
#endif
//TODO Move registering those types out of the property editor, since they are used also in the states editor
Quick2PropertyEditorView::registerQmlTypes();
return true;
}

View File

@@ -60,6 +60,7 @@ Project {
"components/navigator",
"components/pluginmanager",
"components/stateseditor",
"components/stateseditornew",
"components/texteditor",
"components/timelineeditor",
"components/listmodeleditor",
@@ -821,6 +822,18 @@ Project {
"stateseditor/stateseditorview.h",
"stateseditor/stateseditorwidget.cpp",
"stateseditor/stateseditorwidget.h",
"stateseditornew/propertychangesmodel.cpp",
"stateseditornew/propertychangesmodel.h",
"stateseditornew/propertymodel.cpp",
"stateseditornew/propertymodel.h",
"stateseditornew/stateseditorimageprovider.cpp",
"stateseditornew/stateseditorimageprovider.h",
"stateseditornew/stateseditormodel.cpp",
"stateseditornew/stateseditormodel.h",
"stateseditornew/stateseditorview.cpp",
"stateseditornew/stateseditorview.h",
"stateseditornew/stateseditorwidget.cpp",
"stateseditornew/stateseditorwidget.h",
]
}